Merge pull request #31399 from backstage/bui-css-module
Convert component styles into CSS Modules
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/ui': minor
|
||||
---
|
||||
|
||||
**BREAKING** Restructure Backstage UI component styling to use CSS Modules instead of pure CSS. We don't expect this to be an issue in practice but it is important to call out that all styles are now loaded through CSS modules with generated class names. We are still providing fixed class names for all components to allow anyone to style their Backstage instance.
|
||||
+1
-1966
File diff suppressed because it is too large
Load Diff
@@ -481,6 +481,11 @@ export const componentDefinitions: {
|
||||
readonly footer: 'bui-DialogFooter';
|
||||
};
|
||||
};
|
||||
readonly FieldError: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-FieldError';
|
||||
};
|
||||
};
|
||||
readonly FieldLabel: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-FieldLabelWrapper';
|
||||
@@ -700,6 +705,14 @@ export const componentDefinitions: {
|
||||
readonly cellProfileLink: 'bui-TableCellProfileLink';
|
||||
};
|
||||
};
|
||||
readonly TablePagination: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-TablePagination';
|
||||
readonly left: 'bui-TablePaginationLeft';
|
||||
readonly right: 'bui-TablePaginationRight';
|
||||
readonly select: 'bui-TablePaginationSelect';
|
||||
};
|
||||
};
|
||||
readonly Tabs: {
|
||||
readonly classNames: {
|
||||
readonly tabs: 'bui-Tabs';
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Avatar as AvatarPrimitive } from '@base-ui-components/react/avatar';
|
||||
import clsx from 'clsx';
|
||||
import { AvatarProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Avatar.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Avatar = forwardRef<
|
||||
@@ -33,12 +34,17 @@ export const Avatar = forwardRef<
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
data-size={size}
|
||||
{...rest}
|
||||
>
|
||||
<AvatarPrimitive.Image className={classNames.image} src={src} />
|
||||
<AvatarPrimitive.Fallback className={classNames.fallback}>
|
||||
<AvatarPrimitive.Image
|
||||
className={clsx(classNames.image, styles[classNames.image])}
|
||||
src={src}
|
||||
/>
|
||||
<AvatarPrimitive.Fallback
|
||||
className={clsx(classNames.fallback, styles[classNames.fallback])}
|
||||
>
|
||||
{(name || '')
|
||||
.split(' ')
|
||||
.map(word => word[0])
|
||||
|
||||
@@ -18,6 +18,7 @@ import { createElement, forwardRef } from 'react';
|
||||
import { BoxProps } from './types';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Box.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
@@ -32,7 +33,12 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
as,
|
||||
{
|
||||
ref,
|
||||
className: clsx(classNames.root, utilityClasses, className),
|
||||
className: clsx(
|
||||
classNames.root,
|
||||
styles[classNames.root],
|
||||
utilityClasses,
|
||||
className,
|
||||
),
|
||||
style,
|
||||
...rest,
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@ import { forwardRef, Ref } from 'react';
|
||||
import { Button as RAButton } from 'react-aria-components';
|
||||
import type { ButtonProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Button.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Button = forwardRef(
|
||||
@@ -33,7 +34,7 @@ export const Button = forwardRef(
|
||||
|
||||
return (
|
||||
<RAButton
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
ref={ref}
|
||||
{...dataAttributes}
|
||||
{...rest}
|
||||
|
||||
@@ -19,6 +19,8 @@ import { forwardRef, Ref } from 'react';
|
||||
import { Button as RAButton } from 'react-aria-components';
|
||||
import type { ButtonIconProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import stylesButtonIcon from './ButtonIcon.module.css';
|
||||
import stylesButton from '../Button/Button.module.css';
|
||||
|
||||
/** @public */
|
||||
export const ButtonIcon = forwardRef(
|
||||
@@ -35,7 +37,13 @@ export const ButtonIcon = forwardRef(
|
||||
|
||||
return (
|
||||
<RAButton
|
||||
className={clsx(classNames.root, classNamesButtonIcon.root, className)}
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
classNamesButtonIcon.root,
|
||||
stylesButton[classNames.root],
|
||||
stylesButtonIcon[classNamesButtonIcon.root],
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...dataAttributes}
|
||||
{...rest}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useNavigate, useHref } from 'react-router-dom';
|
||||
import type { ButtonLinkProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
import stylesButton from '../Button/Button.module.css';
|
||||
|
||||
/** @public */
|
||||
export const ButtonLink = forwardRef(
|
||||
@@ -47,6 +48,7 @@ export const ButtonLink = forwardRef(
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
classNamesButtonLink.root,
|
||||
stylesButton[classNames.root],
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
@@ -68,6 +70,7 @@ export const ButtonLink = forwardRef(
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
classNamesButtonLink.root,
|
||||
stylesButton[classNames.root],
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -24,6 +24,7 @@ import type {
|
||||
CardBodyProps,
|
||||
CardFooterProps,
|
||||
} from './types';
|
||||
import styles from './Card.module.css';
|
||||
|
||||
/**
|
||||
* Card component.
|
||||
@@ -35,7 +36,11 @@ export const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
const { classNames } = useStyles('Card');
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx(classNames.root, className)} {...rest} />
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,7 +55,15 @@ export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
||||
const { classNames } = useStyles('Card');
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx(classNames.header, className)} {...rest} />
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.header,
|
||||
styles[classNames.header],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -68,7 +81,7 @@ export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
||||
return (
|
||||
<ScrollArea.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.body, className)}
|
||||
className={clsx(classNames.body, styles[classNames.body], className)}
|
||||
{...rest}
|
||||
>
|
||||
<ScrollArea.Viewport style={{ paddingInline: 'var(--bui-space-3)' }}>
|
||||
@@ -96,7 +109,15 @@ export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(
|
||||
const { classNames } = useStyles('Card');
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx(classNames.footer, className)} {...rest} />
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.footer,
|
||||
styles[classNames.footer],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Icon } from '../..';
|
||||
import type { CheckboxProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import clsx from 'clsx';
|
||||
import styles from './Checkbox.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
||||
@@ -41,7 +42,7 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
||||
const checkboxElement = (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
checked={checked}
|
||||
onCheckedChange={onChange}
|
||||
disabled={disabled}
|
||||
@@ -50,14 +51,16 @@ export const Checkbox = forwardRef<HTMLButtonElement, CheckboxProps>(
|
||||
value={value}
|
||||
style={style}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator className={classNames.indicator}>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={clsx(classNames.indicator, styles[classNames.indicator])}
|
||||
>
|
||||
<Icon name="check" size={12} />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
|
||||
return label ? (
|
||||
<label className={classNames.label}>
|
||||
<label className={clsx(classNames.label, styles[classNames.label])}>
|
||||
{checkboxElement}
|
||||
{label}
|
||||
</label>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { forwardRef } from 'react';
|
||||
import { Collapsible as CollapsiblePrimitive } from '@base-ui-components/react/collapsible';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Collapsible.module.css';
|
||||
|
||||
const CollapsibleRoot = forwardRef<
|
||||
React.ElementRef<typeof CollapsiblePrimitive.Root>,
|
||||
@@ -28,7 +29,7 @@ const CollapsibleRoot = forwardRef<
|
||||
return (
|
||||
<CollapsiblePrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -44,7 +45,11 @@ const CollapsibleTrigger = forwardRef<
|
||||
return (
|
||||
<CollapsiblePrimitive.Trigger
|
||||
ref={ref}
|
||||
className={clsx(classNames.trigger, className)}
|
||||
className={clsx(
|
||||
classNames.trigger,
|
||||
styles[classNames.trigger],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -60,7 +65,7 @@ const CollapsiblePanel = forwardRef<
|
||||
return (
|
||||
<CollapsiblePrimitive.Panel
|
||||
ref={ref}
|
||||
className={clsx(classNames.panel, className)}
|
||||
className={clsx(classNames.panel, styles[classNames.panel], className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { forwardRef } from 'react';
|
||||
import { ContainerProps } from './types';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Container.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Container = forwardRef<HTMLDivElement, ContainerProps>(
|
||||
@@ -32,7 +33,12 @@ export const Container = forwardRef<HTMLDivElement, ContainerProps>(
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, utilityClasses, className)}
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
utilityClasses,
|
||||
styles[classNames.root],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
.bui-DialogOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(232, 232, 232, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .bui-Dialog {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.bui-DialogOverlay[data-entering] {
|
||||
animation: fade-in 200ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-DialogOverlay[data-exiting] {
|
||||
animation: fade-out 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-Dialog {
|
||||
background: var(--bui-bg-surface-1);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--bui-border);
|
||||
color: var(--bui-fg-primary);
|
||||
position: relative;
|
||||
width: min(var(--bui-dialog-min-width, 400px), calc(100vw - 3rem));
|
||||
max-width: calc(100vw - 3rem);
|
||||
height: min(var(--bui-dialog-min-height, auto), calc(100vh - 3rem));
|
||||
max-height: calc(100vh - 3rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Dialog entering animation */
|
||||
.bui-DialogOverlay[data-entering] .bui-Dialog {
|
||||
animation: dialog-enter 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
/* Dialog exiting animation */
|
||||
.bui-DialogOverlay[data-exiting] .bui-Dialog {
|
||||
animation: dialog-exit 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-DialogHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-2);
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-DialogHeaderTitle {
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-weight: var(--bui-font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bui-DialogFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: var(--bui-space-2);
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-3);
|
||||
border-top: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-DialogBody {
|
||||
padding: var(--bui-space-3);
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Keyframe animations */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-enter {
|
||||
from {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-exit {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
/* Backdrop */
|
||||
.bui-DialogOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(232, 232, 232, 0.8);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
[data-theme='dark'] .bui-Dialog {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.bui-DialogOverlay[data-entering] {
|
||||
animation: fade-in 200ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-DialogOverlay[data-exiting] {
|
||||
animation: fade-out 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-Dialog {
|
||||
background: var(--bui-bg-surface-1);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--bui-border);
|
||||
color: var(--bui-fg-primary);
|
||||
position: relative;
|
||||
width: min(var(--bui-dialog-min-width, 400px), calc(100vw - 3rem));
|
||||
max-width: calc(100vw - 3rem);
|
||||
height: min(var(--bui-dialog-min-height, auto), calc(100vh - 3rem));
|
||||
max-height: calc(100vh - 3rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Dialog entering animation */
|
||||
.bui-DialogOverlay[data-entering] .bui-Dialog {
|
||||
animation: dialog-enter 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
/* Dialog exiting animation */
|
||||
.bui-DialogOverlay[data-exiting] .bui-Dialog {
|
||||
animation: dialog-exit 150ms ease-out forwards;
|
||||
}
|
||||
|
||||
.bui-DialogHeader {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-2);
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-DialogHeaderTitle {
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-weight: var(--bui-font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.bui-DialogFooter {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: var(--bui-space-2);
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-3);
|
||||
border-top: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-DialogBody {
|
||||
padding: var(--bui-space-3);
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* Keyframe animations */
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade-out {
|
||||
from {
|
||||
opacity: 1;
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-enter {
|
||||
from {
|
||||
opacity: 0.5;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dialog-exit {
|
||||
from {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
to {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
@@ -28,11 +28,11 @@ import type {
|
||||
DialogProps,
|
||||
DialogBodyProps,
|
||||
} from './types';
|
||||
import './Dialog.styles.css';
|
||||
import { RiCloseLine } from '@remixicon/react';
|
||||
import { Button } from '../Button';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { Flex } from '../Flex';
|
||||
import styles from './Dialog.module.css';
|
||||
|
||||
/** @public */
|
||||
export const DialogTrigger = (props: DialogTriggerProps) => {
|
||||
@@ -47,13 +47,17 @@ export const Dialog = forwardRef<React.ElementRef<typeof Modal>, DialogProps>(
|
||||
return (
|
||||
<Modal
|
||||
ref={ref}
|
||||
className={clsx(classNames.overlay)}
|
||||
className={clsx(classNames.overlay, styles[classNames.overlay])}
|
||||
isDismissable
|
||||
isKeyboardDismissDisabled={false}
|
||||
{...props}
|
||||
>
|
||||
<RADialog
|
||||
className={clsx(classNames.dialog, className)}
|
||||
className={clsx(
|
||||
classNames.dialog,
|
||||
styles[classNames.dialog],
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
['--bui-dialog-min-width' as keyof React.CSSProperties]:
|
||||
typeof width === 'number' ? `${width}px` : width || '400px',
|
||||
@@ -82,8 +86,15 @@ export const DialogHeader = forwardRef<
|
||||
const { classNames } = useStyles('Dialog');
|
||||
|
||||
return (
|
||||
<Flex ref={ref} className={clsx(classNames.header, className)} {...props}>
|
||||
<Heading slot="title" className={classNames.headerTitle}>
|
||||
<Flex
|
||||
ref={ref}
|
||||
className={clsx(classNames.header, styles[classNames.header], className)}
|
||||
{...props}
|
||||
>
|
||||
<Heading
|
||||
slot="title"
|
||||
className={clsx(classNames.headerTitle, styles[classNames.headerTitle])}
|
||||
>
|
||||
{children}
|
||||
</Heading>
|
||||
<Button name="close" aria-label="Close" variant="tertiary" slot="close">
|
||||
@@ -100,7 +111,11 @@ export const DialogBody = forwardRef<React.ElementRef<'div'>, DialogBodyProps>(
|
||||
const { classNames } = useStyles('Dialog');
|
||||
|
||||
return (
|
||||
<div className={clsx(classNames.body, className)} ref={ref} {...props}>
|
||||
<div
|
||||
className={clsx(classNames.body, styles[classNames.body], className)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -117,7 +132,11 @@ export const DialogFooter = forwardRef<
|
||||
const { classNames } = useStyles('Dialog');
|
||||
|
||||
return (
|
||||
<div ref={ref} className={clsx(classNames.footer, className)} {...props}>
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.footer, styles[classNames.footer], className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -20,15 +20,19 @@ import {
|
||||
type FieldErrorProps,
|
||||
} from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import styles from './FieldError.module.css';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
|
||||
/** @public */
|
||||
export const FieldError = forwardRef<HTMLDivElement, FieldErrorProps>(
|
||||
(props: FieldErrorProps, ref) => {
|
||||
const { className, ...rest } = props;
|
||||
|
||||
const { classNames } = useStyles('FieldError');
|
||||
|
||||
return (
|
||||
<AriaFieldError
|
||||
className={clsx('bui-FieldError', className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
ref={ref}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -17,6 +17,8 @@ import { Label } from 'react-aria-components';
|
||||
import { forwardRef } from 'react';
|
||||
import type { FieldLabelProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './FieldLabel.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const FieldLabel = forwardRef<HTMLDivElement, FieldLabelProps>(
|
||||
@@ -28,19 +30,40 @@ export const FieldLabel = forwardRef<HTMLDivElement, FieldLabelProps>(
|
||||
if (!label) return null;
|
||||
|
||||
return (
|
||||
<div className={classNames.root} {...rest} ref={ref}>
|
||||
<div
|
||||
className={clsx(classNames.root, styles[classNames.root])}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
>
|
||||
{label && (
|
||||
<Label className={classNames.label} htmlFor={htmlFor} id={id}>
|
||||
<Label
|
||||
className={clsx(classNames.label, styles[classNames.label])}
|
||||
htmlFor={htmlFor}
|
||||
id={id}
|
||||
>
|
||||
{label}
|
||||
{secondaryLabel && (
|
||||
<span aria-hidden="true" className={classNames.secondaryLabel}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={clsx(
|
||||
classNames.secondaryLabel,
|
||||
styles[classNames.secondaryLabel],
|
||||
)}
|
||||
>
|
||||
({secondaryLabel})
|
||||
</span>
|
||||
)}
|
||||
</Label>
|
||||
)}
|
||||
{description && (
|
||||
<div className={classNames.description}>{description}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.description,
|
||||
styles[classNames.description],
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ import { forwardRef } from 'react';
|
||||
import { FlexProps } from './types';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Flex.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
@@ -31,7 +32,12 @@ export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, utilityClasses, className)}
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
utilityClasses,
|
||||
styles[classNames.root],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import type { GridItemProps, GridProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Grid.module.css';
|
||||
|
||||
const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
|
||||
@@ -30,7 +31,12 @@ const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, utilityClasses, className)}
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
utilityClasses,
|
||||
styles[classNames.root],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
@@ -48,7 +54,12 @@ const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, utilityClasses, className)}
|
||||
className={clsx(
|
||||
classNames.root,
|
||||
utilityClasses,
|
||||
styles[classNames.root],
|
||||
className,
|
||||
)}
|
||||
style={style}
|
||||
{...rest}
|
||||
/>
|
||||
|
||||
@@ -19,6 +19,8 @@ import { HeaderToolbar } from './HeaderToolbar';
|
||||
import { Tabs, TabList, Tab } from '../Tabs';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { type NavigateOptions } from 'react-router-dom';
|
||||
import styles from './Header.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
declare module 'react-aria-components' {
|
||||
interface RouterConfig {
|
||||
@@ -49,7 +51,12 @@ export const Header = (props: HeaderProps) => {
|
||||
hasTabs={hasTabs}
|
||||
/>
|
||||
{tabs && (
|
||||
<div className={classNames.tabsWrapper}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.tabsWrapper,
|
||||
styles[classNames.tabsWrapper],
|
||||
)}
|
||||
>
|
||||
<Tabs onSelectionChange={onTabSelectionChange}>
|
||||
<TabList>
|
||||
{tabs?.map(tab => (
|
||||
|
||||
@@ -21,6 +21,8 @@ import { RiShapesLine } from '@remixicon/react';
|
||||
import type { HeaderToolbarProps } from './types';
|
||||
import { Text } from '../Text';
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
import styles from './Header.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* A component that renders a toolbar.
|
||||
@@ -39,27 +41,65 @@ export const HeaderToolbar = (props: HeaderToolbarProps) => {
|
||||
|
||||
const titleContent = (
|
||||
<>
|
||||
<div className={classNames.toolbarIcon}>{icon || <RiShapesLine />}</div>
|
||||
<div
|
||||
className={clsx(classNames.toolbarIcon, styles[classNames.toolbarIcon])}
|
||||
>
|
||||
{icon || <RiShapesLine />}
|
||||
</div>
|
||||
<Text variant="body-medium">{title || 'Your plugin'}</Text>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<div className={classNames.toolbar} data-has-tabs={hasTabs}>
|
||||
<div className={classNames.toolbarWrapper} ref={toolbarWrapperRef}>
|
||||
<div className={classNames.toolbarContent} ref={toolbarContentRef}>
|
||||
<div
|
||||
className={clsx(classNames.toolbar, styles[classNames.toolbar])}
|
||||
data-has-tabs={hasTabs}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.toolbarWrapper,
|
||||
styles[classNames.toolbarWrapper],
|
||||
)}
|
||||
ref={toolbarWrapperRef}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.toolbarContent,
|
||||
styles[classNames.toolbarContent],
|
||||
)}
|
||||
ref={toolbarContentRef}
|
||||
>
|
||||
<Text as="h1" variant="body-medium">
|
||||
{titleLink ? (
|
||||
<Link className={classNames.toolbarName} href={titleLink}>
|
||||
<Link
|
||||
className={clsx(
|
||||
classNames.toolbarName,
|
||||
styles[classNames.toolbarName],
|
||||
)}
|
||||
href={titleLink}
|
||||
>
|
||||
{titleContent}
|
||||
</Link>
|
||||
) : (
|
||||
<div className={classNames.toolbarName}>{titleContent}</div>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.toolbarName,
|
||||
styles[classNames.toolbarName],
|
||||
)}
|
||||
>
|
||||
{titleContent}
|
||||
</div>
|
||||
)}
|
||||
</Text>
|
||||
</div>
|
||||
<div className={classNames.toolbarControls} ref={toolbarControlsRef}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.toolbarControls,
|
||||
styles[classNames.toolbarControls],
|
||||
)}
|
||||
ref={toolbarControlsRef}
|
||||
>
|
||||
{customActions}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,8 @@ import { useStyles } from '../../hooks/useStyles';
|
||||
import { Container } from '../Container';
|
||||
import { Link } from '../Link';
|
||||
import { Fragment } from 'react/jsx-runtime';
|
||||
import styles from './HeaderPage.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* A component that renders a header page.
|
||||
@@ -33,9 +35,14 @@ export const HeaderPage = (props: HeaderPageProps) => {
|
||||
const { classNames } = useStyles('HeaderPage');
|
||||
|
||||
return (
|
||||
<Container className={classNames.root}>
|
||||
<div className={classNames.content}>
|
||||
<div className={classNames.breadcrumbs}>
|
||||
<Container className={clsx(classNames.root, styles[classNames.root])}>
|
||||
<div className={clsx(classNames.content, styles[classNames.content])}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.breadcrumbs,
|
||||
styles[classNames.breadcrumbs],
|
||||
)}
|
||||
>
|
||||
{breadcrumbs &&
|
||||
breadcrumbs.map(breadcrumb => (
|
||||
<Fragment key={breadcrumb.label}>
|
||||
@@ -56,10 +63,17 @@ export const HeaderPage = (props: HeaderPageProps) => {
|
||||
{title}
|
||||
</Text>
|
||||
</div>
|
||||
<div className={classNames.controls}>{customActions}</div>
|
||||
<div className={clsx(classNames.controls, styles[classNames.controls])}>
|
||||
{customActions}
|
||||
</div>
|
||||
</div>
|
||||
{tabs && (
|
||||
<div className={classNames.tabsWrapper}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.tabsWrapper,
|
||||
styles[classNames.tabsWrapper],
|
||||
)}
|
||||
>
|
||||
<Tabs>
|
||||
<TabList>
|
||||
{tabs.map(tab => (
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useIcons } from './context';
|
||||
import type { IconProps } from './types';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Icon.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Icon = (props: IconProps) => {
|
||||
@@ -36,7 +37,7 @@ export const Icon = (props: IconProps) => {
|
||||
|
||||
return (
|
||||
<BckstageIcon
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
style={{
|
||||
...(size ? { width: size, height: size } : {}),
|
||||
...style,
|
||||
|
||||
@@ -21,6 +21,8 @@ import { useStyles } from '../../hooks/useStyles';
|
||||
import type { LinkProps } from './types';
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
import stylesLink from './Link.module.css';
|
||||
import stylesText from '../Text/Text.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
@@ -50,7 +52,13 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
return (
|
||||
<AriaLink
|
||||
ref={ref}
|
||||
className={clsx(textClassNames.root, linkClassNames.root, className)}
|
||||
className={clsx(
|
||||
textClassNames.root,
|
||||
linkClassNames.root,
|
||||
stylesText[textClassNames.root],
|
||||
stylesLink[linkClassNames.root],
|
||||
className,
|
||||
)}
|
||||
data-truncate={truncate}
|
||||
href={href}
|
||||
{...textDataAttributes}
|
||||
@@ -64,7 +72,13 @@ export const Link = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<AriaLink
|
||||
ref={ref}
|
||||
className={clsx(textClassNames.root, linkClassNames.root, className)}
|
||||
className={clsx(
|
||||
textClassNames.root,
|
||||
linkClassNames.root,
|
||||
stylesText[textClassNames.root],
|
||||
stylesLink[linkClassNames.root],
|
||||
className,
|
||||
)}
|
||||
data-truncate={truncate}
|
||||
{...textDataAttributes}
|
||||
href={href}
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
.bui-MenuPopover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--bui-border);
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-surface-1);
|
||||
color: var(--bui-fg-primary);
|
||||
outline: none;
|
||||
transition: transform 200ms, opacity 200ms;
|
||||
/* Let React Aria handle height constraints naturally */
|
||||
min-height: 0;
|
||||
/* Remove overflow from popover since ScrollArea will handle it */
|
||||
overflow: hidden;
|
||||
|
||||
&[data-entering],
|
||||
&[data-exiting] {
|
||||
transform: var(--origin);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[data-placement='top'] {
|
||||
--origin: translateY(8px);
|
||||
}
|
||||
|
||||
&[data-placement='bottom'] {
|
||||
--origin: translateY(-8px);
|
||||
}
|
||||
|
||||
&[data-placement='right'] {
|
||||
--origin: translateX(-8px);
|
||||
}
|
||||
|
||||
&[data-placement='left'] {
|
||||
--origin: translateX(8px);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuContent {
|
||||
max-height: inherit;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
min-width: 150px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
padding-block: var(--bui-space-1);
|
||||
}
|
||||
|
||||
/* Ensure ScrollArea works properly within MenuRA popover */
|
||||
.bui-MenuPopover .bui-ScrollAreaRoot {
|
||||
/* Take full height of popover */
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
/* Ensure the root container has proper height constraints */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bui-MenuPopover .bui-ScrollAreaScrollbar {
|
||||
margin-inline: var(--bui-space-1_5);
|
||||
}
|
||||
|
||||
.bui-MenuItem {
|
||||
padding-inline: var(--bui-space-1);
|
||||
display: block;
|
||||
|
||||
&[data-focused] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-open] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-color='danger'] .bui-MenuItemWrapper {
|
||||
color: var(--bui-fg-danger);
|
||||
}
|
||||
|
||||
&[data-color='danger'][data-focused] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-danger);
|
||||
color: var(--bui-fg-danger);
|
||||
}
|
||||
|
||||
&[data-has-submenu] {
|
||||
& > .bui-MenuItemWrapper > .bui-MenuItemArrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 2rem;
|
||||
padding-inline: var(--bui-space-2);
|
||||
border-radius: var(--bui-radius-2);
|
||||
outline: none;
|
||||
cursor: default;
|
||||
color: var(--bui-fg-primary);
|
||||
font-size: var(--bui-font-size-3);
|
||||
gap: var(--bui-space-6);
|
||||
}
|
||||
|
||||
.bui-MenuItemListBox {
|
||||
padding-inline: var(--bui-space-1);
|
||||
display: block;
|
||||
|
||||
&:hover .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-selected] .bui-MenuItemListBoxCheck {
|
||||
& > svg {
|
||||
opacity: 1;
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemListBoxCheck {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
& > svg {
|
||||
opacity: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemArrow {
|
||||
display: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSection {
|
||||
&:first-child .bui-MenuSectionHeader {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSectionHeader {
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: var(--bui-space-3);
|
||||
padding-left: var(--bui-space-3);
|
||||
color: var(--bui-fg-primary);
|
||||
font-size: var(--bui-font-size-1);
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.05rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.bui-MenuSeparator {
|
||||
height: 1px;
|
||||
background: var(--bui-border);
|
||||
margin-inline: var(--bui-space-1_5);
|
||||
margin-block: var(--bui-space-1);
|
||||
}
|
||||
|
||||
.bui-MenuSearchField {
|
||||
position: relative;
|
||||
font-family: var(--bui-font-regular);
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
&[data-empty] {
|
||||
.bui-MenuSearchFieldClear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSearchFieldInput {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--bui-space-3);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
color: var(--bui-fg-primary);
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
cursor: inherit;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSearchFieldClear {
|
||||
position: absolute;
|
||||
right: var(--bui-space-2);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
color: var(--bui-fg-secondary);
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuEmptyState {
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-2);
|
||||
color: var(--bui-fg-secondary);
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
.bui-MenuPopover {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid var(--bui-border);
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-surface-1);
|
||||
color: var(--bui-fg-primary);
|
||||
outline: none;
|
||||
transition: transform 200ms, opacity 200ms;
|
||||
/* Let React Aria handle height constraints naturally */
|
||||
min-height: 0;
|
||||
/* Remove overflow from popover since ScrollArea will handle it */
|
||||
overflow: hidden;
|
||||
|
||||
&[data-entering],
|
||||
&[data-exiting] {
|
||||
transform: var(--origin);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&[data-placement='top'] {
|
||||
--origin: translateY(8px);
|
||||
}
|
||||
|
||||
&[data-placement='bottom'] {
|
||||
--origin: translateY(-8px);
|
||||
}
|
||||
|
||||
&[data-placement='right'] {
|
||||
--origin: translateX(-8px);
|
||||
}
|
||||
|
||||
&[data-placement='left'] {
|
||||
--origin: translateX(8px);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuContent {
|
||||
max-height: inherit;
|
||||
box-sizing: border-box;
|
||||
overflow: auto;
|
||||
min-width: 150px;
|
||||
box-sizing: border-box;
|
||||
outline: none;
|
||||
padding-block: var(--bui-space-1);
|
||||
}
|
||||
|
||||
/* Ensure ScrollArea works properly within MenuRA popover */
|
||||
.bui-MenuPopover .bui-ScrollAreaRoot {
|
||||
/* Take full height of popover */
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
/* Ensure the root container has proper height constraints */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bui-MenuPopover .bui-ScrollAreaScrollbar {
|
||||
margin-inline: var(--bui-space-1_5);
|
||||
}
|
||||
|
||||
.bui-MenuItem {
|
||||
padding-inline: var(--bui-space-1);
|
||||
display: block;
|
||||
|
||||
&[data-focused] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-open] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-color='danger'] .bui-MenuItemWrapper {
|
||||
color: var(--bui-fg-danger);
|
||||
}
|
||||
|
||||
&[data-color='danger'][data-focused] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-danger);
|
||||
color: var(--bui-fg-danger);
|
||||
}
|
||||
|
||||
&[data-has-submenu] {
|
||||
& > .bui-MenuItemWrapper > .bui-MenuItemArrow {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemWrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 2rem;
|
||||
padding-inline: var(--bui-space-2);
|
||||
border-radius: var(--bui-radius-2);
|
||||
outline: none;
|
||||
cursor: default;
|
||||
color: var(--bui-fg-primary);
|
||||
font-size: var(--bui-font-size-3);
|
||||
gap: var(--bui-space-6);
|
||||
}
|
||||
|
||||
.bui-MenuItemListBox {
|
||||
padding-inline: var(--bui-space-1);
|
||||
display: block;
|
||||
|
||||
&:hover .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-selected] .bui-MenuItemListBoxCheck {
|
||||
& > svg {
|
||||
opacity: 1;
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemListBoxCheck {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
& > svg {
|
||||
opacity: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemContent {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--bui-space-2);
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuItemArrow {
|
||||
display: none;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSection {
|
||||
&:first-child .bui-MenuSectionHeader {
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSectionHeader {
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: var(--bui-space-3);
|
||||
padding-left: var(--bui-space-3);
|
||||
color: var(--bui-fg-primary);
|
||||
font-size: var(--bui-font-size-1);
|
||||
font-weight: bold;
|
||||
letter-spacing: 0.05rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.bui-MenuSeparator {
|
||||
height: 1px;
|
||||
background: var(--bui-border);
|
||||
margin-inline: var(--bui-space-1_5);
|
||||
margin-block: var(--bui-space-1);
|
||||
}
|
||||
|
||||
.bui-MenuSearchField {
|
||||
position: relative;
|
||||
font-family: var(--bui-font-regular);
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
|
||||
&[data-empty] {
|
||||
.bui-MenuSearchFieldClear {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSearchFieldInput {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 var(--bui-space-3);
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
color: var(--bui-fg-primary);
|
||||
width: 100%;
|
||||
height: 2rem;
|
||||
cursor: inherit;
|
||||
outline: none;
|
||||
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuSearchFieldClear {
|
||||
position: absolute;
|
||||
right: var(--bui-space-2);
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
color: var(--bui-fg-secondary);
|
||||
transition: color 0.2s ease-in-out;
|
||||
|
||||
& > svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-MenuEmptyState {
|
||||
padding-inline: var(--bui-space-3);
|
||||
padding-block: var(--bui-space-2);
|
||||
color: var(--bui-fg-secondary);
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
}
|
||||
@@ -56,6 +56,8 @@ import {
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
import { useRef, useEffect, useContext } from 'react';
|
||||
import styles from './Menu.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
// The height will be used for virtualized menus. It should match the size set in CSS for each menu item.
|
||||
const rowHeight = 32;
|
||||
@@ -63,7 +65,11 @@ const rowHeight = 32;
|
||||
const MenuEmptyState = () => {
|
||||
const { classNames } = useStyles('Menu');
|
||||
|
||||
return <div className={classNames.emptyState}>No results found.</div>;
|
||||
return (
|
||||
<div className={clsx(classNames.emptyState, styles[classNames.emptyState])}>
|
||||
No results found.
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/** @public */
|
||||
@@ -118,7 +124,7 @@ export const Menu = (props: MenuProps<object>) => {
|
||||
|
||||
const menuContent = (
|
||||
<RAMenu
|
||||
className={classNames.content}
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
renderEmptyState={() => <MenuEmptyState />}
|
||||
style={{ width: newMaxWidth, maxHeight }}
|
||||
{...rest}
|
||||
@@ -128,7 +134,7 @@ export const Menu = (props: MenuProps<object>) => {
|
||||
return (
|
||||
<RAPopover
|
||||
ref={popoverRef}
|
||||
className={classNames.popover}
|
||||
className={clsx(classNames.popover, styles[classNames.popover])}
|
||||
placement={placement}
|
||||
isNonModal={true}
|
||||
isKeyboardDismissDisabled={false}
|
||||
@@ -166,7 +172,7 @@ export const MenuListBox = (props: MenuListBoxProps<object>) => {
|
||||
|
||||
const listBoxContent = (
|
||||
<RAListBox
|
||||
className={classNames.content}
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
selectionMode={selectionMode}
|
||||
style={{ width: newMaxWidth, maxHeight }}
|
||||
{...rest}
|
||||
@@ -174,7 +180,10 @@ export const MenuListBox = (props: MenuListBoxProps<object>) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<RAPopover className={classNames.popover} placement={placement}>
|
||||
<RAPopover
|
||||
className={clsx(classNames.popover, styles[classNames.popover])}
|
||||
placement={placement}
|
||||
>
|
||||
{virtualized ? (
|
||||
<Virtualizer
|
||||
layout={ListLayout}
|
||||
@@ -207,7 +216,7 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps<object>) => {
|
||||
|
||||
const menuContent = (
|
||||
<RAMenu
|
||||
className={classNames.content}
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
renderEmptyState={() => <MenuEmptyState />}
|
||||
style={{ width: newMaxWidth, maxHeight }}
|
||||
{...rest}
|
||||
@@ -215,16 +224,32 @@ export const MenuAutocomplete = (props: MenuAutocompleteProps<object>) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<RAPopover className={classNames.popover} placement={placement}>
|
||||
<RAPopover
|
||||
className={clsx(classNames.popover, styles[classNames.popover])}
|
||||
placement={placement}
|
||||
>
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField className={classNames.searchField}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
)}
|
||||
>
|
||||
<RAInput
|
||||
className={classNames.searchFieldInput}
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
aria-label="Search"
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton className={classNames.searchFieldClear}>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
@@ -264,7 +289,7 @@ export const MenuAutocompleteListbox = (
|
||||
|
||||
const listBoxContent = (
|
||||
<RAListBox
|
||||
className={classNames.content}
|
||||
className={clsx(classNames.content, styles[classNames.content])}
|
||||
renderEmptyState={() => <MenuEmptyState />}
|
||||
selectionMode={selectionMode}
|
||||
style={{ width: newMaxWidth, maxHeight }}
|
||||
@@ -273,15 +298,31 @@ export const MenuAutocompleteListbox = (
|
||||
);
|
||||
|
||||
return (
|
||||
<RAPopover className={classNames.popover} placement={placement}>
|
||||
<RAPopover
|
||||
className={clsx(classNames.popover, styles[classNames.popover])}
|
||||
placement={placement}
|
||||
>
|
||||
<RAAutocomplete filter={contains}>
|
||||
<RASearchField className={classNames.searchField}>
|
||||
<RASearchField
|
||||
className={clsx(
|
||||
classNames.searchField,
|
||||
styles[classNames.searchField],
|
||||
)}
|
||||
>
|
||||
<RAInput
|
||||
className={classNames.searchFieldInput}
|
||||
className={clsx(
|
||||
classNames.searchFieldInput,
|
||||
styles[classNames.searchFieldInput],
|
||||
)}
|
||||
aria-label="Search"
|
||||
placeholder={props.placeholder || 'Search...'}
|
||||
/>
|
||||
<RAButton className={classNames.searchFieldClear}>
|
||||
<RAButton
|
||||
className={clsx(
|
||||
classNames.searchFieldClear,
|
||||
styles[classNames.searchFieldClear],
|
||||
)}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
</RAButton>
|
||||
</RASearchField>
|
||||
@@ -313,18 +354,30 @@ export const MenuItem = (props: MenuItemProps) => {
|
||||
if (isLink && isExternal) {
|
||||
return (
|
||||
<RAMenuItem
|
||||
className={classNames.item}
|
||||
className={clsx(classNames.item, styles[classNames.item])}
|
||||
data-color={color}
|
||||
textValue={typeof children === 'string' ? children : undefined}
|
||||
onAction={() => window.open(href, '_blank', 'noopener,noreferrer')}
|
||||
{...rest}
|
||||
>
|
||||
<div className={classNames.itemWrapper}>
|
||||
<div className={classNames.itemContent}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemWrapper,
|
||||
styles[classNames.itemWrapper],
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemContent,
|
||||
styles[classNames.itemContent],
|
||||
)}
|
||||
>
|
||||
{iconStart}
|
||||
{children}
|
||||
</div>
|
||||
<div className={classNames.itemArrow}>
|
||||
<div
|
||||
className={clsx(classNames.itemArrow, styles[classNames.itemArrow])}
|
||||
>
|
||||
<RiArrowRightSLine />
|
||||
</div>
|
||||
</div>
|
||||
@@ -334,18 +387,27 @@ export const MenuItem = (props: MenuItemProps) => {
|
||||
|
||||
return (
|
||||
<RAMenuItem
|
||||
className={classNames.item}
|
||||
className={clsx(classNames.item, styles[classNames.item])}
|
||||
data-color={color}
|
||||
href={href}
|
||||
textValue={typeof children === 'string' ? children : undefined}
|
||||
{...rest}
|
||||
>
|
||||
<div className={classNames.itemWrapper}>
|
||||
<div className={classNames.itemContent}>
|
||||
<div
|
||||
className={clsx(classNames.itemWrapper, styles[classNames.itemWrapper])}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemContent,
|
||||
styles[classNames.itemContent],
|
||||
)}
|
||||
>
|
||||
{iconStart}
|
||||
{children}
|
||||
</div>
|
||||
<div className={classNames.itemArrow}>
|
||||
<div
|
||||
className={clsx(classNames.itemArrow, styles[classNames.itemArrow])}
|
||||
>
|
||||
<RiArrowRightSLine />
|
||||
</div>
|
||||
</div>
|
||||
@@ -363,12 +425,24 @@ export const MenuListBoxItem = (props: MenuListBoxItemProps) => {
|
||||
textValue={
|
||||
typeof props.children === 'string' ? props.children : undefined
|
||||
}
|
||||
className={classNames.itemListBox}
|
||||
className={clsx(classNames.itemListBox, styles[classNames.itemListBox])}
|
||||
{...rest}
|
||||
>
|
||||
<div className={classNames.itemWrapper}>
|
||||
<div className={classNames.itemContent}>
|
||||
<div className={classNames.itemListBoxCheck}>
|
||||
<div
|
||||
className={clsx(classNames.itemWrapper, styles[classNames.itemWrapper])}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemContent,
|
||||
styles[classNames.itemContent],
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemListBoxCheck,
|
||||
styles[classNames.itemListBoxCheck],
|
||||
)}
|
||||
>
|
||||
<RiCheckLine />
|
||||
</div>
|
||||
{children}
|
||||
@@ -383,8 +457,16 @@ export const MenuSection = (props: MenuSectionProps<object>) => {
|
||||
const { classNames } = useStyles('Menu');
|
||||
|
||||
return (
|
||||
<RAMenuSection className={classNames.section} {...props}>
|
||||
<RAMenuHeader className={classNames.sectionHeader}>
|
||||
<RAMenuSection
|
||||
className={clsx(classNames.section, styles[classNames.section])}
|
||||
{...props}
|
||||
>
|
||||
<RAMenuHeader
|
||||
className={clsx(
|
||||
classNames.sectionHeader,
|
||||
styles[classNames.sectionHeader],
|
||||
)}
|
||||
>
|
||||
{props.title}
|
||||
</RAMenuHeader>
|
||||
{props.children}
|
||||
@@ -396,5 +478,10 @@ export const MenuSection = (props: MenuSectionProps<object>) => {
|
||||
export const MenuSeparator = (props: MenuSeparatorProps) => {
|
||||
const { classNames } = useStyles('Menu');
|
||||
|
||||
return <RAMenuSeparator className={classNames.separator} {...props} />;
|
||||
return (
|
||||
<RAMenuSeparator
|
||||
className={clsx(classNames.separator, styles[classNames.separator])}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -27,6 +27,8 @@ import { FieldError } from '../FieldError';
|
||||
import type { PasswordFieldProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { RiEyeLine, RiEyeOffLine } from '@remixicon/react';
|
||||
import stylesPasswordField from './PasswordField.module.css';
|
||||
import stylesTextField from '../TextField/TextField.module.css';
|
||||
|
||||
/** @public */
|
||||
export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
|
||||
@@ -76,7 +78,11 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
|
||||
|
||||
return (
|
||||
<AriaTextField
|
||||
className={clsx(passwordFieldClassNames.root, className)}
|
||||
className={clsx(
|
||||
passwordFieldClassNames.root,
|
||||
stylesPasswordField[passwordFieldClassNames.root],
|
||||
className,
|
||||
)}
|
||||
{...dataAttributes}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
@@ -91,19 +97,30 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={textFieldClassNames.inputWrapper}
|
||||
className={clsx(
|
||||
textFieldClassNames.inputWrapper,
|
||||
stylesTextField[textFieldClassNames.inputWrapper],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon && (
|
||||
<div
|
||||
className={textFieldClassNames.inputIcon}
|
||||
className={clsx(
|
||||
textFieldClassNames.inputIcon,
|
||||
stylesTextField[textFieldClassNames.inputIcon],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
)}
|
||||
<div className={textFieldClassNames.inputAction}>
|
||||
<div
|
||||
className={clsx(
|
||||
textFieldClassNames.inputAction,
|
||||
stylesTextField[textFieldClassNames.inputAction],
|
||||
)}
|
||||
>
|
||||
<RAButton
|
||||
data-size={dataAttributes['data-size']}
|
||||
data-variant={'tertiary'}
|
||||
@@ -111,13 +128,19 @@ export const PasswordField = forwardRef<HTMLDivElement, PasswordFieldProps>(
|
||||
aria-controls={isVisible ? 'text' : 'password'}
|
||||
aria-expanded={isVisible}
|
||||
onPress={() => setIsVisible(v => !v)}
|
||||
className={passwordFieldClassNames.inputVisibility}
|
||||
className={clsx(
|
||||
passwordFieldClassNames.inputVisibility,
|
||||
stylesPasswordField[passwordFieldClassNames.inputVisibility],
|
||||
)}
|
||||
>
|
||||
{isVisible ? <RiEyeLine /> : <RiEyeOffLine />}
|
||||
</RAButton>
|
||||
</div>
|
||||
<Input
|
||||
className={textFieldClassNames.input}
|
||||
className={clsx(
|
||||
textFieldClassNames.input,
|
||||
stylesTextField[textFieldClassNames.input],
|
||||
)}
|
||||
{...(icon && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
type={isVisible ? 'text' : 'password'}
|
||||
|
||||
@@ -23,6 +23,7 @@ import clsx from 'clsx';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './RadioGroup.module.css';
|
||||
|
||||
import type { RadioGroupProps, RadioProps } from './types';
|
||||
|
||||
@@ -57,7 +58,7 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
|
||||
|
||||
return (
|
||||
<AriaRadioGroup
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
{...rest}
|
||||
@@ -68,7 +69,9 @@ export const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(
|
||||
secondaryLabel={secondaryLabelText}
|
||||
description={description}
|
||||
/>
|
||||
<div className={classNames.content}>{children}</div>
|
||||
<div className={clsx(classNames.content, styles[classNames.content])}>
|
||||
{children}
|
||||
</div>
|
||||
<FieldError />
|
||||
</AriaRadioGroup>
|
||||
);
|
||||
@@ -85,7 +88,7 @@ export const Radio = forwardRef<HTMLLabelElement, RadioProps>((props, ref) => {
|
||||
|
||||
return (
|
||||
<AriaRadio
|
||||
className={clsx(classNames.radio, className)}
|
||||
className={clsx(classNames.radio, styles[classNames.radio], className)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
/>
|
||||
|
||||
@@ -18,6 +18,7 @@ import { forwardRef } from 'react';
|
||||
import { ScrollArea as ScrollAreaPrimitive } from '@base-ui-components/react/scroll-area';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './ScrollArea.module.css';
|
||||
|
||||
const ScrollAreaRoot = forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
@@ -28,7 +29,7 @@ const ScrollAreaRoot = forwardRef<
|
||||
return (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -44,7 +45,11 @@ const ScrollAreaViewport = forwardRef<
|
||||
return (
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
ref={ref}
|
||||
className={clsx(classNames.viewport, className)}
|
||||
className={clsx(
|
||||
classNames.viewport,
|
||||
styles[classNames.viewport],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -60,7 +65,11 @@ const ScrollAreaScrollbar = forwardRef<
|
||||
return (
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
ref={ref}
|
||||
className={clsx(classNames.scrollbar, className)}
|
||||
className={clsx(
|
||||
classNames.scrollbar,
|
||||
styles[classNames.scrollbar],
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -76,7 +85,7 @@ const ScrollAreaThumb = forwardRef<
|
||||
return (
|
||||
<ScrollAreaPrimitive.Thumb
|
||||
ref={ref}
|
||||
className={clsx(classNames.thumb, className)}
|
||||
className={clsx(classNames.thumb, styles[classNames.thumb], className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -25,6 +25,8 @@ import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
import { RiSearch2Line, RiCloseCircleLine } from '@remixicon/react';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import stylesSearchField from './SearchField.module.css';
|
||||
import stylesTextField from '../TextField/TextField.module.css';
|
||||
|
||||
import type { SearchFieldProps } from './types';
|
||||
|
||||
@@ -102,6 +104,8 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
className={clsx(
|
||||
textFieldClassNames.root,
|
||||
searchFieldClassNames.root,
|
||||
stylesTextField[textFieldClassNames.root],
|
||||
stylesSearchField[searchFieldClassNames.root],
|
||||
className,
|
||||
)}
|
||||
{...dataAttributes}
|
||||
@@ -120,12 +124,18 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={textFieldClassNames.inputWrapper}
|
||||
className={clsx(
|
||||
textFieldClassNames.inputWrapper,
|
||||
stylesTextField[textFieldClassNames.inputWrapper],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon !== false && (
|
||||
<div
|
||||
className={textFieldClassNames.inputIcon}
|
||||
className={clsx(
|
||||
textFieldClassNames.inputIcon,
|
||||
stylesTextField[textFieldClassNames.inputIcon],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -133,12 +143,18 @@ export const SearchField = forwardRef<HTMLDivElement, SearchFieldProps>(
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
className={textFieldClassNames.input}
|
||||
className={clsx(
|
||||
textFieldClassNames.input,
|
||||
stylesTextField[textFieldClassNames.input],
|
||||
)}
|
||||
{...(icon !== false && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
<Button
|
||||
className={searchFieldClassNames.clear}
|
||||
className={clsx(
|
||||
searchFieldClassNames.clear,
|
||||
stylesSearchField[searchFieldClassNames.clear],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
<RiCloseCircleLine />
|
||||
|
||||
@@ -30,6 +30,8 @@ import { useStyles } from '../../hooks/useStyles';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { Icon } from '../Icon';
|
||||
import { FieldError } from '../FieldError';
|
||||
import styles from './Select.module.css';
|
||||
import stylesPopover from '../Popover/Popover.module.css';
|
||||
|
||||
/** @public */
|
||||
export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
@@ -67,7 +69,7 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
|
||||
return (
|
||||
<AriaSelect
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...dataAttributes}
|
||||
ref={ref}
|
||||
aria-label={ariaLabel}
|
||||
@@ -80,26 +82,44 @@ export const Select = forwardRef<HTMLDivElement, SelectProps>((props, ref) => {
|
||||
description={description}
|
||||
/>
|
||||
<Button
|
||||
className={classNames.trigger}
|
||||
className={clsx(classNames.trigger, styles[classNames.trigger])}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon}
|
||||
<SelectValue className={classNames.value} />
|
||||
<SelectValue
|
||||
className={clsx(classNames.value, styles[classNames.value])}
|
||||
/>
|
||||
<Icon aria-hidden="true" name="chevron-down" />
|
||||
</Button>
|
||||
<FieldError />
|
||||
<Popover className={popoverClassNames.root}>
|
||||
<ListBox className={classNames.list}>
|
||||
<Popover
|
||||
className={clsx(
|
||||
popoverClassNames.root,
|
||||
stylesPopover[popoverClassNames.root],
|
||||
)}
|
||||
>
|
||||
<ListBox className={clsx(classNames.list, styles[classNames.list])}>
|
||||
{options?.map(option => (
|
||||
<ListBoxItem
|
||||
key={option.value}
|
||||
id={option.value}
|
||||
className={classNames.item}
|
||||
className={clsx(classNames.item, styles[classNames.item])}
|
||||
>
|
||||
<div className={classNames.itemIndicator}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.itemIndicator,
|
||||
styles[classNames.itemIndicator],
|
||||
)}
|
||||
>
|
||||
<Icon name="check" />
|
||||
</div>
|
||||
<Text slot="label" className={classNames.itemLabel}>
|
||||
<Text
|
||||
slot="label"
|
||||
className={clsx(
|
||||
classNames.itemLabel,
|
||||
styles[classNames.itemLabel],
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</Text>
|
||||
</ListBoxItem>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { SkeletonProps } from './types';
|
||||
import styles from './Skeleton.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const Skeleton = (props: SkeletonProps) => {
|
||||
@@ -24,7 +26,7 @@ export const Skeleton = (props: SkeletonProps) => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames.root}
|
||||
className={clsx(classNames.root, styles[classNames.root])}
|
||||
data-rounded={rounded}
|
||||
style={{
|
||||
width,
|
||||
|
||||
@@ -18,6 +18,8 @@ import { forwardRef } from 'react';
|
||||
import { Switch as AriaSwitch } from 'react-aria-components';
|
||||
import type { SwitchProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Switch.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
|
||||
@@ -25,8 +27,14 @@ export const Switch = forwardRef<HTMLLabelElement, SwitchProps>(
|
||||
const { classNames } = useStyles('Switch');
|
||||
|
||||
return (
|
||||
<AriaSwitch className={classNames.root} ref={ref} {...props}>
|
||||
<div className={classNames.indicator} />
|
||||
<AriaSwitch
|
||||
className={clsx(classNames.root, styles[classNames.root])}
|
||||
ref={ref}
|
||||
{...props}
|
||||
>
|
||||
<div
|
||||
className={clsx(classNames.indicator, styles[classNames.indicator])}
|
||||
/>
|
||||
{label}
|
||||
</AriaSwitch>
|
||||
);
|
||||
|
||||
@@ -20,6 +20,7 @@ import { Link } from '../../Link';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
import type { CellProps } from '../types';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import styles from '../Table.module.css';
|
||||
|
||||
/** @public */
|
||||
const Cell = (props: CellProps) => {
|
||||
@@ -36,12 +37,29 @@ const Cell = (props: CellProps) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
{leadingIcon && (
|
||||
<div className={classNames.cellIcon}>{leadingIcon}</div>
|
||||
<ReactAriaCell
|
||||
className={clsx(classNames.cell, styles[classNames.cell], className)}
|
||||
{...rest}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.cellContentWrapper,
|
||||
styles[classNames.cellContentWrapper],
|
||||
)}
|
||||
<div className={classNames.cellContent}>
|
||||
>
|
||||
{leadingIcon && (
|
||||
<div
|
||||
className={clsx(classNames.cellIcon, styles[classNames.cellIcon])}
|
||||
>
|
||||
{leadingIcon}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.cellContent,
|
||||
styles[classNames.cellContent],
|
||||
)}
|
||||
>
|
||||
{href ? (
|
||||
<Link href={href} variant="body-medium" color={color}>
|
||||
{title}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { Link } from '../../Link/Link';
|
||||
import { Avatar } from '@base-ui-components/react/avatar';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import { Cell as ReactAriaCell } from 'react-aria-components';
|
||||
import styles from '../Table.module.css';
|
||||
|
||||
/** @public */
|
||||
export const CellProfile = (props: CellProfileProps) => {
|
||||
@@ -36,18 +37,39 @@ export const CellProfile = (props: CellProfileProps) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaCell className={clsx(classNames.cell, className)} {...rest}>
|
||||
<div className={classNames.cellContentWrapper}>
|
||||
<div className={classNames.cellIcon}>
|
||||
<ReactAriaCell
|
||||
className={clsx(classNames.cell, styles[classNames.cell], className)}
|
||||
{...rest}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.cellContentWrapper,
|
||||
styles[classNames.cellContentWrapper],
|
||||
)}
|
||||
>
|
||||
<div className={clsx(classNames.cellIcon, styles[classNames.cellIcon])}>
|
||||
{src && (
|
||||
<Avatar.Root className={classNames.cellProfileAvatar}>
|
||||
<Avatar.Root
|
||||
className={clsx(
|
||||
classNames.cellProfileAvatar,
|
||||
styles[classNames.cellProfileAvatar],
|
||||
)}
|
||||
>
|
||||
<Avatar.Image
|
||||
src={src}
|
||||
width="20"
|
||||
height="20"
|
||||
className={classNames.cellProfileAvatarImage}
|
||||
className={clsx(
|
||||
classNames.cellProfileAvatarImage,
|
||||
styles[classNames.cellProfileAvatarImage],
|
||||
)}
|
||||
/>
|
||||
<Avatar.Fallback className={classNames.cellProfileAvatarFallback}>
|
||||
<Avatar.Fallback
|
||||
className={clsx(
|
||||
classNames.cellProfileAvatarFallback,
|
||||
styles[classNames.cellProfileAvatarFallback],
|
||||
)}
|
||||
>
|
||||
{(name || '')
|
||||
.split(' ')
|
||||
.map(word => word[0])
|
||||
@@ -58,7 +80,12 @@ export const CellProfile = (props: CellProfileProps) => {
|
||||
</Avatar.Root>
|
||||
)}
|
||||
</div>
|
||||
<div className={classNames.cellContent}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.cellContent,
|
||||
styles[classNames.cellContent],
|
||||
)}
|
||||
>
|
||||
{name && href ? (
|
||||
<Link href={href}>{name}</Link>
|
||||
) : (
|
||||
|
||||
@@ -20,6 +20,8 @@ import {
|
||||
} from 'react-aria-components';
|
||||
import { Icon } from '../../Icon';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const Column = (
|
||||
@@ -28,12 +30,26 @@ export const Column = (
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaColumn className={classNames.head} {...props}>
|
||||
<ReactAriaColumn
|
||||
className={clsx(classNames.head, styles[classNames.head])}
|
||||
{...props}
|
||||
>
|
||||
{({ allowsSorting, sortDirection }) => (
|
||||
<div className={classNames.headContent}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.headContent,
|
||||
styles[classNames.headContent],
|
||||
)}
|
||||
>
|
||||
{props.children}
|
||||
{allowsSorting && (
|
||||
<span aria-hidden="true" className={classNames.headSortButton}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className={clsx(
|
||||
classNames.headSortButton,
|
||||
styles[classNames.headSortButton],
|
||||
)}
|
||||
>
|
||||
{sortDirection === 'ascending' ? (
|
||||
<Icon name="arrow-up" size={16} />
|
||||
) : (
|
||||
|
||||
@@ -27,6 +27,8 @@ import { useStyles } from '../../../hooks/useStyles';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useHref } from 'react-router-dom';
|
||||
import { isExternalLink } from '../../../utils/isExternalLink';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export function Row<T extends object>({
|
||||
@@ -55,7 +57,11 @@ export function Row<T extends object>({
|
||||
|
||||
if (isExternal) {
|
||||
return (
|
||||
<ReactAriaRow id={id} className={classNames.row} {...otherProps}>
|
||||
<ReactAriaRow
|
||||
id={id}
|
||||
className={clsx(classNames.row, styles[classNames.row])}
|
||||
{...otherProps}
|
||||
>
|
||||
{content}
|
||||
</ReactAriaRow>
|
||||
);
|
||||
@@ -65,7 +71,7 @@ export function Row<T extends object>({
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<ReactAriaRow
|
||||
id={id}
|
||||
className={classNames.row}
|
||||
className={clsx(classNames.row, styles[classNames.row])}
|
||||
data-react-aria-pressable="true"
|
||||
{...otherProps}
|
||||
>
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
Table as ReactAriaTable,
|
||||
type TableProps,
|
||||
} from 'react-aria-components';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const Table = (props: TableProps) => {
|
||||
@@ -26,7 +28,7 @@ export const Table = (props: TableProps) => {
|
||||
|
||||
return (
|
||||
<ReactAriaTable
|
||||
className={classNames.table}
|
||||
className={clsx(classNames.table, styles[classNames.table])}
|
||||
aria-label="Data table"
|
||||
{...props}
|
||||
/>
|
||||
|
||||
@@ -19,10 +19,17 @@ import {
|
||||
type TableBodyProps,
|
||||
} from 'react-aria-components';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const TableBody = <T extends object>(props: TableBodyProps<T>) => {
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return <ReactAriaTableBody className={classNames.body} {...props} />;
|
||||
return (
|
||||
<ReactAriaTableBody
|
||||
className={clsx(classNames.body, styles[classNames.body])}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,6 +22,8 @@ import {
|
||||
import { Collection, useTableOptions } from 'react-aria-components';
|
||||
import { Column } from './Column';
|
||||
import { useStyles } from '../../../hooks/useStyles';
|
||||
import styles from '../Table.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/** @public */
|
||||
export const TableHeader = <T extends object>({
|
||||
@@ -33,7 +35,9 @@ export const TableHeader = <T extends object>({
|
||||
const { classNames } = useStyles('Table');
|
||||
|
||||
return (
|
||||
<ReactAriaTableHeader className={classNames.header}>
|
||||
<ReactAriaTableHeader
|
||||
className={clsx(classNames.header, styles[classNames.header])}
|
||||
>
|
||||
{/* Add extra columns for drag and drop and selection. */}
|
||||
{allowsDragging && <Column />}
|
||||
{selectionBehavior === 'toggle' && (
|
||||
|
||||
+4
-4
@@ -15,27 +15,27 @@
|
||||
*/
|
||||
|
||||
@layer components {
|
||||
.bui-DataTablePagination {
|
||||
.bui-TablePagination {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-top: var(--bui-space-5);
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--left {
|
||||
.bui-TablePaginationLeft {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--right {
|
||||
.bui-TablePaginationRight {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--bui-space-2);
|
||||
}
|
||||
|
||||
.bui-DataTablePagination--select {
|
||||
.bui-TablePaginationSelect {
|
||||
min-width: 10.5rem;
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,8 @@
|
||||
import clsx from 'clsx';
|
||||
import { Text, ButtonIcon, Select, Icon } from '../..';
|
||||
import type { TablePaginationProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './TablePagination.module.css';
|
||||
|
||||
/**
|
||||
* Pagination controls for Table components with page navigation and size selection.
|
||||
@@ -38,6 +40,8 @@ export function TablePagination(props: TablePaginationProps) {
|
||||
...rest
|
||||
} = props;
|
||||
|
||||
const { classNames } = useStyles('TablePagination');
|
||||
|
||||
const currentOffset = offset ?? 0;
|
||||
const currentPageSize = pageSize ?? 10;
|
||||
|
||||
@@ -65,8 +69,11 @@ export function TablePagination(props: TablePaginationProps) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={clsx('bui-DataTablePagination', className)} {...rest}>
|
||||
<div className="bui-DataTablePagination--left">
|
||||
<div
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...rest}
|
||||
>
|
||||
<div className={clsx(classNames.left, styles[classNames.left])}>
|
||||
{showPageSizeOptions && (
|
||||
<Select
|
||||
name="pageSize"
|
||||
@@ -86,11 +93,11 @@ export function TablePagination(props: TablePaginationProps) {
|
||||
setPageSize?.(newPageSize);
|
||||
onPageSizeChange?.(newPageSize);
|
||||
}}
|
||||
className="bui-DataTablePagination--select"
|
||||
className={clsx(classNames.select, styles[classNames.select])}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="bui-DataTablePagination--right">
|
||||
<div className={clsx(classNames.right, styles[classNames.right])}>
|
||||
<Text
|
||||
as="p"
|
||||
variant="body-medium"
|
||||
|
||||
@@ -41,8 +41,9 @@ import {
|
||||
RouterProvider,
|
||||
TabProps as AriaTabProps,
|
||||
} from 'react-aria-components';
|
||||
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Tabs.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const TabsContext = createContext<TabsContextValue | undefined>(undefined);
|
||||
|
||||
@@ -148,7 +149,7 @@ export const Tabs = (props: TabsProps) => {
|
||||
<TabsContext.Provider value={contextValue}>
|
||||
<RouterProvider navigate={navigate} useHref={useHref}>
|
||||
<AriaTabs
|
||||
className={classNames.tabs}
|
||||
className={clsx(classNames.tabs, styles[classNames.tabs])}
|
||||
keyboardActivation="manual"
|
||||
selectedKey={computedSelectedKey}
|
||||
ref={tabsRef}
|
||||
@@ -188,9 +189,14 @@ export const TabList = (props: TabListProps) => {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classNames.tabListWrapper}>
|
||||
<div
|
||||
className={clsx(
|
||||
classNames.tabListWrapper,
|
||||
styles[classNames.tabListWrapper],
|
||||
)}
|
||||
>
|
||||
<AriaTabList
|
||||
className={classNames.tabList}
|
||||
className={clsx(classNames.tabList, styles[classNames.tabList])}
|
||||
aria-label="Toolbar tabs"
|
||||
{...rest}
|
||||
>
|
||||
@@ -219,7 +225,7 @@ export const Tab = (props: TabProps) => {
|
||||
return (
|
||||
<AriaTab
|
||||
id={id}
|
||||
className={classNames.tab}
|
||||
className={clsx(classNames.tab, styles[classNames.tab])}
|
||||
ref={el => setTabRef(id as string, el as HTMLDivElement)}
|
||||
href={href}
|
||||
{...rest}
|
||||
@@ -239,7 +245,10 @@ export const TabPanel = (props: TabPanelProps) => {
|
||||
const { classNames } = useStyles('Tabs');
|
||||
|
||||
return (
|
||||
<AriaTabPanel className={classNames.panel} {...rest}>
|
||||
<AriaTabPanel
|
||||
className={clsx(classNames.panel, styles[classNames.panel])}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
</AriaTabPanel>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,8 @@ import { TabListStateContext } from 'react-aria-components';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { useContext, useEffect, useCallback, useRef } from 'react';
|
||||
import type { TabsIndicatorsProps } from './types';
|
||||
import styles from './Tabs.module.css';
|
||||
import clsx from 'clsx';
|
||||
|
||||
/**
|
||||
* A component that renders the indicators for the toolbar.
|
||||
@@ -184,8 +186,12 @@ export const TabsIndicators = (props: TabsIndicatorsProps) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames.tabActive} />
|
||||
<div className={classNames.tabHovered} />
|
||||
<div
|
||||
className={clsx(classNames.tabActive, styles[classNames.tabActive])}
|
||||
/>
|
||||
<div
|
||||
className={clsx(classNames.tabHovered, styles[classNames.tabHovered])}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { isExternalLink } from '../../utils/isExternalLink';
|
||||
import { useNavigate, useHref } from 'react-router-dom';
|
||||
import styles from './TagGroup.module.css';
|
||||
|
||||
/**
|
||||
* A component that renders a list of tags.
|
||||
@@ -42,9 +43,12 @@ export const TagGroup = <T extends object>({
|
||||
}: TagGroupProps<T>) => {
|
||||
const { classNames } = useStyles('TagGroup');
|
||||
return (
|
||||
<ReactAriaTagGroup className={classNames.group} {...props}>
|
||||
<ReactAriaTagGroup
|
||||
className={clsx(classNames.group, styles[classNames.group])}
|
||||
{...props}
|
||||
>
|
||||
<ReactAriaTagList
|
||||
className={classNames.list}
|
||||
className={clsx(classNames.list, styles[classNames.list])}
|
||||
items={items}
|
||||
renderEmptyState={renderEmptyState}
|
||||
>
|
||||
@@ -70,18 +74,27 @@ export const Tag = (props: TagProps) => {
|
||||
const content = (
|
||||
<ReactAriaTag
|
||||
textValue={textValue}
|
||||
className={clsx(classNames.tag, className)}
|
||||
className={clsx(classNames.tag, styles[classNames.tag], className)}
|
||||
data-size={size}
|
||||
href={href}
|
||||
{...rest}
|
||||
>
|
||||
{({ allowsRemoving }) => (
|
||||
<>
|
||||
{icon && <span className={classNames.tagIcon}>{icon}</span>}
|
||||
{icon && (
|
||||
<span
|
||||
className={clsx(classNames.tagIcon, styles[classNames.tagIcon])}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
{children as ReactNode}
|
||||
{allowsRemoving && (
|
||||
<ReactAriaButton
|
||||
className={classNames.tagRemoveButton}
|
||||
className={clsx(
|
||||
classNames.tagRemoveButton,
|
||||
styles[classNames.tagRemoveButton],
|
||||
)}
|
||||
slot="remove"
|
||||
>
|
||||
<RiCloseCircleLine size={16} />
|
||||
|
||||
@@ -19,6 +19,7 @@ import clsx from 'clsx';
|
||||
import type { ElementType } from 'react';
|
||||
import type { TextProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Text.module.css';
|
||||
|
||||
function TextComponent<T extends ElementType = 'span'>(
|
||||
{
|
||||
@@ -44,7 +45,7 @@ function TextComponent<T extends ElementType = 'span'>(
|
||||
return (
|
||||
<Component
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
data-truncate={truncate}
|
||||
data-as={as}
|
||||
{...dataAttributes}
|
||||
|
||||
@@ -19,9 +19,9 @@ import { Input, TextField as AriaTextField } from 'react-aria-components';
|
||||
import clsx from 'clsx';
|
||||
import { FieldLabel } from '../FieldLabel';
|
||||
import { FieldError } from '../FieldError';
|
||||
|
||||
import type { TextFieldProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './TextField.module.css';
|
||||
|
||||
/** @public */
|
||||
export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
@@ -64,7 +64,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
|
||||
return (
|
||||
<AriaTextField
|
||||
className={clsx(classNames.root, className)}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...dataAttributes}
|
||||
aria-label={ariaLabel}
|
||||
aria-labelledby={ariaLabelledBy}
|
||||
@@ -78,12 +78,18 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
description={description}
|
||||
/>
|
||||
<div
|
||||
className={classNames.inputWrapper}
|
||||
className={clsx(
|
||||
classNames.inputWrapper,
|
||||
styles[classNames.inputWrapper],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
>
|
||||
{icon && (
|
||||
<div
|
||||
className={classNames.inputIcon}
|
||||
className={clsx(
|
||||
classNames.inputIcon,
|
||||
styles[classNames.inputIcon],
|
||||
)}
|
||||
data-size={dataAttributes['data-size']}
|
||||
aria-hidden="true"
|
||||
>
|
||||
@@ -91,7 +97,7 @@ export const TextField = forwardRef<HTMLDivElement, TextFieldProps>(
|
||||
</div>
|
||||
)}
|
||||
<Input
|
||||
className={classNames.input}
|
||||
className={clsx(classNames.input, styles[classNames.input])}
|
||||
{...(icon && { 'data-icon': true })}
|
||||
placeholder={placeholder}
|
||||
/>
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
import clsx from 'clsx';
|
||||
import { TooltipProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import styles from './Tooltip.module.css';
|
||||
|
||||
/** @public */
|
||||
export const TooltipTrigger = (props: TooltipTriggerComponentProps) => {
|
||||
@@ -39,11 +40,17 @@ export const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(
|
||||
|
||||
return (
|
||||
<AriaTooltip
|
||||
className={clsx(classNames.tooltip, className)}
|
||||
className={clsx(
|
||||
classNames.tooltip,
|
||||
styles[classNames.tooltip],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
>
|
||||
<OverlayArrow className={classNames.arrow}>
|
||||
<OverlayArrow
|
||||
className={clsx(classNames.arrow, styles[classNames.arrow])}
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none">
|
||||
<path d="M10.3356 7.39793L15.1924 3.02682C15.9269 2.36577 16.8801 2 17.8683 2H20V7.94781e-07L1.74846e-07 -9.53674e-07L0 2L1.4651 2C2.4532 2 3.4064 2.36577 4.1409 3.02682L8.9977 7.39793C9.378 7.7402 9.9553 7.74021 10.3356 7.39793Z" />
|
||||
<path d="M11.0046 8.14124C10.2439 8.82575 9.08939 8.82578 8.32869 8.14122L3.47189 3.77011C2.92109 3.27432 2.20619 2.99999 1.46509 2.99999L4.10999 3L8.99769 7.39793C9.37799 7.7402 9.95529 7.7402 10.3356 7.39793L15.2226 3L17.8683 2.99999C17.1271 2.99999 16.4122 3.27432 15.8614 3.77011L11.0046 8.14124Z" />
|
||||
|
||||
@@ -23,41 +23,6 @@
|
||||
@import './normalize.css';
|
||||
@import './core.css';
|
||||
|
||||
/* Components */
|
||||
@import '../components/Avatar/Avatar.styles.css';
|
||||
@import '../components/Box/styles.css';
|
||||
@import '../components/Button/styles.css';
|
||||
@import '../components/ButtonIcon/styles.css';
|
||||
@import '../components/Card/Card.styles.css';
|
||||
@import '../components/Checkbox/styles.css';
|
||||
@import '../components/Collapsible/Collapsible.styles.css';
|
||||
@import '../components/Container/styles.css';
|
||||
@import '../components/Dialog/Dialog.styles.css';
|
||||
@import '../components/FieldError/FieldError.styles.css';
|
||||
@import '../components/FieldLabel/FieldLabel.styles.css';
|
||||
@import '../components/Flex/styles.css';
|
||||
@import '../components/Grid/styles.css';
|
||||
@import '../components/Header/Header.styles.css';
|
||||
@import '../components/HeaderPage/HeaderPage.styles.css';
|
||||
@import '../components/Icon/styles.css';
|
||||
@import '../components/Link/styles.css';
|
||||
@import '../components/Menu/Menu.styles.css';
|
||||
@import '../components/Popover/Popover.styles.css';
|
||||
@import '../components/RadioGroup/RadioGroup.styles.css';
|
||||
@import '../components/Table/Table.styles.css';
|
||||
@import '../components/TablePagination/TablePagination.styles.css';
|
||||
@import '../components/Tabs/Tabs.styles.css';
|
||||
@import '../components/TagGroup/TagGroup.styles.css';
|
||||
@import '../components/Text/styles.css';
|
||||
@import '../components/TextField/TextField.styles.css';
|
||||
@import '../components/PasswordField/PasswordField.styles.css';
|
||||
@import '../components/SearchField/SearchField.styles.css';
|
||||
@import '../components/Skeleton/Skeleton.styles.css';
|
||||
@import '../components/Tooltip/Tooltip.styles.css';
|
||||
@import '../components/ScrollArea/ScrollArea.styles.css';
|
||||
@import '../components/Select/Select.styles.css';
|
||||
@import '../components/Switch/Switch.styles.css';
|
||||
|
||||
/* Utilities */
|
||||
|
||||
/* Padding */
|
||||
|
||||
@@ -120,6 +120,11 @@ export const componentDefinitions = {
|
||||
footer: 'bui-DialogFooter',
|
||||
},
|
||||
},
|
||||
FieldError: {
|
||||
classNames: {
|
||||
root: 'bui-FieldError',
|
||||
},
|
||||
},
|
||||
FieldLabel: {
|
||||
classNames: {
|
||||
root: 'bui-FieldLabelWrapper',
|
||||
@@ -339,6 +344,14 @@ export const componentDefinitions = {
|
||||
cellProfileLink: 'bui-TableCellProfileLink',
|
||||
},
|
||||
},
|
||||
TablePagination: {
|
||||
classNames: {
|
||||
root: 'bui-TablePagination',
|
||||
left: 'bui-TablePaginationLeft',
|
||||
right: 'bui-TablePaginationRight',
|
||||
select: 'bui-TablePaginationSelect',
|
||||
},
|
||||
},
|
||||
Tabs: {
|
||||
classNames: {
|
||||
tabs: 'bui-Tabs',
|
||||
|
||||
Reference in New Issue
Block a user