Merge branch 'master' into runtime-module-federation-enablement
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -1,5 +1,221 @@
|
||||
# @backstage/ui
|
||||
|
||||
## 0.12.0-next.2
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- b63c25b: **BREAKING**: Removed gray scale tokens and renamed background surface tokens to neutral tokens
|
||||
|
||||
The `--bui-gray-1` through `--bui-gray-8` tokens have been removed. The `--bui-bg-surface-*` and `--bui-bg-neutral-on-surface-*` tokens have been replaced by a unified `--bui-bg-neutral-*` scale.
|
||||
|
||||
**Migration:**
|
||||
|
||||
Replace surface tokens directly:
|
||||
|
||||
```diff
|
||||
- background: var(--bui-bg-surface-0);
|
||||
+ background: var(--bui-bg-neutral-0);
|
||||
```
|
||||
|
||||
Replace on-surface tokens shifted by +1:
|
||||
|
||||
```diff
|
||||
- background: var(--bui-bg-neutral-on-surface-0);
|
||||
+ background: var(--bui-bg-neutral-1);
|
||||
```
|
||||
|
||||
Replace gray tokens 1-4 with neutral equivalents (`--bui-gray-5` through `--bui-gray-8` have no direct replacement):
|
||||
|
||||
```diff
|
||||
- background: var(--bui-gray-1);
|
||||
+ background: var(--bui-bg-neutral-1);
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- c8ae765: Fixed nested Accordion icon state issue where the inner accordion's arrow icon would incorrectly show as expanded when only the outer accordion was expanded. The CSS selector now uses a direct parent selector to ensure the icon only responds to its own accordion's expanded state.
|
||||
|
||||
Affected components: Accordion
|
||||
|
||||
- 5c76d13: Allow `ref` as a prop on the `Tag` component
|
||||
|
||||
Affected components: Tag
|
||||
|
||||
- 741a98d: Allow data to be passed directly to the `useTable` hook using the property `data` instead of `getData()` for mode `"complete"`.
|
||||
|
||||
This simplifies usage as data changes, rather than having to perform a `useEffect` when data changes, and then reloading the data. It also happens immediately, so stale data won't remain until a rerender (with an internal async state change), so less flickering.
|
||||
|
||||
Affected components: Table
|
||||
|
||||
- a0fe1b2: Fixed changing columns after first render from crashing. It now renders the table with the new column layout as columns change.
|
||||
|
||||
Affected components: Table
|
||||
|
||||
- becf851: export PasswordField component
|
||||
- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
|
||||
- 8c39412: The Table component now wraps the react-aria-components `Table` with a `ResizableTableContainer` only if any column has a width property set. This means that column widths can adapt to the content otherwise (if no width is explicitly set).
|
||||
|
||||
Affected components: Table
|
||||
|
||||
- 20131c5: Migrated to use the standard `backstage-cli package build` for CSS bundling instead of a custom build script.
|
||||
- Updated dependencies
|
||||
- @backstage/version-bridge@1.0.12-next.0
|
||||
|
||||
## 0.12.0-next.1
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- 058ffd9: **BREAKING**: Removed `large` size variant from Button component as it was never implemented.
|
||||
|
||||
**Migration:**
|
||||
|
||||
```diff
|
||||
- <Button size="large">Click me</Button>
|
||||
+ <Button size="medium">Click me</Button>
|
||||
```
|
||||
|
||||
**Affected components:** Button
|
||||
|
||||
- 110fec0: **BREAKING**: Removed link and tint color tokens, added new status foreground tokens, and improved Link component styling
|
||||
|
||||
The following color tokens have been removed:
|
||||
|
||||
- `--bui-fg-link` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
|
||||
- `--bui-fg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
|
||||
- `--bui-bg-tint` (and all related tokens: `-hover`, `-pressed`, `-disabled`)
|
||||
- `--bui-border-tint` (and all related tokens)
|
||||
|
||||
**New Status Tokens:**
|
||||
|
||||
Added dedicated tokens for status colors that distinguish between usage on status backgrounds vs. standalone usage:
|
||||
|
||||
- `--bui-fg-danger-on-bg` / `--bui-fg-danger`
|
||||
- `--bui-fg-warning-on-bg` / `--bui-fg-warning`
|
||||
- `--bui-fg-success-on-bg` / `--bui-fg-success`
|
||||
- `--bui-fg-info-on-bg` / `--bui-fg-info`
|
||||
|
||||
The `-on-bg` variants are designed for text on colored backgrounds, while the base variants are for standalone status indicators with improved visibility and contrast.
|
||||
|
||||
**Migration:**
|
||||
|
||||
For link colors, migrate to one of the following alternatives:
|
||||
|
||||
```diff
|
||||
.custom-link {
|
||||
- color: var(--bui-fg-link);
|
||||
+ color: var(--bui-fg-info); /* For informational links */
|
||||
+ /* or */
|
||||
+ color: var(--bui-fg-primary); /* For standard text links */
|
||||
}
|
||||
```
|
||||
|
||||
For tint colors (backgrounds, foregrounds, borders), migrate to appropriate status or neutral colors:
|
||||
|
||||
```diff
|
||||
.info-section {
|
||||
- background: var(--bui-bg-tint);
|
||||
+ background: var(--bui-bg-info); /* For informational sections */
|
||||
+ /* or */
|
||||
+ background: var(--bui-bg-neutral-on-surface-0); /* For neutral emphasis */
|
||||
}
|
||||
```
|
||||
|
||||
If you're using status foreground colors on colored backgrounds, update to the new `-on-bg` tokens:
|
||||
|
||||
```diff
|
||||
.error-badge {
|
||||
- color: var(--bui-fg-danger);
|
||||
+ color: var(--bui-fg-danger-on-bg);
|
||||
background: var(--bui-bg-danger);
|
||||
}
|
||||
```
|
||||
|
||||
**Affected components:** Link
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 4d1b7f4: Fixed CSS Module syntax to comply with Next.js 16 Turbopack validation by flattening nested dark theme selectors.
|
||||
|
||||
**Affected components:** Popover, Tooltip
|
||||
|
||||
- 2c219b9: Added `destructive` prop to Button for dangerous actions like delete or remove. Works with all variants (primary, secondary, tertiary).
|
||||
|
||||
**Affected components:** Button
|
||||
|
||||
- 5af9e14: Fixed `useDefinition` hook adding literal "undefined" class name when no className prop was passed.
|
||||
- 74c5a76: Fixed Switch component disabled state styling to show `not-allowed` cursor and disabled text color.
|
||||
|
||||
**Affected components:** Switch
|
||||
|
||||
## 0.12.0-next.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- b1f723b: **BREAKING**: Changed CSS selectors for `ButtonIcon` and `ButtonLink` components. Custom styles targeting `.bui-Button` to style these components must be updated to use `.bui-ButtonIcon` or `.bui-ButtonLink` respectively.
|
||||
|
||||
```diff
|
||||
-/* This no longer styles ButtonIcon or ButtonLink */
|
||||
-.bui-Button[data-variant="primary"] { ... }
|
||||
+/* Use component-specific selectors */
|
||||
+.bui-ButtonIcon[data-variant="primary"] { ... }
|
||||
+.bui-ButtonLink[data-variant="primary"] { ... }
|
||||
```
|
||||
|
||||
Affected components: ButtonIcon, ButtonLink
|
||||
|
||||
- caeb9ad: **BREAKING**: The `cell` and `header` properties in `ColumnConfig` now return `ReactElement` instead of `ReactNode`.
|
||||
|
||||
This fixes an issue where React Aria's Collection component would inject an `id` prop into Fragment wrappers, causing "Invalid prop `id` supplied to `React.Fragment`" errors on render.
|
||||
|
||||
Migration:
|
||||
|
||||
```diff
|
||||
const columns: ColumnConfig<MyItem>[] = [
|
||||
{
|
||||
id: 'name',
|
||||
label: 'Name',
|
||||
- cell: (item) => item.name,
|
||||
+ cell: (item) => <CellText title={item.name} />,
|
||||
- header: () => 'Name',
|
||||
+ header: () => <Column>Name</Column>,
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 350c948: Fixed Box component to forward HTML attributes to the underlying div element.
|
||||
|
||||
**Affected components:** Box
|
||||
|
||||
- 7455dae: Use node prefix on native imports
|
||||
- 508bd1a: Added new `Alert` component with support for status variants (info, success, warning, danger), icons, loading states, and custom actions.
|
||||
|
||||
Updated status color tokens for improved contrast and consistency across light and dark themes:
|
||||
|
||||
- Added new `--bui-bg-info` and `--bui-fg-info` tokens for info status
|
||||
- Updated `--bui-bg-danger`, `--bui-fg-danger` tokens
|
||||
- Updated `--bui-bg-warning`, `--bui-fg-warning` tokens
|
||||
- Updated `--bui-bg-success`, `--bui-fg-success` tokens
|
||||
|
||||
**Affected components**: Alert
|
||||
|
||||
- da30862: Fixed client-side navigation for container components by wrapping the container (not individual items) in RouterProvider. Components now conditionally provide routing context only when children have internal links, removing the Router context requirement when not needed. This also removes the need to wrap these components in MemoryRouter during tests when they are not using the `href` prop.
|
||||
|
||||
Additionally, when multiple tabs match the current URL via prefix matching, the tab with the most specific path (highest segment count) is now selected. For example, with URL `/catalog/users/john`, a tab with path `/catalog/users` is now selected over a tab with path `/catalog`.
|
||||
|
||||
Affected components: Tabs, Tab, TagGroup, Tag, Menu, MenuItem, MenuAutocomplete
|
||||
|
||||
- 092c453: Fixed an infinite render loop in Tabs when navigating to a URL that doesn't match any tab `href`.
|
||||
- 5320aa8: Fixed components to not require a Router context when rendering without internal links.
|
||||
|
||||
Affected components: Link, ButtonLink, Row
|
||||
|
||||
- cb090b4: Bump react-aria-components to v1.14.0
|
||||
- c429101: Fixed React 17 compatibility by using `useId` from `react-aria` instead of the built-in React hook which is only available in React 18+.
|
||||
- Updated dependencies
|
||||
- @backstage/version-bridge@1.0.11
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+20
-17
@@ -1,13 +1,11 @@
|
||||
{
|
||||
"name": "@backstage/ui",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.0-next.2",
|
||||
"backstage": {
|
||||
"role": "web-library"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "dist/index.esm.js",
|
||||
"types": "dist/index.d.ts"
|
||||
"access": "public"
|
||||
},
|
||||
"keywords": [
|
||||
"backstage"
|
||||
@@ -22,17 +20,25 @@
|
||||
"sideEffects": [
|
||||
"*.css"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./css/styles.css": "./src/css/styles.css",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"package.json": [
|
||||
"package.json"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"css"
|
||||
"dist"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "yarn build:app && yarn build:css",
|
||||
"build:app": "backstage-cli package build",
|
||||
"build:css": "node scripts/build-css.mjs",
|
||||
"build:css:watch": "node scripts/build-css.mjs --watch",
|
||||
"build": "backstage-cli package build",
|
||||
"clean": "backstage-cli package clean",
|
||||
"lint": "backstage-cli package lint",
|
||||
"prepack": "backstage-cli package prepack",
|
||||
@@ -50,22 +56,19 @@
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@types/react": "^18.0.0",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"chalk": "^5.4.1",
|
||||
"eslint-plugin-storybook": "^10.2.0-alpha.7",
|
||||
"eslint-plugin-storybook": "^10.3.0-alpha.1",
|
||||
"glob": "^11.0.1",
|
||||
"globals": "^15.11.0",
|
||||
"lightningcss": "^1.29.1",
|
||||
"mini-css-extract-plugin": "^2.9.2",
|
||||
"react": "^18.0.2",
|
||||
"react-dom": "^18.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"storybook": "^10.2.0-alpha.7"
|
||||
"react-router-dom": "^6.30.2",
|
||||
"storybook": "^10.3.0-alpha.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0",
|
||||
"react-router-dom": "^6.3.0"
|
||||
"react-router-dom": "^6.30.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
|
||||
+206
-94
@@ -144,7 +144,6 @@ export const AlertDefinition: {
|
||||
readonly spinner: 'bui-AlertSpinner';
|
||||
readonly actions: 'bui-AlertActions';
|
||||
};
|
||||
readonly surface: 'container';
|
||||
readonly propDefs: {
|
||||
readonly status: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -157,7 +156,6 @@ export const AlertDefinition: {
|
||||
readonly customActions: {};
|
||||
readonly title: {};
|
||||
readonly description: {};
|
||||
readonly surface: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
};
|
||||
@@ -165,7 +163,7 @@ export const AlertDefinition: {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
export type AlertOwnProps = {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
@@ -207,6 +205,23 @@ export interface AvatarProps extends React.ComponentPropsWithoutRef<'div'> {
|
||||
src: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BgContextValue {
|
||||
// (undocumented)
|
||||
bg: ContainerBg | undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const BgProvider: ({ bg, children }: BgProviderProps) => JSX_2.Element;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface BgProviderProps {
|
||||
// (undocumented)
|
||||
bg: ContainerBg;
|
||||
// (undocumented)
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export type Border = 'none' | 'base' | 'error' | 'warning' | 'selected';
|
||||
|
||||
@@ -234,12 +249,12 @@ export const BoxDefinition: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Box';
|
||||
};
|
||||
readonly surface: 'container';
|
||||
readonly bg: 'provider';
|
||||
readonly propDefs: {
|
||||
readonly as: {
|
||||
readonly default: 'div';
|
||||
};
|
||||
readonly surface: {
|
||||
readonly bg: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly children: {};
|
||||
@@ -275,7 +290,7 @@ export const BoxDefinition: {
|
||||
// @public (undocumented)
|
||||
export type BoxOwnProps = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
@@ -320,7 +335,7 @@ export const ButtonDefinition: {
|
||||
readonly content: 'bui-ButtonContent';
|
||||
readonly spinner: 'bui-ButtonSpinner';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -330,12 +345,14 @@ export const ButtonDefinition: {
|
||||
readonly dataAttribute: true;
|
||||
readonly default: 'primary';
|
||||
};
|
||||
readonly destructive: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly loading: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly iconStart: {};
|
||||
readonly iconEnd: {};
|
||||
readonly onSurface: {};
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
@@ -357,7 +374,7 @@ export const ButtonIconDefinition: {
|
||||
readonly content: 'bui-ButtonIconContent';
|
||||
readonly spinner: 'bui-ButtonIconSpinner';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -371,14 +388,13 @@ export const ButtonIconDefinition: {
|
||||
readonly dataAttribute: true;
|
||||
};
|
||||
readonly icon: {};
|
||||
readonly onSurface: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonIconOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonIconOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
icon?: ReactElement;
|
||||
@@ -406,7 +422,7 @@ export const ButtonLinkDefinition: {
|
||||
readonly root: 'bui-ButtonLink';
|
||||
readonly content: 'bui-ButtonLinkContent';
|
||||
};
|
||||
readonly surface: 'leaf';
|
||||
readonly bg: 'consumer';
|
||||
readonly propDefs: {
|
||||
readonly size: {
|
||||
readonly dataAttribute: true;
|
||||
@@ -418,7 +434,6 @@ export const ButtonLinkDefinition: {
|
||||
};
|
||||
readonly iconStart: {};
|
||||
readonly iconEnd: {};
|
||||
readonly onSurface: {};
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
readonly style: {};
|
||||
@@ -426,7 +441,7 @@ export const ButtonLinkDefinition: {
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonLinkOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonLinkOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
iconStart?: ReactElement;
|
||||
@@ -442,9 +457,10 @@ export interface ButtonLinkProps
|
||||
ButtonLinkOwnProps {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type ButtonOwnProps = LeafSurfaceProps & {
|
||||
size?: Responsive<'small' | 'medium' | 'large'>;
|
||||
export type ButtonOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
destructive?: boolean;
|
||||
iconStart?: ReactElement;
|
||||
iconEnd?: ReactElement;
|
||||
loading?: boolean;
|
||||
@@ -469,18 +485,41 @@ export const CardBody: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardBodyDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardBody';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardBodyOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardBodyProps
|
||||
extends CardBodyOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public
|
||||
export const CardDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-Card';
|
||||
readonly header: 'bui-CardHeader';
|
||||
readonly body: 'bui-CardBody';
|
||||
readonly footer: 'bui-CardFooter';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -490,10 +529,29 @@ export const CardFooter: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardFooterDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardFooter';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardFooterOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardFooterProps
|
||||
extends CardFooterOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public
|
||||
export const CardHeader: ForwardRefExoticComponent<
|
||||
@@ -501,16 +559,40 @@ export const CardHeader: ForwardRefExoticComponent<
|
||||
>;
|
||||
|
||||
// @public
|
||||
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export const CardHeaderDefinition: {
|
||||
readonly styles: {
|
||||
readonly [key: string]: string;
|
||||
};
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-CardHeader';
|
||||
};
|
||||
readonly propDefs: {
|
||||
readonly children: {};
|
||||
readonly className: {};
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardHeaderOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardHeaderProps
|
||||
extends CardHeaderOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export type CardOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export interface CardProps
|
||||
extends CardOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Cell: {
|
||||
@@ -650,6 +732,15 @@ export const Container: ForwardRefExoticComponent<
|
||||
ContainerProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type ContainerBg =
|
||||
| 'neutral-1'
|
||||
| 'neutral-2'
|
||||
| 'neutral-3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success';
|
||||
|
||||
// @public
|
||||
export const ContainerDefinition: {
|
||||
readonly classNames: {
|
||||
@@ -680,12 +771,6 @@ export interface ContainerProps {
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ContainerSurfaceProps {
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CursorParams<TFilter> {
|
||||
// (undocumented)
|
||||
@@ -857,11 +942,10 @@ export const FlexDefinition: {
|
||||
'direction',
|
||||
];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -877,6 +961,8 @@ export interface FlexProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
align?: Responsive<'start' | 'center' | 'end' | 'baseline' | 'stretch'>;
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
className?: string;
|
||||
@@ -888,8 +974,6 @@ export interface FlexProps extends SpaceProps {
|
||||
justify?: Responsive<'start' | 'center' | 'end' | 'between'>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -927,11 +1011,10 @@ export const GridDefinition: {
|
||||
'py',
|
||||
];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -946,11 +1029,10 @@ export const GridItemDefinition: {
|
||||
};
|
||||
readonly utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'];
|
||||
readonly dataAttributes: {
|
||||
readonly surface: readonly [
|
||||
'0',
|
||||
'1',
|
||||
'2',
|
||||
'3',
|
||||
readonly bg: readonly [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
@@ -960,6 +1042,8 @@ export const GridItemDefinition: {
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GridItemProps {
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
@@ -974,12 +1058,12 @@ export interface GridItemProps {
|
||||
rowSpan?: Responsive<Columns>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface GridProps extends SpaceProps {
|
||||
// (undocumented)
|
||||
bg?: Responsive<ProviderBg>;
|
||||
// (undocumented)
|
||||
children?: React.ReactNode;
|
||||
// (undocumented)
|
||||
@@ -990,8 +1074,6 @@ export interface GridProps extends SpaceProps {
|
||||
gap?: Responsive<Space>;
|
||||
// (undocumented)
|
||||
style?: React.CSSProperties;
|
||||
// (undocumented)
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public
|
||||
@@ -1084,12 +1166,6 @@ export type JustifyContent =
|
||||
| 'around'
|
||||
| 'between';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface LeafSurfaceProps {
|
||||
// (undocumented)
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const Link: ForwardRefExoticComponent<
|
||||
LinkProps & RefAttributes<HTMLAnchorElement>
|
||||
@@ -1109,8 +1185,10 @@ export const LinkDefinition: {
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
'info',
|
||||
];
|
||||
readonly truncate: readonly [true, false];
|
||||
readonly standalone: readonly [true, false];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1124,6 +1202,8 @@ export interface LinkProps extends LinkProps_2 {
|
||||
| TextColorStatus
|
||||
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
|
||||
// (undocumented)
|
||||
standalone?: boolean;
|
||||
// (undocumented)
|
||||
title?: string;
|
||||
// (undocumented)
|
||||
truncate?: boolean;
|
||||
@@ -1388,6 +1468,34 @@ export interface PaginationOptions
|
||||
initialOffset?: number;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export const PasswordField: ForwardRefExoticComponent<
|
||||
PasswordFieldProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const PasswordFieldDefinition: {
|
||||
readonly classNames: {
|
||||
readonly root: 'bui-PasswordField';
|
||||
readonly inputWrapper: 'bui-PasswordFieldInputWrapper';
|
||||
readonly input: 'bui-PasswordFieldInput';
|
||||
readonly inputIcon: 'bui-PasswordFieldIcon';
|
||||
readonly inputVisibility: 'bui-PasswordFieldVisibility';
|
||||
};
|
||||
readonly dataAttributes: {
|
||||
readonly size: readonly ['small', 'medium'];
|
||||
};
|
||||
};
|
||||
|
||||
// @public (undocumented)
|
||||
export interface PasswordFieldProps
|
||||
extends TextFieldProps_2,
|
||||
Omit<FieldLabelProps, 'htmlFor' | 'id' | 'className'> {
|
||||
icon?: ReactNode;
|
||||
placeholder?: string;
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const Popover: ForwardRefExoticComponent<
|
||||
PopoverProps & RefAttributes<HTMLDivElement>
|
||||
@@ -1408,6 +1516,9 @@ export interface PopoverProps extends Omit<PopoverProps_2, 'children'> {
|
||||
hideArrow?: boolean;
|
||||
}
|
||||
|
||||
// @public
|
||||
export type ProviderBg = ContainerBg | 'neutral-auto';
|
||||
|
||||
// @public (undocumented)
|
||||
export interface QueryOptions<TFilter> {
|
||||
// (undocumented)
|
||||
@@ -1619,17 +1730,6 @@ export const SubmenuTrigger: (props: SubmenuTriggerProps) => JSX_2.Element;
|
||||
// @public (undocumented)
|
||||
export interface SubmenuTriggerProps extends SubmenuTriggerProps_2 {}
|
||||
|
||||
// @public
|
||||
export type Surface =
|
||||
| '0'
|
||||
| '1'
|
||||
| '2'
|
||||
| '3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'auto';
|
||||
|
||||
// @public (undocumented)
|
||||
export const Switch: ForwardRefExoticComponent<
|
||||
SwitchProps & RefAttributes<HTMLLabelElement>
|
||||
@@ -1858,7 +1958,9 @@ export const TabsDefinition: {
|
||||
export interface TabsProps extends TabsProps_2 {}
|
||||
|
||||
// @public
|
||||
export const Tag: (props: TagProps) => JSX_2.Element;
|
||||
export const Tag: ForwardRefExoticComponent<
|
||||
TagProps & RefAttributes<HTMLDivElement>
|
||||
>;
|
||||
|
||||
// @public
|
||||
export const TagGroup: <T extends object>(
|
||||
@@ -1902,7 +2004,7 @@ export { Text_2 as Text };
|
||||
export type TextColors = 'primary' | 'secondary';
|
||||
|
||||
// @public (undocumented)
|
||||
export type TextColorStatus = 'danger' | 'warning' | 'success';
|
||||
export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info';
|
||||
|
||||
// @public
|
||||
export const TextDefinition: {
|
||||
@@ -1918,6 +2020,7 @@ export const TextDefinition: {
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
'info',
|
||||
];
|
||||
readonly truncate: readonly [true, false];
|
||||
};
|
||||
@@ -2042,7 +2145,6 @@ export interface ToggleButtonProps extends ToggleButtonProps_2 {
|
||||
iconEnd?: ReactElement;
|
||||
// (undocumented)
|
||||
iconStart?: ReactElement;
|
||||
onSurface?: Responsive<Surface>;
|
||||
// (undocumented)
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
}
|
||||
@@ -2071,6 +2173,12 @@ export const TooltipTrigger: (
|
||||
props: TooltipTriggerComponentProps,
|
||||
) => JSX_2.Element;
|
||||
|
||||
// @public
|
||||
export function useBgConsumer(): BgContextValue;
|
||||
|
||||
// @public
|
||||
export function useBgProvider(bg?: Responsive<ProviderBg>): BgContextValue;
|
||||
|
||||
// @public (undocumented)
|
||||
export const useBreakpoint: () => {
|
||||
breakpoint: Breakpoint;
|
||||
@@ -2084,21 +2192,25 @@ export function useTable<T extends TableItem, TFilter = unknown>(
|
||||
): UseTableResult<T, TFilter>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTableCompleteOptions<T extends TableItem, TFilter = unknown>
|
||||
extends QueryOptions<TFilter> {
|
||||
// (undocumented)
|
||||
filterFn?: (data: T[], filter: TFilter) => T[];
|
||||
// (undocumented)
|
||||
getData: () => T[] | Promise<T[]>;
|
||||
// (undocumented)
|
||||
export type UseTableCompleteOptions<
|
||||
T extends TableItem,
|
||||
TFilter = unknown,
|
||||
> = QueryOptions<TFilter> & {
|
||||
mode: 'complete';
|
||||
// (undocumented)
|
||||
paginationOptions?: PaginationOptions;
|
||||
// (undocumented)
|
||||
searchFn?: (data: T[], search: string) => T[];
|
||||
// (undocumented)
|
||||
sortFn?: (data: T[], sort: SortDescriptor) => T[];
|
||||
}
|
||||
filterFn?: (data: T[], filter: TFilter) => T[];
|
||||
searchFn?: (data: T[], search: string) => T[];
|
||||
} & (
|
||||
| {
|
||||
data: T[] | undefined;
|
||||
getData?: never;
|
||||
}
|
||||
| {
|
||||
data?: never;
|
||||
getData: () => T[] | Promise<T[]>;
|
||||
}
|
||||
);
|
||||
|
||||
// @public (undocumented)
|
||||
export interface UseTableCursorOptions<T extends TableItem, TFilter = unknown>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2024 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.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-restricted-imports */
|
||||
import { transform, bundle } from 'lightningcss';
|
||||
import fs from 'node:fs';
|
||||
import chalk from 'chalk';
|
||||
/* eslint-enable no-restricted-imports */
|
||||
|
||||
const srcFile = 'src/css/styles.css';
|
||||
const distDir = 'css';
|
||||
const distFile = `${distDir}/styles.css`;
|
||||
|
||||
// Check if styles.css exists
|
||||
if (!fs.existsSync(srcFile)) {
|
||||
console.error(`${srcFile} does not exist`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Ensure the dist/css directory exists
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true });
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const watchMode = args.includes('--watch');
|
||||
|
||||
async function buildCSS(logs = true) {
|
||||
const css = fs.readFileSync(srcFile);
|
||||
|
||||
let { code: bundleCode } = bundle({
|
||||
filename: srcFile,
|
||||
});
|
||||
|
||||
const { code } = transform({
|
||||
filename: distFile,
|
||||
code: bundleCode,
|
||||
minify: false,
|
||||
});
|
||||
|
||||
// Prepend the layer order declaration that lightningcss removes during bundling
|
||||
// This is crucial to maintain the correct layer cascade order
|
||||
const layerDeclaration = '@layer tokens, base, components, utilities;\n\n';
|
||||
const finalCode = layerDeclaration + code;
|
||||
|
||||
fs.writeFileSync(distFile, finalCode);
|
||||
if (logs) {
|
||||
console.log(chalk.blue('CSS transformed and minified: ') + 'styles.css');
|
||||
console.log(chalk.green('CSS file built successfully!'));
|
||||
}
|
||||
}
|
||||
|
||||
if (watchMode) {
|
||||
fs.watch('src/css', { recursive: true }, (eventType, filename) => {
|
||||
if (filename === 'styles.css') {
|
||||
console.log(
|
||||
chalk.yellow(`Changes detected in ${filename}, rebuilding...`),
|
||||
);
|
||||
buildCSS(false).catch(console.error);
|
||||
}
|
||||
});
|
||||
buildCSS().catch(console.error);
|
||||
console.log(chalk.yellow('Watching for CSS changes...'));
|
||||
} else {
|
||||
buildCSS().catch(console.error);
|
||||
}
|
||||
@@ -19,7 +19,7 @@
|
||||
@layer components {
|
||||
.bui-Accordion {
|
||||
width: 100%;
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
border-radius: var(--bui-radius-3);
|
||||
padding: var(--bui-space-3);
|
||||
}
|
||||
@@ -73,13 +73,13 @@
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
[data-expanded='true'] & {
|
||||
.bui-Accordion[data-expanded='true'] > .bui-AccordionTrigger & {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-AccordionPanel {
|
||||
[data-expanded='true'] & {
|
||||
.bui-Accordion[data-expanded='true'] > & {
|
||||
padding-top: var(--bui-space-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
@layer components {
|
||||
.bui-Alert {
|
||||
--alert-bg: var(--bui-bg-surface-1);
|
||||
--alert-bg: var(--bui-bg-neutral-1);
|
||||
--alert-fg: var(--bui-fg-primary);
|
||||
|
||||
display: flex;
|
||||
@@ -36,22 +36,22 @@
|
||||
|
||||
.bui-Alert[data-status='info'] {
|
||||
--alert-bg: var(--bui-bg-info);
|
||||
--alert-fg: var(--bui-fg-info);
|
||||
--alert-fg: var(--bui-fg-info-on-bg);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='success'] {
|
||||
--alert-bg: var(--bui-bg-success);
|
||||
--alert-fg: var(--bui-fg-success);
|
||||
--alert-fg: var(--bui-fg-success-on-bg);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='warning'] {
|
||||
--alert-bg: var(--bui-bg-warning);
|
||||
--alert-fg: var(--bui-fg-warning);
|
||||
--alert-fg: var(--bui-fg-warning-on-bg);
|
||||
}
|
||||
|
||||
.bui-Alert[data-status='danger'] {
|
||||
--alert-bg: var(--bui-bg-danger);
|
||||
--alert-fg: var(--bui-fg-danger);
|
||||
--alert-fg: var(--bui-fg-danger-on-bg);
|
||||
}
|
||||
|
||||
.bui-AlertIcon {
|
||||
|
||||
@@ -284,50 +284,38 @@ export const LongContent = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const OnDifferentSurfaces = meta.story({
|
||||
export const OnDifferentBackgrounds = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Default Surface</Text>
|
||||
<Text>Default</Text>
|
||||
<Flex direction="column" gap="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on default surface" />
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
title="Alert on default surface"
|
||||
/>
|
||||
<Alert status="info" icon={true} title="Alert on default bg" />
|
||||
<Alert status="success" icon={true} title="Alert on default bg" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex direction="column" gap="2" surface="0" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 0" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 0" />
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-1" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-1" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-1" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex direction="column" gap="2" surface="1" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 1" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 1" />
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-2" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-2" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex direction="column" gap="2" surface="2" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 2" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 2" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex direction="column" gap="2" surface="3" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on surface 3" />
|
||||
<Alert status="success" icon={true} title="Alert on surface 3" />
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex direction="column" gap="2" bg="neutral-3" p="4">
|
||||
<Alert status="info" icon={true} title="Alert on neutral-3" />
|
||||
<Alert status="success" icon={true} title="Alert on neutral-3" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -349,7 +337,7 @@ export const Responsive = meta.story({
|
||||
|
||||
export const WithUtilityProps = meta.story({
|
||||
render: () => (
|
||||
<Box surface="1" py="4">
|
||||
<Box bg="neutral-1" py="4">
|
||||
<Alert
|
||||
status="success"
|
||||
icon={true}
|
||||
|
||||
@@ -34,7 +34,6 @@ export const AlertDefinition = defineComponent<AlertOwnProps>()({
|
||||
spinner: 'bui-AlertSpinner',
|
||||
actions: 'bui-AlertActions',
|
||||
},
|
||||
surface: 'container',
|
||||
propDefs: {
|
||||
status: { dataAttribute: true, default: 'info' },
|
||||
loading: { dataAttribute: true },
|
||||
@@ -42,7 +41,6 @@ export const AlertDefinition = defineComponent<AlertOwnProps>()({
|
||||
customActions: {},
|
||||
title: {},
|
||||
description: {},
|
||||
surface: {},
|
||||
className: {},
|
||||
style: {},
|
||||
},
|
||||
|
||||
@@ -15,14 +15,10 @@
|
||||
*/
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type {
|
||||
ContainerSurfaceProps,
|
||||
Responsive,
|
||||
MarginProps,
|
||||
} from '../../types';
|
||||
import type { Responsive, MarginProps } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type AlertOwnProps = ContainerSurfaceProps & {
|
||||
export type AlertOwnProps = {
|
||||
status?: Responsive<'info' | 'success' | 'warning' | 'danger'>;
|
||||
icon?: boolean | ReactElement;
|
||||
loading?: boolean;
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
user-select: none;
|
||||
font-weight: 500;
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
font-size: 1rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
|
||||
@@ -23,31 +23,27 @@
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='0'] {
|
||||
background-color: var(--bui-bg-surface-0);
|
||||
.bui-Box[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='1'] {
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
.bui-Box[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='2'] {
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
.bui-Box[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='3'] {
|
||||
background-color: var(--bui-bg-surface-3);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='danger'] {
|
||||
.bui-Box[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='warning'] {
|
||||
.bui-Box[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Box[data-surface='success'] {
|
||||
.bui-Box[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,90 +346,47 @@ export const Display = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const BackgroundColors = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex align="center" style={{ flexWrap: 'wrap' }}>
|
||||
<Box {...args}>Default</Box>
|
||||
<Box surface="0" {...args}>
|
||||
Surface 0
|
||||
<Box bg="neutral-1" {...args}>
|
||||
Neutral 1
|
||||
</Box>
|
||||
<Box surface="1" {...args}>
|
||||
Surface 1
|
||||
<Box bg="neutral-2" {...args}>
|
||||
Neutral 2
|
||||
</Box>
|
||||
<Box surface="2" {...args}>
|
||||
Surface 2
|
||||
<Box bg="neutral-3" {...args}>
|
||||
Neutral 3
|
||||
</Box>
|
||||
<Box surface="3" {...args}>
|
||||
Surface 3
|
||||
<Box bg={{ initial: 'neutral-1', sm: 'neutral-2' }} {...args}>
|
||||
Responsive Neutral
|
||||
</Box>
|
||||
<Box surface={{ initial: '0', sm: '1' }} {...args}>
|
||||
Responsive Surface
|
||||
<Box bg="danger" {...args}>
|
||||
Danger
|
||||
</Box>
|
||||
<Box surface="danger" {...args}>
|
||||
Surface Danger
|
||||
<Box bg="warning" {...args}>
|
||||
Warning
|
||||
</Box>
|
||||
<Box surface="warning" {...args}>
|
||||
Surface Warning
|
||||
</Box>
|
||||
<Box surface="success" {...args}>
|
||||
Surface Success
|
||||
<Box bg="success" {...args}>
|
||||
Success
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesNested = meta.story({
|
||||
export const NestedNeutralColors = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
In this test, we are nesting boxes and buttons on different surfaces to
|
||||
ensure that the correct surface is applied to each element. If a Button
|
||||
is placed on a surface that doesn't have the surface prop set, it will
|
||||
inherit the surface from the parent.
|
||||
</Box>
|
||||
<Box {...args} surface="1">
|
||||
<Button variant="secondary">Button</Button>
|
||||
<Box {...args} surface="2" mt="4">
|
||||
<Button variant="secondary">Button</Button>
|
||||
<Box {...args} mt="4">
|
||||
<Button variant="secondary">Button</Button>
|
||||
</Box>
|
||||
<Box {...args} bg="neutral-1">
|
||||
<Button variant="secondary">Button (on neutral-1)</Button>
|
||||
<Box {...args} bg="neutral-2" mt="4">
|
||||
<Button variant="secondary">Button (on neutral-2)</Button>
|
||||
<Box {...args} bg="neutral-3" mt="4">
|
||||
<Button variant="secondary">Button (on neutral-3)</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
Using surface="auto" automatically increments from the parent surface
|
||||
level. This makes components more reusable as they don't need to know
|
||||
their absolute surface level. Notice how each nested Box with
|
||||
surface="auto" automatically increments: 0 → 1 → 2 → 3 (capped at 3).
|
||||
</Box>
|
||||
<Box {...args} surface="0">
|
||||
Surface 0 (explicit)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
<Box mb="3">Surface auto (becomes 1)</Box>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Button auto
|
||||
</Button>
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (becomes 2)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (becomes 3)
|
||||
<Box {...args} surface="auto" mt="4">
|
||||
Surface auto (stays 3 - capped)
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Box>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
BoxDefinition,
|
||||
props,
|
||||
);
|
||||
const { classes, as, surfaceChildren } = ownProps;
|
||||
const { classes, as, childrenWithBgProvider } = ownProps;
|
||||
|
||||
return createElement(
|
||||
as,
|
||||
@@ -36,7 +36,7 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
|
||||
...dataAttributes,
|
||||
...restProps,
|
||||
},
|
||||
surfaceChildren,
|
||||
childrenWithBgProvider,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -27,10 +27,10 @@ export const BoxDefinition = defineComponent<BoxOwnProps>()({
|
||||
classNames: {
|
||||
root: 'bui-Box',
|
||||
},
|
||||
surface: 'container',
|
||||
bg: 'provider',
|
||||
propDefs: {
|
||||
as: { default: 'div' },
|
||||
surface: { dataAttribute: true },
|
||||
bg: { dataAttribute: true },
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
*/
|
||||
|
||||
import type { ReactNode, CSSProperties } from 'react';
|
||||
import type { Responsive, Surface, SpaceProps } from '../../types';
|
||||
import type { Responsive, ProviderBg, SpaceProps } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type BoxOwnProps = {
|
||||
as?: keyof JSX.IntrinsicElements;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
|
||||
@@ -79,28 +79,61 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-variant='primary'][data-destructive='true'] {
|
||||
/* Custom properties matching future token names (without bui- prefix) */
|
||||
--bg-solid-danger: #dc2626;
|
||||
--bg-solid-danger-hover: #b91c1c;
|
||||
--bg-solid-danger-pressed: #991b1b;
|
||||
--bg-solid-danger-disabled: #fca5a5;
|
||||
--fg-solid-danger: var(--bui-white);
|
||||
|
||||
[data-theme-mode='dark'] & {
|
||||
--bg-solid-danger: #ef4444;
|
||||
--bg-solid-danger-hover: #dc2626;
|
||||
--bg-solid-danger-pressed: #b91c1c;
|
||||
--bg-solid-danger-disabled: #7f1d1d;
|
||||
}
|
||||
|
||||
--bg: var(--bg-solid-danger);
|
||||
--bg-hover: var(--bg-solid-danger-hover);
|
||||
--bg-active: var(--bg-solid-danger-pressed);
|
||||
--fg: var(--fg-solid-danger);
|
||||
|
||||
&[data-disabled='true'],
|
||||
&[data-loading='true'] {
|
||||
--bg: var(--bg-solid-danger-disabled);
|
||||
--bg-hover: var(--bg-solid-danger-disabled);
|
||||
--bg-active: var(--bg-solid-danger-disabled);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
outline: 2px solid var(--bui-border-danger);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-variant='secondary'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-0);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg: var(--bui-bg-neutral-1);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-1);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-2);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-3);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'],
|
||||
@@ -117,24 +150,51 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-variant='secondary'][data-destructive='true'] {
|
||||
/* Custom properties for hover/active states (no tokens exist yet) */
|
||||
--bg-danger-hover: #fecaca;
|
||||
--bg-danger-pressed: #fca5a5;
|
||||
|
||||
[data-theme-mode='dark'] & {
|
||||
--bg-danger-hover: #450a0a;
|
||||
--bg-danger-pressed: #7f1d1d;
|
||||
}
|
||||
|
||||
--bg: var(--bui-bg-danger);
|
||||
--bg-hover: var(--bg-danger-hover);
|
||||
--bg-active: var(--bg-danger-pressed);
|
||||
--fg: var(--bui-fg-danger);
|
||||
|
||||
&[data-disabled='true'],
|
||||
&[data-loading='true'] {
|
||||
--bg-hover: var(--bg);
|
||||
--bg-active: var(--bg);
|
||||
--fg: var(--bui-fg-disabled);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: inset 0 0 0 2px var(--bui-border-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-variant='tertiary'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'],
|
||||
@@ -151,6 +211,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-variant='tertiary'][data-destructive='true'] {
|
||||
/* Custom properties for hover/active states (no tokens exist yet) */
|
||||
--bg-danger-hover: #fecaca;
|
||||
--bg-danger-pressed: #fca5a5;
|
||||
|
||||
[data-theme-mode='dark'] & {
|
||||
--bg-danger-hover: #450a0a;
|
||||
--bg-danger-pressed: #7f1d1d;
|
||||
}
|
||||
|
||||
--bg-hover: var(--bui-bg-danger);
|
||||
--bg-active: var(--bg-danger-pressed);
|
||||
--fg: var(--bui-fg-danger);
|
||||
|
||||
&[data-disabled='true'],
|
||||
&[data-loading='true'] {
|
||||
--bg-hover: var(--bg);
|
||||
--bg-active: var(--bg);
|
||||
--fg: var(--bui-fg-disabled);
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: inset 0 0 0 2px var(--bui-border-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.bui-Button[data-size='small'] {
|
||||
font-size: var(--bui-font-size-3);
|
||||
padding: 0 var(--bui-space-2);
|
||||
|
||||
@@ -31,7 +31,10 @@ const meta = preview.meta({
|
||||
},
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['primary', 'secondary'],
|
||||
options: ['primary', 'secondary', 'tertiary'],
|
||||
},
|
||||
destructive: {
|
||||
control: 'boolean',
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -67,11 +70,20 @@ export const Variants = meta.story({
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4">
|
||||
<Text>Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
@@ -81,11 +93,20 @@ export const Variants = meta.story({
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4">
|
||||
<Text>Neutral 2</Text>
|
||||
<Flex align="center" bg="neutral-2" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
@@ -95,11 +116,20 @@ export const Variants = meta.story({
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4">
|
||||
<Text>Neutral 3</Text>
|
||||
<Flex align="center" bg="neutral-3" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
</Button>
|
||||
@@ -109,19 +139,97 @@ export const Variants = meta.story({
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="primary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary" destructive>
|
||||
Button
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const Destructive = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Primary Destructive</Text>
|
||||
<Flex align="center" p="4" gap="4">
|
||||
<Button variant="primary" destructive>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="primary" destructive iconStart={<RiCloudLine />}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="primary" destructive isDisabled>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button variant="primary" destructive loading>
|
||||
Loading
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4">
|
||||
<Button iconStart={<RiCloudLine />} variant="primary">
|
||||
Button
|
||||
<Text>Secondary Destructive</Text>
|
||||
<Flex align="center" p="4" gap="4">
|
||||
<Button variant="secondary" destructive>
|
||||
Delete
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="secondary">
|
||||
Button
|
||||
<Button variant="secondary" destructive iconStart={<RiCloudLine />}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button iconStart={<RiCloudLine />} variant="tertiary">
|
||||
Button
|
||||
<Button variant="secondary" destructive isDisabled>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button variant="secondary" destructive loading>
|
||||
Loading
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Tertiary Destructive</Text>
|
||||
<Flex align="center" p="4" gap="4">
|
||||
<Button variant="tertiary" destructive>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="tertiary" destructive iconStart={<RiCloudLine />}>
|
||||
Delete
|
||||
</Button>
|
||||
<Button variant="tertiary" destructive isDisabled>
|
||||
Disabled
|
||||
</Button>
|
||||
<Button variant="tertiary" destructive loading>
|
||||
Loading
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4" gap="4">
|
||||
<Button variant="primary" destructive>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" destructive>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary" destructive>
|
||||
Tertiary
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>Sizes</Text>
|
||||
<Flex align="center" p="4" gap="4">
|
||||
<Button variant="primary" destructive size="small">
|
||||
Small
|
||||
</Button>
|
||||
<Button variant="primary" destructive size="medium">
|
||||
Medium
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
@@ -181,16 +289,29 @@ export const FullWidth = meta.story({
|
||||
|
||||
export const Disabled = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="row" gap="4">
|
||||
<Button variant="primary" isDisabled>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary" isDisabled>
|
||||
Tertiary
|
||||
</Button>
|
||||
<Flex direction="column" gap="4">
|
||||
<Flex direction="row" gap="4">
|
||||
<Button variant="primary" isDisabled>
|
||||
Primary
|
||||
</Button>
|
||||
<Button variant="secondary" isDisabled>
|
||||
Secondary
|
||||
</Button>
|
||||
<Button variant="tertiary" isDisabled>
|
||||
Tertiary
|
||||
</Button>
|
||||
</Flex>
|
||||
<Flex direction="row" gap="4">
|
||||
<Button variant="primary" destructive isDisabled>
|
||||
Primary Destructive
|
||||
</Button>
|
||||
<Button variant="secondary" destructive isDisabled>
|
||||
Secondary Destructive
|
||||
</Button>
|
||||
<Button variant="tertiary" destructive isDisabled>
|
||||
Tertiary Destructive
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
@@ -270,6 +391,24 @@ export const LoadingVariants = meta.story({
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Text>Primary Destructive</Text>
|
||||
<Flex align="center" gap="4">
|
||||
<Button variant="primary" destructive size="small" loading>
|
||||
Small Loading
|
||||
</Button>
|
||||
<Button variant="primary" destructive size="medium" loading>
|
||||
Medium Loading
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
destructive
|
||||
loading
|
||||
iconStart={<RiCloudLine />}
|
||||
>
|
||||
With Icon
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
<Text>Loading vs Disabled</Text>
|
||||
<Flex align="center" gap="4">
|
||||
<Button variant="primary" loading>
|
||||
@@ -286,49 +425,33 @@ export const LoadingVariants = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const OnSurfaceAuto = meta.story({
|
||||
export const AutoBg = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<div style={{ maxWidth: '600px' }}>
|
||||
Using onSurface="auto" on buttons inherits their container's surface
|
||||
level, making them reusable. This is equivalent to not specifying
|
||||
onSurface. To override, use explicit surface values like onSurface="0"
|
||||
or onSurface="2".
|
||||
Buttons automatically detect their parent bg context and increment the
|
||||
neutral level by 1. No prop is needed on the button -- it's fully
|
||||
automatic.
|
||||
</div>
|
||||
<Box surface="0" p="4">
|
||||
<Text>Surface 0 container</Text>
|
||||
<Box bg="neutral-1" p="4">
|
||||
<Text>Neutral 1 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 0)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 0)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="1">
|
||||
Explicit 1
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-2)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-2)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box surface="1" p="4">
|
||||
<Text>Surface 1 container</Text>
|
||||
<Box bg="neutral-2" p="4">
|
||||
<Text>Neutral 2 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 1)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 1)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="2">
|
||||
Explicit 2
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-3)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-3)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
<Box surface="2" p="4">
|
||||
<Text>Surface 2 container</Text>
|
||||
<Box bg="neutral-3" p="4">
|
||||
<Text>Neutral 3 container</Text>
|
||||
<Flex gap="2" mt="2">
|
||||
<Button variant="secondary">Default (inherits 2)</Button>
|
||||
<Button variant="secondary" onSurface="auto">
|
||||
Auto (inherits 2)
|
||||
</Button>
|
||||
<Button variant="secondary" onSurface="3">
|
||||
Explicit 3
|
||||
</Button>
|
||||
<Button variant="secondary">Auto (neutral-4)</Button>
|
||||
<Button variant="tertiary">Auto (neutral-4)</Button>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Flex>
|
||||
|
||||
@@ -29,14 +29,14 @@ export const ButtonDefinition = defineComponent<ButtonOwnProps>()({
|
||||
content: 'bui-ButtonContent',
|
||||
spinner: 'bui-ButtonSpinner',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
destructive: { dataAttribute: true },
|
||||
loading: { dataAttribute: true },
|
||||
iconStart: {},
|
||||
iconEnd: {},
|
||||
onSurface: {},
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -16,12 +16,13 @@
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type { ButtonProps as RAButtonProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonOwnProps = LeafSurfaceProps & {
|
||||
size?: Responsive<'small' | 'medium' | 'large'>;
|
||||
export type ButtonOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
destructive?: boolean;
|
||||
iconStart?: ReactElement;
|
||||
iconEnd?: ReactElement;
|
||||
loading?: boolean;
|
||||
|
||||
@@ -80,27 +80,27 @@
|
||||
}
|
||||
|
||||
.bui-ButtonIcon[data-variant='secondary'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-0);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg: var(--bui-bg-neutral-1);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-1);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-2);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-3);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'],
|
||||
@@ -118,23 +118,23 @@
|
||||
}
|
||||
|
||||
.bui-ButtonIcon[data-variant='tertiary'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'],
|
||||
|
||||
@@ -29,13 +29,12 @@ export const ButtonIconDefinition = defineComponent<ButtonIconOwnProps>()({
|
||||
content: 'bui-ButtonIconContent',
|
||||
spinner: 'bui-ButtonIconSpinner',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
loading: { dataAttribute: true },
|
||||
icon: {},
|
||||
onSurface: {},
|
||||
className: {},
|
||||
style: {},
|
||||
},
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import type { ReactElement, CSSProperties } from 'react';
|
||||
import type { ButtonProps as RAButtonProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonIconOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonIconOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
icon?: ReactElement;
|
||||
|
||||
@@ -74,27 +74,27 @@
|
||||
}
|
||||
|
||||
.bui-ButtonLink[data-variant='secondary'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-0);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg: var(--bui-bg-neutral-1);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-1);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg: var(--bui-bg-neutral-2);
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-2);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg: var(--bui-bg-neutral-3);
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg: var(--bui-bg-neutral-on-surface-3);
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg: var(--bui-bg-neutral-4);
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
@@ -111,23 +111,23 @@
|
||||
}
|
||||
|
||||
.bui-ButtonLink[data-variant='tertiary'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-0-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-0-pressed);
|
||||
--bg-hover: var(--bui-bg-neutral-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-1-pressed);
|
||||
--fg: var(--bui-fg-primary);
|
||||
|
||||
&[data-on-surface='1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-1-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-1-pressed);
|
||||
&[data-on-bg='neutral-1'] {
|
||||
--bg-hover: var(--bui-bg-neutral-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-2-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-2-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-2-pressed);
|
||||
&[data-on-bg='neutral-2'] {
|
||||
--bg-hover: var(--bui-bg-neutral-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-3-pressed);
|
||||
}
|
||||
|
||||
&[data-on-surface='3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-on-surface-3-hover);
|
||||
--bg-active: var(--bui-bg-neutral-on-surface-3-pressed);
|
||||
&[data-on-bg='neutral-3'] {
|
||||
--bg-hover: var(--bui-bg-neutral-4-hover);
|
||||
--bg-active: var(--bui-bg-neutral-4-pressed);
|
||||
}
|
||||
|
||||
&[data-disabled='true'] {
|
||||
|
||||
@@ -28,13 +28,12 @@ export const ButtonLinkDefinition = defineComponent<ButtonLinkOwnProps>()({
|
||||
root: 'bui-ButtonLink',
|
||||
content: 'bui-ButtonLinkContent',
|
||||
},
|
||||
surface: 'leaf',
|
||||
bg: 'consumer',
|
||||
propDefs: {
|
||||
size: { dataAttribute: true, default: 'small' },
|
||||
variant: { dataAttribute: true, default: 'primary' },
|
||||
iconStart: {},
|
||||
iconEnd: {},
|
||||
onSurface: {},
|
||||
children: {},
|
||||
className: {},
|
||||
style: {},
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
import type { ReactElement, ReactNode, CSSProperties } from 'react';
|
||||
import type { LinkProps as RALinkProps } from 'react-aria-components';
|
||||
import type { LeafSurfaceProps, Responsive } from '../../types';
|
||||
import type { Responsive } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export type ButtonLinkOwnProps = LeafSurfaceProps & {
|
||||
export type ButtonLinkOwnProps = {
|
||||
size?: Responsive<'small' | 'medium'>;
|
||||
variant?: Responsive<'primary' | 'secondary' | 'tertiary'>;
|
||||
iconStart?: ReactElement;
|
||||
|
||||
@@ -21,11 +21,9 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--bui-space-3);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
border-radius: var(--bui-radius-3);
|
||||
padding-block: var(--bui-space-3);
|
||||
color: var(--bui-fg-primary);
|
||||
border: 1px solid var(--bui-border);
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
width: 100%;
|
||||
|
||||
@@ -16,6 +16,9 @@
|
||||
import preview from '../../../../../.storybook/preview';
|
||||
import { Card, CardHeader, CardBody, CardFooter } from './Card';
|
||||
import { Text } from '../..';
|
||||
import { Flex } from '../Flex';
|
||||
import { Box } from '../Box';
|
||||
import { Button } from '../Button';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Card',
|
||||
@@ -80,7 +83,7 @@ const ListRow = ({ children }: { children: React.ReactNode }) => {
|
||||
style={{
|
||||
height: 40,
|
||||
width: '100%',
|
||||
backgroundColor: 'var(--bui-gray-3)',
|
||||
backgroundColor: 'var(--bui-bg-neutral-3)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
paddingInline: 'var(--bui-space-3)',
|
||||
@@ -124,3 +127,119 @@ export const WithListRow = meta.story({
|
||||
</Card>
|
||||
),
|
||||
});
|
||||
|
||||
export const Backgrounds = meta.story({
|
||||
render: args => (
|
||||
<Flex align="start" style={{ flexWrap: 'wrap' }} gap="4">
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>No parent</CardHeader>
|
||||
<CardBody>Defaults to neutral-1</CardBody>
|
||||
</Card>
|
||||
<Box bg="neutral-1" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-1</CardHeader>
|
||||
<CardBody>Auto-increments to neutral-2</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-2" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-2</CardHeader>
|
||||
<CardBody>Auto-increments to neutral-3</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-3" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-3</CardHeader>
|
||||
<CardBody>Steps up to neutral-4</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const BgNested = meta.story({
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Box style={{ maxWidth: '600px' }} mb="4">
|
||||
Nested cards auto-increment their neutral level. Buttons inherit the
|
||||
parent card's bg via data-on-bg.
|
||||
</Box>
|
||||
<Card {...args} style={{ width: '500px' }}>
|
||||
<CardHeader>Card (visual: neutral-1, provides: neutral-1)</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-1)</Button>
|
||||
<Card {...args} style={{ marginTop: '16px' }}>
|
||||
<CardHeader>
|
||||
Card (visual: neutral-2, provides: neutral-2)
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-2)</Button>
|
||||
<Card {...args} style={{ marginTop: '16px' }}>
|
||||
<CardHeader>
|
||||
Card (visual: neutral-4, provides: neutral-3)
|
||||
</CardHeader>
|
||||
<CardBody>
|
||||
<Button variant="secondary">Button (on neutral-3)</Button>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</CardBody>
|
||||
</Card>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const BgOnProviders = meta.story({
|
||||
render: args => (
|
||||
<Flex align="start" style={{ flexWrap: 'wrap' }} gap="4">
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>No provider</CardHeader>
|
||||
<CardBody>Card defaults to neutral-1</CardBody>
|
||||
</Card>
|
||||
<Box bg="neutral-1" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-1</CardHeader>
|
||||
<CardBody>Card auto-increments to neutral-2</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-2" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-2</CardHeader>
|
||||
<CardBody>Card auto-increments to neutral-3</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
<Box bg="neutral-3" p="4" style={{ borderRadius: '8px' }}>
|
||||
<Card {...args} style={{ width: '200px' }}>
|
||||
<CardHeader>On neutral-3</CardHeader>
|
||||
<CardBody>Card visually at neutral-4</CardBody>
|
||||
</Card>
|
||||
</Box>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const CustomCardWithBox = meta.story({
|
||||
render: () => (
|
||||
<Flex direction="column" gap="4">
|
||||
<Box style={{ maxWidth: '600px' }}>
|
||||
A custom card built with Box. Use Box with an explicit bg prop to create
|
||||
a card-like container that participates in the bg system as a provider.
|
||||
</Box>
|
||||
<Box
|
||||
bg="neutral-auto"
|
||||
p="4"
|
||||
style={{ borderRadius: '8px', width: '300px' }}
|
||||
>
|
||||
<Button variant="secondary" style={{ marginTop: '8px' }}>
|
||||
Button (on neutral-1)
|
||||
</Button>
|
||||
</Box>
|
||||
<Card style={{ width: '300px' }}>
|
||||
<CardHeader>Header</CardHeader>
|
||||
<CardBody>Body</CardBody>
|
||||
<CardFooter>Footer</CardFooter>
|
||||
</Card>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -15,16 +15,20 @@
|
||||
*/
|
||||
|
||||
import { forwardRef } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { CardDefinition } from './definition';
|
||||
import { useDefinition } from '../../hooks/useDefinition';
|
||||
import {
|
||||
CardDefinition,
|
||||
CardHeaderDefinition,
|
||||
CardBodyDefinition,
|
||||
CardFooterDefinition,
|
||||
} from './definition';
|
||||
import type {
|
||||
CardProps,
|
||||
CardHeaderProps,
|
||||
CardBodyProps,
|
||||
CardFooterProps,
|
||||
} from './types';
|
||||
import styles from './Card.module.css';
|
||||
import { Box } from '../Box/Box';
|
||||
|
||||
/**
|
||||
* Card component.
|
||||
@@ -32,18 +36,27 @@ import styles from './Card.module.css';
|
||||
* @public
|
||||
*/
|
||||
export const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps, dataAttributes } = useDefinition(
|
||||
CardDefinition,
|
||||
props,
|
||||
);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
<Box
|
||||
bg="neutral-auto"
|
||||
ref={ref}
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
{...rest}
|
||||
/>
|
||||
className={classes.root}
|
||||
{...dataAttributes}
|
||||
{...restProps}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
Card.displayName = 'Card';
|
||||
|
||||
/**
|
||||
* CardHeader component.
|
||||
*
|
||||
@@ -51,23 +64,19 @@ export const Card = forwardRef<HTMLDivElement, CardProps>((props, ref) => {
|
||||
*/
|
||||
export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardHeaderDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.header,
|
||||
styles[classNames.header],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
/**
|
||||
* CardBody component.
|
||||
*
|
||||
@@ -75,19 +84,19 @@ export const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(
|
||||
*/
|
||||
export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardBodyDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(classNames.body, styles[classNames.body], className)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardBody.displayName = 'CardBody';
|
||||
|
||||
/**
|
||||
* CardFooter component.
|
||||
*
|
||||
@@ -95,19 +104,15 @@ export const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(
|
||||
*/
|
||||
export const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(
|
||||
(props, ref) => {
|
||||
const { classNames, cleanedProps } = useStyles(CardDefinition, props);
|
||||
const { className, ...rest } = cleanedProps;
|
||||
const { ownProps, restProps } = useDefinition(CardFooterDefinition, props);
|
||||
const { classes, children } = ownProps;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(
|
||||
classNames.footer,
|
||||
styles[classNames.footer],
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
<div ref={ref} className={classes.root} {...restProps}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
@@ -14,17 +14,71 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentDefinition } from '../../types';
|
||||
import { defineComponent } from '../../hooks/useDefinition';
|
||||
import type {
|
||||
CardOwnProps,
|
||||
CardHeaderOwnProps,
|
||||
CardBodyOwnProps,
|
||||
CardFooterOwnProps,
|
||||
} from './types';
|
||||
import styles from './Card.module.css';
|
||||
|
||||
/**
|
||||
* Component definition for Card
|
||||
* @public
|
||||
*/
|
||||
export const CardDefinition = {
|
||||
export const CardDefinition = defineComponent<CardOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-Card',
|
||||
header: 'bui-CardHeader',
|
||||
body: 'bui-CardBody',
|
||||
footer: 'bui-CardFooter',
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardHeader
|
||||
* @public
|
||||
*/
|
||||
export const CardHeaderDefinition = defineComponent<CardHeaderOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardHeader',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardBody
|
||||
* @public
|
||||
*/
|
||||
export const CardBodyDefinition = defineComponent<CardBodyOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardBody',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Component definition for CardFooter
|
||||
* @public
|
||||
*/
|
||||
export const CardFooterDefinition = defineComponent<CardFooterOwnProps>()({
|
||||
styles,
|
||||
classNames: {
|
||||
root: 'bui-CardFooter',
|
||||
},
|
||||
propDefs: {
|
||||
children: {},
|
||||
className: {},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,11 +15,10 @@
|
||||
*/
|
||||
|
||||
export { Card, CardHeader, CardBody, CardFooter } from './Card';
|
||||
export { CardDefinition } from './definition';
|
||||
|
||||
export type {
|
||||
CardProps,
|
||||
CardHeaderProps,
|
||||
CardBodyProps,
|
||||
CardFooterProps,
|
||||
} from './types';
|
||||
export {
|
||||
CardDefinition,
|
||||
CardHeaderDefinition,
|
||||
CardBodyDefinition,
|
||||
CardFooterDefinition,
|
||||
} from './definition';
|
||||
export type * from './types';
|
||||
|
||||
@@ -14,38 +14,64 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/** @public */
|
||||
export type CardOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the Card component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardProps
|
||||
extends CardOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardHeaderOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardHeader component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardHeaderProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardHeaderProps
|
||||
extends CardHeaderOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardBodyOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardBody component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardBodyProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardBodyProps
|
||||
extends CardBodyOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
/** @public */
|
||||
export type CardFooterOwnProps = {
|
||||
children?: ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Props for the CardFooter component.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface CardFooterProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
export interface CardFooterProps
|
||||
extends CardFooterOwnProps,
|
||||
React.HTMLAttributes<HTMLDivElement> {}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
box-shadow: inset 0 0 0 1px var(--bui-border);
|
||||
border-radius: 2px;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
color: transparent;
|
||||
@@ -63,7 +63,7 @@
|
||||
}
|
||||
|
||||
.bui-Checkbox[data-indeterminate] & {
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
box-shadow: inset 0 0 0 1px var(--bui-border);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: color-mix(in srgb, var(--bui-gray-2) 80%, transparent);
|
||||
background: color-mix(in srgb, var(--bui-bg-neutral-2) 80%, transparent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -43,7 +43,7 @@
|
||||
}
|
||||
|
||||
.bui-Dialog {
|
||||
background: var(--bui-bg-surface-1);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--bui-border);
|
||||
color: var(--bui-fg-primary);
|
||||
|
||||
@@ -24,31 +24,27 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='0'] {
|
||||
background-color: var(--bui-bg-surface-0);
|
||||
.bui-Flex[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='1'] {
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
.bui-Flex[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='2'] {
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
.bui-Flex[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='3'] {
|
||||
background-color: var(--bui-bg-surface-3);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='danger'] {
|
||||
.bui-Flex[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='warning'] {
|
||||
.bui-Flex[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Flex[data-surface='success'] {
|
||||
.bui-Flex[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ export const WithTextTruncate = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
px: '6',
|
||||
py: '4',
|
||||
@@ -252,54 +252,46 @@ export const Surfaces = meta.story({
|
||||
render: args => (
|
||||
<Flex align="center" style={{ flexWrap: 'wrap' }}>
|
||||
<Flex {...args}>Default</Flex>
|
||||
<Flex surface="0" {...args}>
|
||||
Surface 0
|
||||
<Flex bg="neutral-1" {...args}>
|
||||
Neutral 1
|
||||
</Flex>
|
||||
<Flex surface="1" {...args}>
|
||||
Surface 1
|
||||
<Flex bg="neutral-2" {...args}>
|
||||
Neutral 2
|
||||
</Flex>
|
||||
<Flex surface="2" {...args}>
|
||||
Surface 2
|
||||
<Flex bg="neutral-3" {...args}>
|
||||
Neutral 3
|
||||
</Flex>
|
||||
<Flex surface="3" {...args}>
|
||||
Surface 3
|
||||
<Flex bg={{ initial: 'neutral-1', sm: 'neutral-2' }} {...args}>
|
||||
Responsive Bg
|
||||
</Flex>
|
||||
<Flex surface={{ initial: '0', sm: '1' }} {...args}>
|
||||
Responsive Surface
|
||||
<Flex bg="danger" {...args}>
|
||||
Danger
|
||||
</Flex>
|
||||
<Flex surface="danger" {...args}>
|
||||
Surface Danger
|
||||
<Flex bg="warning" {...args}>
|
||||
Warning
|
||||
</Flex>
|
||||
<Flex surface="warning" {...args}>
|
||||
Surface Warning
|
||||
</Flex>
|
||||
<Flex surface="success" {...args}>
|
||||
Surface Success
|
||||
<Flex bg="success" {...args}>
|
||||
Success
|
||||
</Flex>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
export const BgNeutralAuto = meta.story({
|
||||
args: { px: '6', py: '4', gap: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
|
||||
Using surface="auto" automatically increments from the parent surface.
|
||||
This allows components to be reusable without hardcoding surface levels.
|
||||
Using bg="neutral-auto" on Flex auto-increments from the parent context.
|
||||
The first Flex defaults to neutral-1 (no parent), then each nested Flex
|
||||
increments by one, capping at neutral-3.
|
||||
</div>
|
||||
<Flex {...args} surface="0" direction="column">
|
||||
<div>Surface 0 (explicit)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 1)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 2)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (becomes 3)</div>
|
||||
<Flex {...args} surface="auto" direction="column">
|
||||
<div>Surface auto (stays 3 - capped)</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 1 (auto, no parent)</div>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 2 (auto-incremented)</div>
|
||||
<Flex {...args} bg="neutral-auto" direction="column">
|
||||
<div>Neutral 3 (auto-incremented, capped)</div>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -20,24 +20,20 @@ import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { FlexDefinition } from './definition';
|
||||
import styles from './Flex.module.css';
|
||||
import { SurfaceProvider, useSurface } from '../../hooks/useSurface';
|
||||
import { BgProvider, useBgProvider } from '../../hooks/useBg';
|
||||
|
||||
/** @public */
|
||||
export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
// Resolve the surface this Flex creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(FlexDefinition, {
|
||||
gap: '4',
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -54,8 +50,8 @@ export const Flex = forwardRef<HTMLDivElement, FlexProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
|
||||
@@ -45,6 +45,13 @@ export const FlexDefinition = {
|
||||
'direction',
|
||||
],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Responsive, Space, SpaceProps, Surface } from '../../types';
|
||||
import type { Responsive, Space, SpaceProps, ProviderBg } from '../../types';
|
||||
|
||||
/** @public */
|
||||
export interface FlexProps extends SpaceProps {
|
||||
@@ -25,5 +25,5 @@ export interface FlexProps extends SpaceProps {
|
||||
direction?: Responsive<'row' | 'column' | 'row-reverse' | 'column-reverse'>;
|
||||
className?: string;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
@@ -21,38 +21,33 @@
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='0'],
|
||||
.bui-GridItem[data-surface='0'] {
|
||||
background-color: var(--bui-bg-surface-0);
|
||||
.bui-Grid[data-bg='neutral-1'],
|
||||
.bui-GridItem[data-bg='neutral-1'] {
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='1'],
|
||||
.bui-GridItem[data-surface='1'] {
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
.bui-Grid[data-bg='neutral-2'],
|
||||
.bui-GridItem[data-bg='neutral-2'] {
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='2'],
|
||||
.bui-GridItem[data-surface='2'] {
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
.bui-Grid[data-bg='neutral-3'],
|
||||
.bui-GridItem[data-bg='neutral-3'] {
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='3'],
|
||||
.bui-GridItem[data-surface='3'] {
|
||||
background-color: var(--bui-bg-surface-3);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='danger'],
|
||||
.bui-GridItem[data-surface='danger'] {
|
||||
.bui-Grid[data-bg='danger'],
|
||||
.bui-GridItem[data-bg='danger'] {
|
||||
background-color: var(--bui-bg-danger);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='warning'],
|
||||
.bui-GridItem[data-surface='warning'] {
|
||||
.bui-Grid[data-bg='warning'],
|
||||
.bui-GridItem[data-bg='warning'] {
|
||||
background-color: var(--bui-bg-warning);
|
||||
}
|
||||
|
||||
.bui-Grid[data-surface='success'],
|
||||
.bui-GridItem[data-surface='success'] {
|
||||
.bui-Grid[data-bg='success'],
|
||||
.bui-GridItem[data-bg='success'] {
|
||||
background-color: var(--bui-bg-success);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,78 +106,70 @@ export const RowAndColumns = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: { px: '6', py: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<Flex style={{ flexWrap: 'wrap' }}>
|
||||
<Grid.Root {...args} surface="0">
|
||||
Surface 0
|
||||
<Grid.Root {...args} bg="neutral-1">
|
||||
Neutral 1
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="1">
|
||||
Surface 1
|
||||
<Grid.Root {...args} bg="neutral-2">
|
||||
Neutral 2
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="2">
|
||||
Surface 2
|
||||
<Grid.Root {...args} bg="neutral-3">
|
||||
Neutral 3
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="3">
|
||||
Surface 3
|
||||
<Grid.Root {...args} bg={{ initial: 'neutral-1', sm: 'neutral-2' }}>
|
||||
Responsive Bg
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface={{ initial: '0', sm: '1' }}>
|
||||
Responsive Surface
|
||||
<Grid.Root {...args} bg="danger">
|
||||
Danger
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="danger">
|
||||
Surface Danger
|
||||
<Grid.Root {...args} bg="warning">
|
||||
Warning
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="warning">
|
||||
Surface Warning
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args} surface="success">
|
||||
Surface Success
|
||||
<Grid.Root {...args} bg="success">
|
||||
Success
|
||||
</Grid.Root>
|
||||
</Flex>
|
||||
<Flex style={{ flexWrap: 'wrap' }}>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="0" style={{ padding: '4px' }}>
|
||||
Surface 0
|
||||
<Grid.Item bg="neutral-1" style={{ padding: '4px' }}>
|
||||
Neutral 1
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="1" style={{ padding: '4px' }}>
|
||||
Surface 1
|
||||
<Grid.Item bg="neutral-2" style={{ padding: '4px' }}>
|
||||
Neutral 2
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="2" style={{ padding: '4px' }}>
|
||||
Surface 2
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="3" style={{ padding: '4px' }}>
|
||||
Surface 3
|
||||
<Grid.Item bg="neutral-3" style={{ padding: '4px' }}>
|
||||
Neutral 3
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item
|
||||
surface={{ initial: '0', sm: '1' }}
|
||||
bg={{ initial: 'neutral-1', sm: 'neutral-2' }}
|
||||
style={{ padding: '4px' }}
|
||||
>
|
||||
Responsive Surface
|
||||
Responsive Bg
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="danger" style={{ padding: '4px' }}>
|
||||
Surface Danger
|
||||
<Grid.Item bg="danger" style={{ padding: '4px' }}>
|
||||
Danger
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="warning" style={{ padding: '4px' }}>
|
||||
Surface Warning
|
||||
<Grid.Item bg="warning" style={{ padding: '4px' }}>
|
||||
Warning
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
<Grid.Root {...args}>
|
||||
<Grid.Item surface="success" style={{ padding: '4px' }}>
|
||||
Surface Success
|
||||
<Grid.Item bg="success" style={{ padding: '4px' }}>
|
||||
Success
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
</Flex>
|
||||
@@ -185,23 +177,21 @@ export const Surfaces = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const SurfacesAutoIncrement = meta.story({
|
||||
export const BgNeutralAuto = meta.story({
|
||||
args: { px: '6', py: '4', columns: '2', gap: '4' },
|
||||
render: args => (
|
||||
<Flex direction="column">
|
||||
<div style={{ maxWidth: '600px', marginBottom: '16px' }}>
|
||||
Using surface="auto" automatically increments from the parent surface.
|
||||
Each Grid.Item with auto will be one level above its Grid.Root parent.
|
||||
Grid is a layout primitive and is transparent to the bg system by
|
||||
default. Only an explicit bg prop establishes a new bg level. Nested
|
||||
grids without a bg prop inherit the parent context unchanged.
|
||||
</div>
|
||||
<Grid.Root {...args} surface="0">
|
||||
<Grid.Item>Surface 0 (Grid.Root)</Grid.Item>
|
||||
<Grid.Item surface="auto">Surface auto (becomes 1)</Grid.Item>
|
||||
<Grid.Root {...args} bg="neutral-1">
|
||||
<Grid.Item>Neutral 1 (Grid.Root)</Grid.Item>
|
||||
<Grid.Item>
|
||||
<Grid.Root {...args} surface="auto">
|
||||
<Grid.Item>Nested: Surface auto (becomes 1)</Grid.Item>
|
||||
<Grid.Item surface="auto">
|
||||
Nested: Surface auto (becomes 2)
|
||||
</Grid.Item>
|
||||
<Grid.Root {...args} bg="neutral-2">
|
||||
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
|
||||
<Grid.Item>Nested: neutral-2 (explicit)</Grid.Item>
|
||||
</Grid.Root>
|
||||
</Grid.Item>
|
||||
</Grid.Root>
|
||||
|
||||
@@ -20,24 +20,20 @@ import type { GridItemProps, GridProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { GridDefinition, GridItemDefinition } from './definition';
|
||||
import styles from './Grid.module.css';
|
||||
import { SurfaceProvider, useSurface } from '../../hooks/useSurface';
|
||||
import { BgProvider, useBgProvider } from '../../hooks/useBg';
|
||||
|
||||
const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
// Resolve the surface this Grid creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(GridDefinition, {
|
||||
columns: 'auto',
|
||||
gap: '4',
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -54,27 +50,23 @@ const GridRoot = forwardRef<HTMLDivElement, GridProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
});
|
||||
|
||||
const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
|
||||
// Resolve the surface this GridItem creates for its children
|
||||
// Using 'surface' parameter = container behavior (auto increments)
|
||||
const { surface: resolvedSurface } = useSurface({
|
||||
surface: props.surface,
|
||||
});
|
||||
const { bg: resolvedBg } = useBgProvider(props.bg);
|
||||
|
||||
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
|
||||
useStyles(GridItemDefinition, {
|
||||
...props,
|
||||
surface: resolvedSurface, // Use resolved surface for data attribute
|
||||
bg: resolvedBg, // Use resolved bg for data attribute
|
||||
});
|
||||
|
||||
const { className, surface, ...rest } = cleanedProps;
|
||||
const { className, bg, ...rest } = cleanedProps;
|
||||
|
||||
const content = (
|
||||
<div
|
||||
@@ -91,8 +83,8 @@ const GridItem = forwardRef<HTMLDivElement, GridItemProps>((props, ref) => {
|
||||
/>
|
||||
);
|
||||
|
||||
return resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>{content}</SurfaceProvider>
|
||||
return resolvedBg ? (
|
||||
<BgProvider bg={resolvedBg}>{content}</BgProvider>
|
||||
) : (
|
||||
content
|
||||
);
|
||||
|
||||
@@ -43,7 +43,14 @@ export const GridDefinition = {
|
||||
'py',
|
||||
],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -57,6 +64,13 @@ export const GridItemDefinition = {
|
||||
},
|
||||
utilityProps: ['colSpan', 'colEnd', 'colStart', 'rowSpan'],
|
||||
dataAttributes: {
|
||||
surface: ['0', '1', '2', '3', 'danger', 'warning', 'success'] as const,
|
||||
bg: [
|
||||
'neutral-1',
|
||||
'neutral-2',
|
||||
'neutral-3',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -19,7 +19,7 @@ import type {
|
||||
SpaceProps,
|
||||
Responsive,
|
||||
Columns,
|
||||
Surface,
|
||||
ProviderBg,
|
||||
} from '../../types';
|
||||
|
||||
/** @public */
|
||||
@@ -29,7 +29,7 @@ export interface GridProps extends SpaceProps {
|
||||
columns?: Responsive<Columns>;
|
||||
gap?: Responsive<Space>;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
@@ -41,5 +41,5 @@ export interface GridItemProps {
|
||||
colStart?: Responsive<Columns>;
|
||||
rowSpan?: Responsive<Columns>;
|
||||
style?: React.CSSProperties;
|
||||
surface?: Responsive<Surface>;
|
||||
bg?: Responsive<ProviderBg>;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
height: 16px;
|
||||
background-color: var(--bui-bg-surface-0);
|
||||
background-color: var(--bui-bg-neutral-0);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
padding-inline: var(--bui-space-5);
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
color: var(--bui-fg-primary);
|
||||
@@ -93,6 +93,6 @@
|
||||
margin-bottom: var(--bui-space-4);
|
||||
padding-inline: var(--bui-space-3);
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export const HeaderPage = (props: HeaderPageProps) => {
|
||||
color="secondary"
|
||||
truncate
|
||||
style={{ maxWidth: '240px' }}
|
||||
standalone
|
||||
>
|
||||
{breadcrumb.label}
|
||||
</Link>
|
||||
|
||||
@@ -22,9 +22,14 @@
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
text-decoration-line: none;
|
||||
display: inline-block;
|
||||
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-thickness: min(2px, max(1px, 0.05em));
|
||||
text-underline-offset: calc(0.025em + 2px);
|
||||
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
|
||||
|
||||
&:hover {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
@@ -102,9 +107,25 @@
|
||||
color: var(--bui-fg-success);
|
||||
}
|
||||
|
||||
.bui-Link[data-color='info'] {
|
||||
color: var(--bui-fg-info);
|
||||
}
|
||||
|
||||
.bui-Link[data-truncate] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bui-Link[data-standalone] {
|
||||
text-decoration-line: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration-line: underline;
|
||||
text-decoration-style: solid;
|
||||
text-decoration-thickness: min(2px, max(1px, 0.05em));
|
||||
text-underline-offset: calc(0.025em + 2px);
|
||||
text-decoration-color: color-mix(in srgb, currentColor 30%, transparent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +102,12 @@ export const AllColors = meta.story({
|
||||
color="success"
|
||||
children="I am success"
|
||||
/>
|
||||
<Link
|
||||
href="https://ui.backstage.io"
|
||||
variant="title-small"
|
||||
color="info"
|
||||
children="I am info"
|
||||
/>
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
@@ -235,6 +241,25 @@ export const Truncate = meta.story({
|
||||
},
|
||||
});
|
||||
|
||||
export const Standalone = meta.story({
|
||||
args: {
|
||||
href: '/',
|
||||
children: 'Standalone link (no underline by default)',
|
||||
standalone: true,
|
||||
},
|
||||
});
|
||||
|
||||
export const StandaloneComparison = meta.story({
|
||||
render: () => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text>Default link (underline by default):</Text>
|
||||
<Link href="/" children="Sign up for Backstage" />
|
||||
<Text>Standalone link (underline on hover only):</Text>
|
||||
<Link href="/" standalone children="Sign up for Backstage" />
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
export const Responsive = meta.story({
|
||||
args: {
|
||||
...Default.input.args,
|
||||
@@ -244,27 +269,3 @@ export const Responsive = meta.story({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const Playground = meta.story({
|
||||
args: {
|
||||
...Default.input.args,
|
||||
},
|
||||
render: args => (
|
||||
<Flex gap="4" direction="column">
|
||||
<Text>Title X Small</Text>
|
||||
<Link variant="title-x-small" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Body X Small</Text>
|
||||
<Link variant="body-x-small" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Body Small</Text>
|
||||
<Link variant="body-small" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Body Medium</Text>
|
||||
<Link variant="body-medium" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Body Large</Text>
|
||||
<Link variant="body-large" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Title Small</Text>
|
||||
<Link variant="title-small" style={{ maxWidth: '600px' }} {...args} />
|
||||
<Text>Title Medium</Text>
|
||||
<Link variant="title-medium" style={{ maxWidth: '600px' }} {...args} />
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -44,6 +44,7 @@ const LinkInternal = forwardRef<HTMLAnchorElement, LinkProps>((props, ref) => {
|
||||
weight,
|
||||
color,
|
||||
truncate,
|
||||
standalone,
|
||||
slot,
|
||||
...restProps
|
||||
} = cleanedProps;
|
||||
|
||||
@@ -27,7 +27,15 @@ export const LinkDefinition = {
|
||||
dataAttributes: {
|
||||
variant: ['subtitle', 'body', 'caption', 'label'] as const,
|
||||
weight: ['regular', 'bold'] as const,
|
||||
color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const,
|
||||
color: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
'info',
|
||||
] as const,
|
||||
truncate: [true, false] as const,
|
||||
standalone: [true, false] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -33,6 +33,7 @@ export interface LinkProps extends AriaLinkProps {
|
||||
| TextColorStatus
|
||||
| Partial<Record<Breakpoint, TextColors | TextColorStatus>>;
|
||||
truncate?: boolean;
|
||||
standalone?: boolean;
|
||||
|
||||
// This is used to set the title attribute on the link
|
||||
title?: string;
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
box-shadow: var(--bui-shadow);
|
||||
border: 1px solid var(--bui-border);
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-surface-1);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
color: var(--bui-fg-primary);
|
||||
outline: none;
|
||||
transition: transform 200ms, opacity 200ms;
|
||||
@@ -70,12 +70,12 @@
|
||||
display: block;
|
||||
|
||||
&[data-focused] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
&[data-open] .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@
|
||||
display: block;
|
||||
|
||||
&:hover .bui-MenuItemWrapper {
|
||||
background: var(--bui-bg-surface-2);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
color: var(--bui-fg-primary);
|
||||
}
|
||||
|
||||
@@ -195,7 +195,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
height: 2rem;
|
||||
|
||||
&[data-empty] {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
align-items: center;
|
||||
border-radius: var(--bui-radius-2);
|
||||
border: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
|
||||
|
||||
&[data-size='small'] {
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
.bui-Popover {
|
||||
box-shadow: var(--bui-shadow);
|
||||
border-radius: var(--bui-radius-3);
|
||||
background: var(--bui-bg-surface-1);
|
||||
border: 1px solid var(--bui-gray-3);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
border: 1px solid var(--bui-border);
|
||||
forced-color-adjust: none;
|
||||
outline: none;
|
||||
max-height: inherit;
|
||||
@@ -90,11 +90,11 @@
|
||||
elements in order to guarantee that the stroke is
|
||||
always overlaying a consistent color. */
|
||||
path:nth-child(1) {
|
||||
fill: var(--bui-bg-surface-1);
|
||||
fill: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
path:nth-child(2) {
|
||||
fill: var(--bui-gray-3);
|
||||
fill: var(--bui-fg-secondary);
|
||||
}
|
||||
|
||||
/* The arrow svg overlaps the popover by 2px, so we
|
||||
@@ -122,20 +122,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.bui-Popover {
|
||||
background: var(--bui-bg-surface-2);
|
||||
border: 1px solid var(--bui-gray-4);
|
||||
}
|
||||
[data-theme='dark'] .bui-Popover {
|
||||
background: var(--bui-bg-neutral-2);
|
||||
border: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-PopoverArrow {
|
||||
svg path:nth-child(1) {
|
||||
fill: var(--bui-bg-surface-2);
|
||||
}
|
||||
[data-theme='dark'] .bui-PopoverArrow svg path:nth-child(1) {
|
||||
fill: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
svg path:nth-child(2) {
|
||||
fill: var(--bui-gray-4);
|
||||
}
|
||||
}
|
||||
[data-theme='dark'] .bui-PopoverArrow svg path:nth-child(2) {
|
||||
fill: var(--bui-fg-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
height: 1rem;
|
||||
box-sizing: border-box;
|
||||
border: 0.125rem solid var(--bui-border);
|
||||
background: var(--bui-gray-1);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
border-radius: var(--bui-radius-full);
|
||||
transition: all 200ms;
|
||||
flex-shrink: 0;
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
align-items: center;
|
||||
border-radius: var(--bui-radius-2);
|
||||
border: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
transition: border-color 0.2s ease-in-out, outline-color 0.2s ease-in-out;
|
||||
|
||||
&[data-size='small'] {
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--bui-radius-3);
|
||||
border: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -160,7 +160,7 @@
|
||||
inset-block: 0;
|
||||
inset-inline: var(--bui-space-1);
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-surface-2);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
@layer components {
|
||||
.bui-Skeleton {
|
||||
animation: var(--bui-animate-pulse);
|
||||
background-color: var(--bui-bg-surface-2);
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
border-radius: var(--bui-radius-2);
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
color: var(--bui-fg-primary);
|
||||
cursor: pointer;
|
||||
|
||||
&[data-disabled] {
|
||||
cursor: not-allowed;
|
||||
color: var(--bui-fg-disabled);
|
||||
}
|
||||
|
||||
&[data-pressed] .bui-SwitchIndicator {
|
||||
&:before {
|
||||
background: var(--bui-fg-solid);
|
||||
@@ -45,7 +50,7 @@
|
||||
|
||||
&[data-pressed] {
|
||||
.indicator {
|
||||
background: var(--bui-gray-3);
|
||||
background: var(--bui-bg-neutral-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,7 +65,7 @@
|
||||
width: 2rem;
|
||||
height: 1.143rem;
|
||||
border: 2px;
|
||||
background: var(--bui-gray-3);
|
||||
background: var(--bui-bg-neutral-3);
|
||||
border-radius: 1.143rem;
|
||||
transition: all 200ms;
|
||||
|
||||
|
||||
@@ -137,13 +137,26 @@ export function Table<T extends TableItem>({
|
||||
data !== undefined,
|
||||
);
|
||||
|
||||
const manualColumnSizing = columnConfig.some(
|
||||
col =>
|
||||
col.width != null ||
|
||||
col.minWidth != null ||
|
||||
col.maxWidth != null ||
|
||||
col.defaultWidth != null,
|
||||
);
|
||||
|
||||
const wrapResizable = manualColumnSizing
|
||||
? (elem: React.ReactNode) => (
|
||||
<ResizableTableContainer>{elem}</ResizableTableContainer>
|
||||
)
|
||||
: (elem: React.ReactNode) => <>{elem}</>;
|
||||
|
||||
return (
|
||||
<div className={className} style={style}>
|
||||
<VisuallyHidden aria-live="polite" id={liveRegionId}>
|
||||
{liveRegionLabel}
|
||||
</VisuallyHidden>
|
||||
|
||||
<ResizableTableContainer>
|
||||
{wrapResizable(
|
||||
<TableRoot
|
||||
selectionMode={selectionMode}
|
||||
selectionBehavior={selectionBehavior}
|
||||
@@ -176,6 +189,7 @@ export function Table<T extends TableItem>({
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
items={data}
|
||||
dependencies={[visibleColumns]}
|
||||
renderEmptyState={
|
||||
emptyState ? () => <Flex p="3">{emptyState}</Flex> : undefined
|
||||
}
|
||||
@@ -206,8 +220,8 @@ export function Table<T extends TableItem>({
|
||||
);
|
||||
}}
|
||||
</TableBody>
|
||||
</TableRoot>
|
||||
</ResizableTableContainer>
|
||||
</TableRoot>,
|
||||
)}
|
||||
{pagination.type === 'page' && (
|
||||
<TablePagination
|
||||
pageSize={pagination.pageSize}
|
||||
|
||||
@@ -96,15 +96,25 @@ export interface CursorResponse<T> {
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseTableCompleteOptions<T extends TableItem, TFilter = unknown>
|
||||
extends QueryOptions<TFilter> {
|
||||
export type UseTableCompleteOptions<
|
||||
T extends TableItem,
|
||||
TFilter = unknown,
|
||||
> = QueryOptions<TFilter> & {
|
||||
mode: 'complete';
|
||||
getData: () => T[] | Promise<T[]>;
|
||||
paginationOptions?: PaginationOptions;
|
||||
sortFn?: (data: T[], sort: SortDescriptor) => T[];
|
||||
filterFn?: (data: T[], filter: TFilter) => T[];
|
||||
searchFn?: (data: T[], search: string) => T[];
|
||||
}
|
||||
} & (
|
||||
| {
|
||||
data: T[] | undefined;
|
||||
getData?: never;
|
||||
}
|
||||
| {
|
||||
data?: never;
|
||||
getData: () => T[] | Promise<T[]>;
|
||||
}
|
||||
);
|
||||
|
||||
/** @public */
|
||||
export interface UseTableOffsetOptions<T extends TableItem, TFilter = unknown>
|
||||
|
||||
@@ -30,7 +30,8 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
query: QueryState<TFilter>,
|
||||
): PaginationResult<T> & { reload: () => void } {
|
||||
const {
|
||||
getData: getDataProp,
|
||||
data,
|
||||
getData: getDataProp = () => [],
|
||||
paginationOptions = {},
|
||||
sortFn,
|
||||
filterFn,
|
||||
@@ -43,7 +44,7 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
const { sort, filter, search } = query;
|
||||
|
||||
const [items, setItems] = useState<T[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(!data);
|
||||
const [error, setError] = useState<Error | undefined>(undefined);
|
||||
const [loadCount, setLoadCount] = useState(0);
|
||||
|
||||
@@ -52,6 +53,10 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
|
||||
// Load data on mount and when loadCount changes (reload trigger)
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
setIsLoading(true);
|
||||
setError(undefined);
|
||||
@@ -75,7 +80,7 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [getData, loadCount]);
|
||||
}, [data, getData, loadCount]);
|
||||
|
||||
// Reset offset when query changes (query object is memoized)
|
||||
const prevQueryRef = useRef(query);
|
||||
@@ -86,9 +91,11 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
}
|
||||
}, [query]);
|
||||
|
||||
const resolvedItems = useMemo(() => data ?? items, [data, items]);
|
||||
|
||||
// Process data client-side (filter, search, sort)
|
||||
const processedData = useMemo(() => {
|
||||
let result = [...items];
|
||||
let result = [...resolvedItems];
|
||||
if (filter !== undefined && filterFn) {
|
||||
result = filterFn(result, filter);
|
||||
}
|
||||
@@ -99,7 +106,7 @@ export function useCompletePagination<T extends TableItem, TFilter>(
|
||||
result = sortFn(result, sort);
|
||||
}
|
||||
return result;
|
||||
}, [items, sort, filter, search, filterFn, searchFn, sortFn]);
|
||||
}, [resolvedItems, sort, filter, search, filterFn, searchFn, sortFn]);
|
||||
|
||||
const totalCount = processedData.length;
|
||||
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
top: calc(var(--hovered-tab-top) + 4px);
|
||||
width: var(--hovered-tab-width);
|
||||
height: calc(var(--hovered-tab-height) - 8px);
|
||||
background-color: var(--bui-gray-2);
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
border-radius: 4px;
|
||||
opacity: var(--hovered-tab-opacity);
|
||||
transition: left var(--hovered-transition-duration) ease-out,
|
||||
|
||||
@@ -135,9 +135,10 @@ export const Tabs = (props: TabsProps) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Has routed tabs but none are active - controlled mode with no selection
|
||||
// Has routed tabs but none are active - use empty string for no selection
|
||||
// (React Aria has a bug with null that causes infinite loops)
|
||||
if (activeTabs.size === 0) {
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
|
||||
let selectedId: string | null = null;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
.bui-Tag {
|
||||
color: var(--bui-fg-primary);
|
||||
background-color: var(--bui-gray-2);
|
||||
background-color: var(--bui-bg-neutral-2);
|
||||
border-radius: var(--bui-radius-2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
.bui-Tag[data-hovered] {
|
||||
background-color: var(--bui-gray-3);
|
||||
background-color: var(--bui-bg-neutral-3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
}
|
||||
|
||||
.bui-Tag[data-selected] {
|
||||
box-shadow: inset 0 0 0 1px var(--bui-gray-8);
|
||||
box-shadow: inset 0 0 0 1px var(--bui-fg-secondary);
|
||||
}
|
||||
|
||||
.bui-Tag[data-disabled] {
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
Tag as ReactAriaTag,
|
||||
Button as ReactAriaButton,
|
||||
} from 'react-aria-components';
|
||||
import type { ReactNode } from 'react';
|
||||
import { forwardRef, type PropsWithRef, type ReactNode, type Ref } from 'react';
|
||||
import { RiCloseCircleLine } from '@remixicon/react';
|
||||
import clsx from 'clsx';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
@@ -64,47 +64,50 @@ export const TagGroup = <T extends object>(props: TagGroupProps<T>) => {
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const Tag = (props: TagProps) => {
|
||||
const { classNames, cleanedProps } = useStyles(TagGroupDefinition, {
|
||||
size: 'small',
|
||||
...props,
|
||||
});
|
||||
const { children, className, icon, size, href, ...rest } = cleanedProps;
|
||||
const textValue = typeof children === 'string' ? children : undefined;
|
||||
export const Tag = forwardRef(
|
||||
(props: PropsWithRef<TagProps>, ref: Ref<HTMLDivElement>) => {
|
||||
const { classNames, cleanedProps } = useStyles(TagGroupDefinition, {
|
||||
size: 'small',
|
||||
...props,
|
||||
});
|
||||
const { children, className, icon, size, href, ...rest } = cleanedProps;
|
||||
const textValue = typeof children === 'string' ? children : undefined;
|
||||
|
||||
useRoutingRegistrationEffect(href);
|
||||
useRoutingRegistrationEffect(href);
|
||||
|
||||
return (
|
||||
<ReactAriaTag
|
||||
textValue={textValue}
|
||||
className={clsx(classNames.tag, styles[classNames.tag], className)}
|
||||
data-size={size}
|
||||
href={href}
|
||||
{...rest}
|
||||
>
|
||||
{({ allowsRemoving }) => (
|
||||
<>
|
||||
{icon && (
|
||||
<span
|
||||
className={clsx(classNames.tagIcon, styles[classNames.tagIcon])}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
{children as ReactNode}
|
||||
{allowsRemoving && (
|
||||
<ReactAriaButton
|
||||
className={clsx(
|
||||
classNames.tagRemoveButton,
|
||||
styles[classNames.tagRemoveButton],
|
||||
)}
|
||||
slot="remove"
|
||||
>
|
||||
<RiCloseCircleLine size={16} />
|
||||
</ReactAriaButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ReactAriaTag>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<ReactAriaTag
|
||||
ref={ref}
|
||||
textValue={textValue}
|
||||
className={clsx(classNames.tag, styles[classNames.tag], className)}
|
||||
data-size={size}
|
||||
href={href}
|
||||
{...rest}
|
||||
>
|
||||
{({ allowsRemoving }) => (
|
||||
<>
|
||||
{icon && (
|
||||
<span
|
||||
className={clsx(classNames.tagIcon, styles[classNames.tagIcon])}
|
||||
>
|
||||
{icon}
|
||||
</span>
|
||||
)}
|
||||
{children as ReactNode}
|
||||
{allowsRemoving && (
|
||||
<ReactAriaButton
|
||||
className={clsx(
|
||||
classNames.tagRemoveButton,
|
||||
styles[classNames.tagRemoveButton],
|
||||
)}
|
||||
slot="remove"
|
||||
>
|
||||
<RiCloseCircleLine size={16} />
|
||||
</ReactAriaButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</ReactAriaTag>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -91,6 +91,10 @@
|
||||
color: var(--bui-fg-success);
|
||||
}
|
||||
|
||||
.bui-Text[data-color='info'] {
|
||||
color: var(--bui-fg-info);
|
||||
}
|
||||
|
||||
.bui-Text[data-truncate] {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -101,6 +101,7 @@ export const AllColors = meta.story({
|
||||
<Text {...args} color="danger" children="I am danger" />
|
||||
<Text {...args} color="warning" children="I am warning" />
|
||||
<Text {...args} color="success" children="I am success" />
|
||||
<Text {...args} color="info" children="I am info" />
|
||||
</Flex>
|
||||
),
|
||||
});
|
||||
|
||||
@@ -27,7 +27,14 @@ export const TextDefinition = {
|
||||
dataAttributes: {
|
||||
variant: ['subtitle', 'body', 'caption', 'label'] as const,
|
||||
weight: ['regular', 'bold'] as const,
|
||||
color: ['primary', 'secondary', 'danger', 'warning', 'success'] as const,
|
||||
color: [
|
||||
'primary',
|
||||
'secondary',
|
||||
'danger',
|
||||
'warning',
|
||||
'success',
|
||||
'info',
|
||||
] as const,
|
||||
truncate: [true, false] as const,
|
||||
},
|
||||
} as const satisfies ComponentDefinition;
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
padding: 0 var(--bui-space-3);
|
||||
border-radius: var(--bui-radius-2);
|
||||
border: 1px solid var(--bui-border);
|
||||
background-color: var(--bui-bg-surface-1);
|
||||
background-color: var(--bui-bg-neutral-1);
|
||||
font-size: var(--bui-font-size-3);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-regular);
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
gap: var(--bui-space-1_5);
|
||||
border: none;
|
||||
border-radius: var(--bui-radius-2);
|
||||
background: var(--bui-bg-surface-1);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
color: var(--bui-fg-primary);
|
||||
font-family: var(--bui-font-regular);
|
||||
font-weight: var(--bui-font-weight-bold);
|
||||
@@ -41,16 +41,16 @@
|
||||
}
|
||||
|
||||
&:not([data-selected])[data-hovered] {
|
||||
background: var(--bui-bg-surface-2);
|
||||
background: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
&[data-disabled] {
|
||||
background: var(--bui-bg-neutral-on-surface-0-disabled);
|
||||
background: var(--bui-bg-neutral-1-disabled);
|
||||
color: var(--bui-fg-disabled);
|
||||
}
|
||||
|
||||
&[data-disabled][data-hovered] {
|
||||
background: var(--bui-bg-neutral-on-surface-0-disabled);
|
||||
background: var(--bui-bg-neutral-1-disabled);
|
||||
}
|
||||
|
||||
&[data-disabled][data-selected] {
|
||||
|
||||
@@ -44,7 +44,7 @@ export const Default = meta.story({
|
||||
},
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
children: 'Toggle',
|
||||
},
|
||||
@@ -64,26 +64,20 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4">
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex align="center" bg="neutral-2" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4">
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex align="center" bg="neutral-3" p="4">
|
||||
<ToggleButton>Toggle</ToggleButton>
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -21,7 +21,6 @@ import type { ToggleButtonProps } from './types';
|
||||
import { useStyles } from '../../hooks/useStyles';
|
||||
import { ToggleButtonDefinition } from './definition';
|
||||
import styles from './ToggleButton.module.css';
|
||||
import { useSurface } from '../../hooks/useSurface';
|
||||
|
||||
/** @public */
|
||||
export const ToggleButton = forwardRef(
|
||||
@@ -34,17 +33,13 @@ export const ToggleButton = forwardRef(
|
||||
},
|
||||
);
|
||||
|
||||
const { children, className, iconStart, iconEnd, onSurface, ...rest } =
|
||||
cleanedProps;
|
||||
|
||||
const { surface } = useSurface({ onSurface });
|
||||
const { children, className, iconStart, iconEnd, ...rest } = cleanedProps;
|
||||
|
||||
return (
|
||||
<AriaToggleButton
|
||||
className={clsx(classNames.root, styles[classNames.root], className)}
|
||||
ref={ref}
|
||||
{...dataAttributes}
|
||||
{...(typeof surface === 'string' ? { 'data-on-surface': surface } : {})}
|
||||
{...rest}
|
||||
>
|
||||
{renderProps => {
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import type { Breakpoint } from '../..';
|
||||
import type { ReactElement } from 'react';
|
||||
import type { ToggleButtonProps as AriaToggleButtonProps } from 'react-aria-components';
|
||||
import type { Responsive, Surface } from '../../types';
|
||||
|
||||
/**
|
||||
* Properties for {@link ToggleButton}
|
||||
@@ -28,6 +27,4 @@ export interface ToggleButtonProps extends AriaToggleButtonProps {
|
||||
size?: 'small' | 'medium' | Partial<Record<Breakpoint, 'small' | 'medium'>>;
|
||||
iconStart?: ReactElement;
|
||||
iconEnd?: ReactElement;
|
||||
/** Surface the toggle button is placed on. Defaults to context surface if available */
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ export const MultipleSelection = meta.story({
|
||||
),
|
||||
});
|
||||
|
||||
export const Surfaces = meta.story({
|
||||
export const Backgrounds = meta.story({
|
||||
args: {
|
||||
selectionMode: 'single',
|
||||
defaultSelectedKeys: ['option1'],
|
||||
@@ -99,8 +99,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 0</Text>
|
||||
<Flex align="center" surface="0" p="4" gap="4">
|
||||
<Text>On Neutral 1</Text>
|
||||
<Flex align="center" bg="neutral-1" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
@@ -112,8 +112,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 1</Text>
|
||||
<Flex align="center" surface="1" p="4" gap="4">
|
||||
<Text>On Neutral 2</Text>
|
||||
<Flex align="center" bg="neutral-2" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
@@ -125,21 +125,8 @@ export const Surfaces = meta.story({
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 2</Text>
|
||||
<Flex align="center" surface="2" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
>
|
||||
<ToggleButton id="option1">Option 1</ToggleButton>
|
||||
<ToggleButton id="option2">Option 2</ToggleButton>
|
||||
<ToggleButton id="option3">Option 3</ToggleButton>
|
||||
</ToggleButtonGroup>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex direction="column" gap="4">
|
||||
<Text>On Surface 3</Text>
|
||||
<Flex align="center" surface="3" p="4" gap="4">
|
||||
<Text>On Neutral 3</Text>
|
||||
<Flex align="center" bg="neutral-3" p="4" gap="4">
|
||||
<ToggleButtonGroup
|
||||
selectionMode="single"
|
||||
defaultSelectedKeys={['option1']}
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
.bui-Tooltip {
|
||||
box-shadow: var(--bui-shadow);
|
||||
border-radius: 4px;
|
||||
background: var(--bui-bg-surface-1);
|
||||
border: 1px solid var(--bui-gray-3);
|
||||
background: var(--bui-bg-neutral-1);
|
||||
border: 1px solid var(--bui-border);
|
||||
forced-color-adjust: none;
|
||||
outline: none;
|
||||
padding: var(--bui-space-2) var(--bui-space-3);
|
||||
@@ -74,11 +74,11 @@
|
||||
elements in order to guarantee that the stroke is
|
||||
always overlaying a consistent color. */
|
||||
path:nth-child(1) {
|
||||
fill: var(--bui-bg-surface-1);
|
||||
fill: var(--bui-bg-neutral-1);
|
||||
}
|
||||
|
||||
path:nth-child(2) {
|
||||
fill: var(--bui-gray-3);
|
||||
fill: var(--bui-fg-secondary);
|
||||
}
|
||||
|
||||
/* The arrow svg overlaps the tooltip by 2px, so we
|
||||
@@ -106,21 +106,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
[data-theme='dark'] {
|
||||
.bui-Tooltip {
|
||||
background: var(--bui-bg-surface-2);
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--bui-gray-4);
|
||||
}
|
||||
[data-theme='dark'] .bui-Tooltip {
|
||||
background: var(--bui-bg-neutral-2);
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--bui-border);
|
||||
}
|
||||
|
||||
.bui-TooltipArrow {
|
||||
svg path:nth-child(1) {
|
||||
fill: var(--bui-bg-surface-2);
|
||||
}
|
||||
[data-theme='dark'] .bui-TooltipArrow svg path:nth-child(1) {
|
||||
fill: var(--bui-bg-neutral-2);
|
||||
}
|
||||
|
||||
svg path:nth-child(2) {
|
||||
fill: var(--bui-gray-4);
|
||||
}
|
||||
}
|
||||
[data-theme='dark'] .bui-TooltipArrow svg path:nth-child(2) {
|
||||
fill: var(--bui-fg-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
import preview from '../../../../.storybook/preview';
|
||||
import { Box } from '../components/Box';
|
||||
import { Flex } from '../components/Flex';
|
||||
import { Text } from '../components/Text';
|
||||
|
||||
const meta = preview.meta({
|
||||
title: 'Backstage UI/Colors',
|
||||
});
|
||||
|
||||
export const Default = meta.story({
|
||||
render: () => (
|
||||
<div style={{ backgroundColor: 'var(--bui-bg-neutral-0)' }}>
|
||||
<Box p="4" style={{ backgroundColor: 'var(--bui-bg-neutral-1)' }}>
|
||||
<Flex direction="row" gap="4" align="center">
|
||||
<Text>Neutral 1</Text>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-1-hover)' }}
|
||||
>
|
||||
<Text>Hover</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-1-pressed)' }}
|
||||
>
|
||||
<Text>Pressed</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-1-disabled)' }}
|
||||
>
|
||||
<Text style={{ color: 'var(--bui-fg-disabled)' }}>Disabled</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
p="4"
|
||||
mt="4"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-2)' }}
|
||||
>
|
||||
<Flex direction="row" gap="4" align="center">
|
||||
<Text>Neutral 2</Text>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-2-hover)' }}
|
||||
>
|
||||
<Text>Hover</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-2-pressed)' }}
|
||||
>
|
||||
<Text>Pressed</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-2-disabled)' }}
|
||||
>
|
||||
<Text style={{ color: 'var(--bui-fg-disabled)' }}>Disabled</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
p="4"
|
||||
mt="4"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-3)' }}
|
||||
>
|
||||
<Flex direction="row" gap="4" align="center">
|
||||
<Text>Neutral 3</Text>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-3-hover)' }}
|
||||
>
|
||||
<Text>Hover</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-3-pressed)' }}
|
||||
>
|
||||
<Text>Pressed</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-3-disabled)' }}
|
||||
>
|
||||
<Text style={{ color: 'var(--bui-fg-disabled)' }}>
|
||||
Disabled
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Box
|
||||
p="4"
|
||||
mt="4"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-4)' }}
|
||||
>
|
||||
<Flex direction="row" gap="4" align="center">
|
||||
<Text>Neutral 4</Text>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-4-hover)' }}
|
||||
>
|
||||
<Text>Hover</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{ backgroundColor: 'var(--bui-bg-neutral-4-pressed)' }}
|
||||
>
|
||||
<Text>Pressed</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
px="2"
|
||||
py="1"
|
||||
style={{
|
||||
backgroundColor: 'var(--bui-bg-neutral-4-disabled)',
|
||||
}}
|
||||
>
|
||||
<Text style={{ color: 'var(--bui-fg-disabled)' }}>
|
||||
Disabled
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
),
|
||||
});
|
||||
@@ -27,7 +27,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--bui-bg-surface-0);
|
||||
background-color: var(--bui-bg-neutral-0);
|
||||
|
||||
/* Text defaults */
|
||||
color: var(--bui-fg-primary);
|
||||
|
||||
@@ -70,22 +70,6 @@
|
||||
--bui-black: #000000;
|
||||
--bui-white: #ffffff;
|
||||
|
||||
/* Gray scale */
|
||||
--bui-gray-1: #f8f8f8;
|
||||
--bui-gray-2: #ececec;
|
||||
--bui-gray-3: #d9d9d9;
|
||||
--bui-gray-4: #c1c1c1;
|
||||
--bui-gray-5: #9e9e9e;
|
||||
--bui-gray-6: #8c8c8c;
|
||||
--bui-gray-7: #757575;
|
||||
--bui-gray-8: #595959;
|
||||
|
||||
/* Surfaces colors */
|
||||
--bui-bg-surface-0: var(--bui-gray-1);
|
||||
--bui-bg-surface-1: var(--bui-white);
|
||||
--bui-bg-surface-2: var(--bui-gray-1);
|
||||
--bui-bg-surface-3: var(--bui-gray-2);
|
||||
|
||||
/* Solid background colors */
|
||||
--bui-bg-solid: #1f5493;
|
||||
--bui-bg-solid-hover: #163a66;
|
||||
@@ -93,25 +77,27 @@
|
||||
--bui-bg-solid-disabled: #163a66;
|
||||
|
||||
/* Neutral background colors */
|
||||
--bui-bg-neutral-on-surface-0: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-on-surface-0-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-0-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-on-surface-0-disabled: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-0: #f8f8f8;
|
||||
|
||||
--bui-bg-neutral-on-surface-1: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-on-surface-1-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-1-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-on-surface-1-disabled: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-1: #fff;
|
||||
--bui-bg-neutral-1-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-1-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-1-disabled: oklch(0% 0 0 / 6%);
|
||||
|
||||
--bui-bg-neutral-on-surface-2: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-on-surface-2-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-2-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-on-surface-2-disabled: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-2: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-2-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-2-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-2-disabled: oklch(0% 0 0 / 6%);
|
||||
|
||||
--bui-bg-neutral-on-surface-3: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-on-surface-3-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-3-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-3: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-3-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-3-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-3-disabled: oklch(0% 0 0 / 6%);
|
||||
|
||||
--bui-bg-neutral-4: oklch(0% 0 0 / 6%);
|
||||
--bui-bg-neutral-4-hover: oklch(0% 0 0 / 12%);
|
||||
--bui-bg-neutral-4-pressed: oklch(0% 0 0 / 16%);
|
||||
--bui-bg-neutral-4-disabled: oklch(0% 0 0 / 6%);
|
||||
|
||||
/* Status background colors */
|
||||
--bui-bg-danger: #ffe2e2;
|
||||
@@ -121,18 +107,20 @@
|
||||
|
||||
/* Foreground colors */
|
||||
--bui-fg-primary: var(--bui-black);
|
||||
--bui-fg-secondary: var(--bui-gray-7);
|
||||
--bui-fg-link: #1f5493;
|
||||
--bui-fg-link-hover: #1f2d5c;
|
||||
--bui-fg-disabled: #9e9e9e;
|
||||
--bui-fg-secondary: oklch(0% 0 0 / 50%);
|
||||
--bui-fg-disabled: oklch(0% 0 0 / 28%);
|
||||
--bui-fg-solid: var(--bui-white);
|
||||
--bui-fg-solid-disabled: #98a8bc;
|
||||
--bui-fg-tint: #1f5493;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #991919;
|
||||
--bui-fg-warning: #92310a;
|
||||
--bui-fg-success: #116932;
|
||||
--bui-fg-info: #173da6;
|
||||
|
||||
/* Foreground Statuses */
|
||||
--bui-fg-danger-on-bg: #991919;
|
||||
--bui-fg-warning-on-bg: #92310a;
|
||||
--bui-fg-success-on-bg: #116932;
|
||||
--bui-fg-info-on-bg: #173da6;
|
||||
--bui-fg-danger: #ec3b18;
|
||||
--bui-fg-warning: #ef7a32;
|
||||
--bui-fg-success: #1ed760;
|
||||
--bui-fg-info: #0d74ce;
|
||||
|
||||
/* Border colors */
|
||||
--bui-border: rgba(0, 0, 0, 0.1);
|
||||
@@ -158,22 +146,6 @@
|
||||
|
||||
/* Dark theme tokens */
|
||||
[data-theme-mode='dark'] {
|
||||
/* Gray scale */
|
||||
--bui-gray-1: #191919;
|
||||
--bui-gray-2: #242424;
|
||||
--bui-gray-3: #373737;
|
||||
--bui-gray-4: #464646;
|
||||
--bui-gray-5: #575757;
|
||||
--bui-gray-6: #7b7b7b;
|
||||
--bui-gray-7: #9e9e9e;
|
||||
--bui-gray-8: #b4b4b4;
|
||||
|
||||
/* Background Colors */
|
||||
--bui-bg-surface-0: #333333;
|
||||
--bui-bg-surface-1: var(--bui-gray-1);
|
||||
--bui-bg-surface-2: var(--bui-gray-2);
|
||||
--bui-bg-surface-3: var(--bui-gray-3);
|
||||
|
||||
/* Solid background colors */
|
||||
--bui-bg-solid: #9cc9ff;
|
||||
--bui-bg-solid-hover: #83b9fd;
|
||||
@@ -181,25 +153,27 @@
|
||||
--bui-bg-solid-disabled: #1b3d68;
|
||||
|
||||
/* Neutral background colors */
|
||||
--bui-bg-neutral-on-surface-0: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-on-surface-0-hover: oklch(100% 0 0 / 14%);
|
||||
--bui-bg-neutral-on-surface-0-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-on-surface-0-disabled: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-0: #333333;
|
||||
|
||||
--bui-bg-neutral-on-surface-1: oklch(100% 0 0 / 6%);
|
||||
--bui-bg-neutral-on-surface-1-hover: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-on-surface-1-pressed: oklch(100% 0 0 / 16%);
|
||||
--bui-bg-neutral-on-surface-1-disabled: oklch(100% 0 0 / 6%);
|
||||
--bui-bg-neutral-1: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-1-hover: oklch(100% 0 0 / 14%);
|
||||
--bui-bg-neutral-1-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-1-disabled: oklch(100% 0 0 / 10%);
|
||||
|
||||
--bui-bg-neutral-on-surface-2: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-on-surface-2-hover: oklch(100% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-2-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-on-surface-2-disabled: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-2: oklch(100% 0 0 / 6%);
|
||||
--bui-bg-neutral-2-hover: oklch(100% 0 0 / 10%);
|
||||
--bui-bg-neutral-2-pressed: oklch(100% 0 0 / 16%);
|
||||
--bui-bg-neutral-2-disabled: oklch(100% 0 0 / 6%);
|
||||
|
||||
--bui-bg-neutral-on-surface-3: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-on-surface-3-hover: oklch(100% 0 0 / 12%);
|
||||
--bui-bg-neutral-on-surface-3-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-3: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-3-hover: oklch(100% 0 0 / 12%);
|
||||
--bui-bg-neutral-3-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-3-disabled: oklch(100% 0 0 / 8%);
|
||||
|
||||
--bui-bg-neutral-4: oklch(100% 0 0 / 8%);
|
||||
--bui-bg-neutral-4-hover: oklch(100% 0 0 / 12%);
|
||||
--bui-bg-neutral-4-pressed: oklch(100% 0 0 / 20%);
|
||||
--bui-bg-neutral-4-disabled: oklch(100% 0 0 / 8%);
|
||||
|
||||
/* Status background colors */
|
||||
--bui-bg-danger: #300c0c;
|
||||
@@ -209,18 +183,20 @@
|
||||
|
||||
/* Foreground colors */
|
||||
--bui-fg-primary: var(--bui-white);
|
||||
--bui-fg-secondary: var(--bui-gray-7);
|
||||
--bui-fg-link: #9cc9ff;
|
||||
--bui-fg-link-hover: #7eb5f7;
|
||||
--bui-fg-disabled: var(--bui-gray-7);
|
||||
--bui-fg-secondary: oklch(0% 0 0 / 50%);
|
||||
--bui-fg-disabled: oklch(0% 0 0 / 28%);
|
||||
--bui-fg-solid: #101821;
|
||||
--bui-fg-solid-disabled: #6191cc;
|
||||
--bui-fg-tint: #9cc9ff;
|
||||
--bui-fg-tint-disabled: var(--bui-gray-5);
|
||||
--bui-fg-danger: #fca5a5;
|
||||
--bui-fg-warning: #fdba74;
|
||||
--bui-fg-success: #86efac;
|
||||
--bui-fg-info: #a3cfff;
|
||||
|
||||
/* Foreground statuses */
|
||||
--bui-fg-danger-on-bg: #fca5a5;
|
||||
--bui-fg-warning-on-bg: #fdba74;
|
||||
--bui-fg-success-on-bg: #86efac;
|
||||
--bui-fg-info-on-bg: #a3cfff;
|
||||
--bui-fg-danger: #ff5a30;
|
||||
--bui-fg-warning: #ffa057;
|
||||
--bui-fg-success: #1ed760;
|
||||
--bui-fg-info: #70b8ff;
|
||||
|
||||
/* Border colors */
|
||||
--bui-border: rgba(255, 255, 255, 0.12);
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { useContext, ReactNode } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import { ContainerBg, ProviderBg, Responsive } from '../types';
|
||||
import { useBreakpoint } from './useBreakpoint';
|
||||
import { resolveResponsiveValue } from './useDefinition/helpers';
|
||||
|
||||
/** @public */
|
||||
export interface BgContextValue {
|
||||
bg: ContainerBg | undefined;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface BgProviderProps {
|
||||
bg: ContainerBg;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const BgContext = createVersionedContext<{
|
||||
1: BgContextValue;
|
||||
}>('bg-context');
|
||||
|
||||
/**
|
||||
* Increments a neutral bg level by one, capping at 'neutral-3'.
|
||||
* Intent backgrounds (danger, warning, success) pass through unchanged.
|
||||
*
|
||||
* The 'neutral-4' level is reserved for consumer component CSS and is
|
||||
* never set on providers.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function incrementNeutralBg(bg: ContainerBg | undefined): ContainerBg {
|
||||
if (!bg) return 'neutral-1';
|
||||
if (bg === 'neutral-1') return 'neutral-2';
|
||||
if (bg === 'neutral-2') return 'neutral-3';
|
||||
if (bg === 'neutral-3') return 'neutral-3'; // capped at neutral-3
|
||||
// Intent values pass through unchanged
|
||||
return bg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that establishes the bg context for child components.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const BgProvider = ({ bg, children }: BgProviderProps) => {
|
||||
return (
|
||||
<BgContext.Provider value={createVersionedValueMap({ 1: { bg } })}>
|
||||
{children}
|
||||
</BgContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for consumer components (e.g. Button) to read the parent bg context.
|
||||
*
|
||||
* Returns the parent container's bg unchanged. The consumer component's CSS
|
||||
* handles the visual step-up (e.g. on a neutral-1 surface, the consumer
|
||||
* uses neutral-2 tokens via `data-on-bg`).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useBgConsumer(): BgContextValue {
|
||||
const value = useContext(BgContext)?.atVersion(1);
|
||||
return value ?? { bg: undefined };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for provider components (e.g. Box, Card) to resolve and provide bg context.
|
||||
*
|
||||
* **Resolution rules:**
|
||||
*
|
||||
* - `bg` is `undefined` -- transparent, no context change, returns `{ bg: undefined }`.
|
||||
* This is the default for Box, Flex, and Grid (they do **not** auto-increment).
|
||||
* - `bg` is a `ContainerBg` value -- uses that value directly (e.g. `'neutral-1'`).
|
||||
* - `bg` is `'neutral-auto'` -- increments the neutral level from the parent context,
|
||||
* capping at `neutral-3`. Only components that explicitly pass `'neutral-auto'`
|
||||
* (e.g. Card) will auto-increment; it is never implicit.
|
||||
*
|
||||
* **Capping:**
|
||||
*
|
||||
* Provider components cap at `neutral-3`. The `neutral-4` level is **not** a valid
|
||||
* prop value -- it exists only in consumer component CSS (e.g. a Button on a
|
||||
* `neutral-3` surface renders with `neutral-4` tokens via `data-on-bg`).
|
||||
*
|
||||
* The caller is responsible for wrapping children with `BgProvider` when the
|
||||
* resolved bg is defined.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export function useBgProvider(bg?: Responsive<ProviderBg>): BgContextValue {
|
||||
const { breakpoint } = useBreakpoint();
|
||||
const context = useBgConsumer();
|
||||
|
||||
if (bg === undefined) {
|
||||
return { bg: undefined };
|
||||
}
|
||||
|
||||
const resolved = resolveResponsiveValue(bg, breakpoint);
|
||||
|
||||
if (resolved === 'neutral-auto') {
|
||||
return { bg: incrementNeutralBg(context.bg) };
|
||||
}
|
||||
|
||||
return { bg: resolved };
|
||||
}
|
||||
@@ -14,13 +14,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ComponentConfig, SurfacePropsConstraint } from './types';
|
||||
import type { ComponentConfig, BgPropsConstraint } from './types';
|
||||
|
||||
export function defineComponent<P extends Record<string, any>>() {
|
||||
return <
|
||||
const S extends Record<string, string>,
|
||||
const C extends ComponentConfig<P, S>,
|
||||
>(
|
||||
config: C & SurfacePropsConstraint<P, C['surface']>,
|
||||
config: C & BgPropsConstraint<P, C['bg']>,
|
||||
): C => config;
|
||||
}
|
||||
|
||||
@@ -36,25 +36,25 @@ export interface ComponentConfig<
|
||||
propDefs: { [K in keyof P]: PropDefConfig<P[K]> };
|
||||
// readonly for compatibility with const inference from factory
|
||||
utilityProps?: readonly UtilityPropKey[];
|
||||
surface?: 'container' | 'leaf';
|
||||
/**
|
||||
* How this component participates in the bg system.
|
||||
*
|
||||
* - `'provider'` — calls `useBgProvider`, sets `data-bg`, wraps children in `BgProvider`
|
||||
* - `'consumer'` — calls `useBgConsumer`, sets `data-on-bg`
|
||||
*/
|
||||
bg?: 'provider' | 'consumer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Type constraint that validates surface props are present in the props type.
|
||||
* - If surface is 'leaf', P must include 'onSurface'
|
||||
* - If surface is 'container', P must include 'surface'
|
||||
* Type constraint that validates bg props are present in the props type.
|
||||
* - Provider components must include 'bg' in their props
|
||||
* - Consumer components don't need a bg prop
|
||||
*/
|
||||
export type SurfacePropsConstraint<P, Surface> = Surface extends 'leaf'
|
||||
? 'onSurface' extends keyof P
|
||||
export type BgPropsConstraint<P, Bg> = Bg extends 'provider'
|
||||
? 'bg' extends keyof P
|
||||
? {}
|
||||
: {
|
||||
__error: 'Leaf components must include onSurface in props type. Extend LeafProps.';
|
||||
}
|
||||
: Surface extends 'container'
|
||||
? 'surface' extends keyof P
|
||||
? {}
|
||||
: {
|
||||
__error: 'Container components must include surface in props type. Extend ContainerProps.';
|
||||
__error: 'Bg provider components must include bg in props type.';
|
||||
}
|
||||
: {};
|
||||
|
||||
@@ -81,12 +81,19 @@ type ResolvedOwnProps<
|
||||
[K in keyof PropDefs & keyof P]: ResolvePropType<P[K], PropDefs[K]>;
|
||||
};
|
||||
|
||||
type ChildrenProps<Surface extends 'container' | 'leaf' | undefined> =
|
||||
Surface extends 'container'
|
||||
? { surfaceChildren: ReactNode; children?: never }
|
||||
: Surface extends 'leaf'
|
||||
? { children: ReactNode; surfaceChildren?: never }
|
||||
: { children: ReactNode };
|
||||
type BaseOwnProps<
|
||||
D extends ComponentConfig<any, any>,
|
||||
P extends Record<string, any>,
|
||||
> = {
|
||||
classes: Record<keyof D['classNames'], string>;
|
||||
} & ResolvedOwnProps<P, D['propDefs']>;
|
||||
|
||||
type ResolveBgProps<
|
||||
D extends ComponentConfig<any, any>,
|
||||
TBase,
|
||||
> = D['bg'] extends 'provider'
|
||||
? Omit<TBase, 'children'> & { childrenWithBgProvider: ReactNode }
|
||||
: TBase;
|
||||
|
||||
type DataAttributeKeys<PropDefs> = {
|
||||
[K in keyof PropDefs]: PropDefs[K] extends { dataAttribute: true }
|
||||
@@ -98,7 +105,7 @@ type DataAttributes<PropDefs> = {
|
||||
[K in DataAttributeKeys<PropDefs> as `data-${Lowercase<
|
||||
string & K
|
||||
>}`]?: string;
|
||||
} & { 'data-on-surface'?: string };
|
||||
} & { 'data-bg'?: string; 'data-on-bg'?: string };
|
||||
|
||||
export type UtilityKeys<D extends ComponentConfig<any, any>> =
|
||||
D['utilityProps'] extends ReadonlyArray<infer K extends string> ? K : never;
|
||||
@@ -124,10 +131,7 @@ export interface UseDefinitionResult<
|
||||
D extends ComponentConfig<any, any>,
|
||||
P extends Record<string, any>,
|
||||
> {
|
||||
ownProps: {
|
||||
classes: Record<keyof D['classNames'], string>;
|
||||
} & ResolvedOwnProps<P, D['propDefs']> &
|
||||
ChildrenProps<D['surface']>;
|
||||
ownProps: ResolveBgProps<D, BaseOwnProps<D, P>>;
|
||||
|
||||
// Rest props excludes both propDefs keys AND utility prop keys
|
||||
restProps: keyof Omit<P, keyof D['propDefs'] | UtilityKeys<D>> extends never
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { ReactNode } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { useBreakpoint } from '../useBreakpoint';
|
||||
import { useSurface, SurfaceProvider, UseSurfaceOptions } from '../useSurface';
|
||||
import { useBgProvider, useBgConsumer, BgProvider } from '../useBg';
|
||||
import { resolveResponsiveValue, processUtilityProps } from './helpers';
|
||||
import type {
|
||||
ComponentConfig,
|
||||
@@ -36,14 +36,13 @@ export function useDefinition<
|
||||
): UseDefinitionResult<D, P> {
|
||||
const { breakpoint } = useBreakpoint();
|
||||
|
||||
const surfaceOptions: UseSurfaceOptions | undefined =
|
||||
definition.surface === 'container'
|
||||
? { surface: props.surface }
|
||||
: definition.surface === 'leaf'
|
||||
? { onSurface: props.onSurface }
|
||||
: undefined;
|
||||
// Provider: resolve bg and provide context for children
|
||||
const providerBg = useBgProvider(
|
||||
definition.bg === 'provider' ? props.bg : undefined,
|
||||
);
|
||||
|
||||
const { surface: resolvedSurface } = useSurface(surfaceOptions);
|
||||
// Consumer: read parent context bg
|
||||
const consumerBg = useBgConsumer();
|
||||
|
||||
const ownPropKeys = new Set(Object.keys(definition.propDefs));
|
||||
const utilityPropKeys = new Set(definition.utilityProps ?? []);
|
||||
@@ -70,6 +69,9 @@ export function useDefinition<
|
||||
if (finalValue !== undefined) {
|
||||
ownPropsResolved[key] = finalValue;
|
||||
|
||||
// Skip data-bg for bg prop when the provider path handles it
|
||||
if (key === 'bg' && definition.bg === 'provider') continue;
|
||||
|
||||
if ((config as any).dataAttribute) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
dataAttributes[`data-${key.toLowerCase()}`] = String(finalValue);
|
||||
@@ -77,16 +79,14 @@ export function useDefinition<
|
||||
}
|
||||
}
|
||||
|
||||
// Add data-on-surface for leaf components
|
||||
if (definition.surface === 'leaf' && resolvedSurface !== undefined) {
|
||||
// Handle responsive surface values - for data attributes, use the resolved string
|
||||
const surfaceValue =
|
||||
typeof resolvedSurface === 'object'
|
||||
? resolveResponsiveValue(resolvedSurface as any, breakpoint)
|
||||
: resolvedSurface;
|
||||
if (surfaceValue !== undefined) {
|
||||
dataAttributes['data-on-surface'] = String(surfaceValue);
|
||||
}
|
||||
// Provider: set data-bg from the resolved provider bg
|
||||
if (definition.bg === 'provider' && providerBg.bg !== undefined) {
|
||||
dataAttributes['data-bg'] = String(providerBg.bg);
|
||||
}
|
||||
|
||||
// Consumer: set data-on-bg from the parent context
|
||||
if (definition.bg === 'consumer' && consumerBg.bg !== undefined) {
|
||||
dataAttributes['data-on-bg'] = String(consumerBg.bg);
|
||||
}
|
||||
|
||||
const { utilityClasses, utilityStyle } = processUtilityProps<UtilityKeys<D>>(
|
||||
@@ -103,21 +103,17 @@ export function useDefinition<
|
||||
classes[name] = clsx(
|
||||
cssKey as string,
|
||||
definition.styles[cssKey as keyof typeof definition.styles],
|
||||
{
|
||||
[utilityClasses]: utilityTarget === name,
|
||||
[ownPropsResolved.className]: classNameTarget === name,
|
||||
},
|
||||
utilityTarget === name && utilityClasses,
|
||||
classNameTarget === name && ownPropsResolved.className,
|
||||
);
|
||||
}
|
||||
|
||||
let children: ReactNode | undefined;
|
||||
let surfaceChildren: ReactNode | undefined;
|
||||
let childrenWithBgProvider: ReactNode | undefined;
|
||||
|
||||
if (definition.surface === 'container') {
|
||||
surfaceChildren = resolvedSurface ? (
|
||||
<SurfaceProvider surface={resolvedSurface}>
|
||||
{props.children}
|
||||
</SurfaceProvider>
|
||||
if (definition.bg === 'provider') {
|
||||
childrenWithBgProvider = providerBg.bg ? (
|
||||
<BgProvider bg={providerBg.bg}>{props.children}</BgProvider>
|
||||
) : (
|
||||
props.children
|
||||
);
|
||||
@@ -129,8 +125,8 @@ export function useDefinition<
|
||||
ownProps: {
|
||||
classes,
|
||||
...ownPropsResolved,
|
||||
...(definition.surface === 'container'
|
||||
? { surfaceChildren }
|
||||
...(definition.bg === 'provider'
|
||||
? { childrenWithBgProvider }
|
||||
: { children }),
|
||||
},
|
||||
restProps,
|
||||
|
||||
@@ -1,204 +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.
|
||||
*/
|
||||
|
||||
import { useContext, ReactNode } from 'react';
|
||||
import {
|
||||
createVersionedContext,
|
||||
createVersionedValueMap,
|
||||
} from '@backstage/version-bridge';
|
||||
import { Surface, Responsive } from '../types';
|
||||
|
||||
/** @public */
|
||||
export interface SurfaceContextValue {
|
||||
surface: Responsive<Surface> | undefined;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface SurfaceProviderProps {
|
||||
surface: Responsive<Surface>;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface UseSurfaceOptions {
|
||||
/**
|
||||
* The surface value this component CREATES for its children (container behavior).
|
||||
* When 'auto', increments from parent surface.
|
||||
*
|
||||
* Use this for components like Box, Flex, Grid that establish surface context.
|
||||
*/
|
||||
surface?: Responsive<Surface>;
|
||||
/**
|
||||
* The surface value this component is ON for styling (leaf behavior).
|
||||
* When 'auto', inherits from current surface.
|
||||
*
|
||||
* Use this for leaf components like Button that consume surface for styling.
|
||||
*/
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
const SurfaceContext = createVersionedContext<{
|
||||
1: SurfaceContextValue;
|
||||
}>('surface-context');
|
||||
|
||||
/**
|
||||
* Increments a surface level by one, capping at '3'.
|
||||
* Intent surfaces (danger, warning, success) remain unchanged.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
function incrementSurface(surface: Surface | undefined): Surface {
|
||||
if (!surface) return '0'; // no context = root level
|
||||
if (surface === '0') return '1';
|
||||
if (surface === '1') return '2';
|
||||
if (surface === '2' || surface === '3') return '3'; // cap at max
|
||||
// Intent surfaces remain unchanged
|
||||
if (surface === 'danger') return 'danger';
|
||||
if (surface === 'warning') return 'warning';
|
||||
if (surface === 'success') return 'success';
|
||||
// 'auto' should not appear here, but handle it defensively
|
||||
if (surface === 'auto') return '1';
|
||||
return surface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a surface value for containers (SurfaceProvider).
|
||||
* When 'auto' is used, increments from the parent surface.
|
||||
* For responsive surfaces (objects), returns them as-is without resolution.
|
||||
*
|
||||
* @param contextSurface - The surface from context
|
||||
* @param requestedSurface - The requested surface value (may be 'auto')
|
||||
* @returns The resolved surface value
|
||||
* @internal
|
||||
*/
|
||||
export function resolveSurfaceForProvider(
|
||||
contextSurface: Responsive<Surface> | undefined,
|
||||
requestedSurface: Responsive<Surface> | undefined,
|
||||
): Responsive<Surface> | undefined {
|
||||
if (!requestedSurface) {
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
// If requestedSurface is a responsive object (breakpoint-based), return as-is
|
||||
if (typeof requestedSurface === 'object') {
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// If contextSurface is a responsive object, we can't auto-increment from it
|
||||
// Return the requested surface as-is or default to '0' for auto
|
||||
if (typeof contextSurface === 'object') {
|
||||
if (requestedSurface === 'auto') {
|
||||
return '0'; // fallback to root when context is responsive
|
||||
}
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// For containers, 'auto' means increment to create a new elevated context
|
||||
if (requestedSurface === 'auto') {
|
||||
return incrementSurface(contextSurface);
|
||||
}
|
||||
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a surface value for leaf components (useSurface hook).
|
||||
* When 'auto' is used, inherits the current surface (doesn't increment).
|
||||
* For responsive surfaces (objects), returns them as-is without resolution.
|
||||
*
|
||||
* @param contextSurface - The surface from context
|
||||
* @param requestedSurface - The requested surface value (may be 'auto')
|
||||
* @returns The resolved surface value
|
||||
* @internal
|
||||
*/
|
||||
function resolveSurfaceForConsumer(
|
||||
contextSurface: Responsive<Surface> | undefined,
|
||||
requestedSurface: Responsive<Surface> | undefined,
|
||||
): Responsive<Surface> | undefined {
|
||||
if (!requestedSurface) {
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
// If requestedSurface is a responsive object (breakpoint-based), return as-is
|
||||
if (typeof requestedSurface === 'object') {
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
// For leaf components, 'auto' means inherit the current surface
|
||||
if (requestedSurface === 'auto') {
|
||||
// If context is responsive, fallback to '0'
|
||||
if (typeof contextSurface === 'object') {
|
||||
return '0';
|
||||
}
|
||||
return contextSurface;
|
||||
}
|
||||
|
||||
return requestedSurface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider component that establishes the surface context for child components.
|
||||
* This allows components to adapt their styling based on their background surface.
|
||||
*
|
||||
* Note: The surface value should already be resolved before passing to this provider.
|
||||
* Container components should use useSurface with the surface parameter.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
export const SurfaceProvider = ({
|
||||
surface,
|
||||
children,
|
||||
}: SurfaceProviderProps) => {
|
||||
return (
|
||||
<SurfaceContext.Provider
|
||||
value={createVersionedValueMap({ 1: { surface } })}
|
||||
>
|
||||
{children}
|
||||
</SurfaceContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook to access the current surface context.
|
||||
* Returns the current surface level, or undefined if no provider is present.
|
||||
*
|
||||
* The parameter name determines the behavior:
|
||||
* - `surface`: Container behavior - 'auto' increments from parent
|
||||
* - `onSurface`: Leaf behavior - 'auto' inherits from parent
|
||||
*
|
||||
* @param options - Optional configuration for surface resolution
|
||||
* @internal
|
||||
*/
|
||||
export const useSurface = (
|
||||
options?: UseSurfaceOptions,
|
||||
): SurfaceContextValue => {
|
||||
const value = useContext(SurfaceContext)?.atVersion(1);
|
||||
const context = value ?? { surface: undefined };
|
||||
|
||||
// Infer behavior from which parameter is provided
|
||||
// 'surface' = provider behavior (increment)
|
||||
// 'onSurface' = consumer behavior (inherit)
|
||||
const isProvider = options?.surface !== undefined;
|
||||
const requestedSurface = options?.surface ?? options?.onSurface;
|
||||
|
||||
const resolvedSurface = isProvider
|
||||
? resolveSurfaceForProvider(context.surface, requestedSurface)
|
||||
: resolveSurfaceForConsumer(context.surface, requestedSurface);
|
||||
|
||||
return {
|
||||
surface: resolvedSurface,
|
||||
};
|
||||
};
|
||||
@@ -46,6 +46,7 @@ export * from './components/Tabs';
|
||||
export * from './components/TagGroup';
|
||||
export * from './components/Text';
|
||||
export * from './components/TextField';
|
||||
export * from './components/PasswordField';
|
||||
export * from './components/Tooltip';
|
||||
export * from './components/Menu';
|
||||
export * from './components/Popover';
|
||||
@@ -63,3 +64,5 @@ export * from './types';
|
||||
|
||||
// Hooks
|
||||
export { useBreakpoint } from './hooks/useBreakpoint';
|
||||
export { useBgProvider, useBgConsumer, BgProvider } from './hooks/useBg';
|
||||
export type { BgContextValue, BgProviderProps } from './hooks/useBg';
|
||||
|
||||
+21
-20
@@ -131,7 +131,7 @@ export type TextVariants =
|
||||
export type TextColors = 'primary' | 'secondary';
|
||||
|
||||
/** @public */
|
||||
export type TextColorStatus = 'danger' | 'warning' | 'success';
|
||||
export type TextColorStatus = 'danger' | 'warning' | 'success' | 'info';
|
||||
|
||||
/** @public */
|
||||
export type TextWeights = 'regular' | 'bold';
|
||||
@@ -182,29 +182,30 @@ export interface ComponentDefinition {
|
||||
}
|
||||
|
||||
/**
|
||||
* Surface type
|
||||
* Background type for the neutral bg system.
|
||||
*
|
||||
* Supports absolute levels ('0'-'3'), intent surfaces ('danger', 'warning', 'success'),
|
||||
* and 'auto' which increments from the parent surface context.
|
||||
* Supports neutral levels ('neutral-1' through 'neutral-3') and
|
||||
* intent backgrounds ('danger', 'warning', 'success').
|
||||
*
|
||||
* The 'neutral-4' level is not exposed as a prop value -- it is reserved
|
||||
* for leaf component CSS (e.g. Button on a 'neutral-3' surface).
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type Surface =
|
||||
| '0'
|
||||
| '1'
|
||||
| '2'
|
||||
| '3'
|
||||
export type ContainerBg =
|
||||
| 'neutral-1'
|
||||
| 'neutral-2'
|
||||
| 'neutral-3'
|
||||
| 'danger'
|
||||
| 'warning'
|
||||
| 'success'
|
||||
| 'auto';
|
||||
| 'success';
|
||||
|
||||
/** @public */
|
||||
export interface LeafSurfaceProps {
|
||||
onSurface?: Responsive<Surface>;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ContainerSurfaceProps {
|
||||
surface?: Responsive<Surface>;
|
||||
}
|
||||
/**
|
||||
* Background values accepted by provider components.
|
||||
*
|
||||
* Includes all `ContainerBg` values plus `'neutral-auto'` which
|
||||
* automatically increments the neutral level from the parent context.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export type ProviderBg = ContainerBg | 'neutral-auto';
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
<svg width="640" height="640" viewBox="0 0 640 640" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M370.766 153.765C377.563 166.716 371.236 182.114 359.357 190.647C323.005 216.758 299.326 259.411 299.326 307.597C299.326 365.997 334.107 416.27 384.077 438.817C395.235 443.852 403.478 454.368 403.478 466.609V640H382.864C224.433 640 96 511.517 96 353.025L96 20.6143C96 9.22934 105.229 0 116.614 0V0C226.982 3.14552e-06 322.792 62.3523 370.766 153.765Z" fill="black"/>
|
||||
<path d="M438.772 411.19C496.335 411.19 543 364.454 543 306.803C543 249.152 496.335 202.417 438.772 202.417C381.208 202.417 334.543 249.152 334.543 306.803C334.543 364.454 381.208 411.19 438.772 411.19Z" fill="black"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="640" height="640" fill="none" viewBox="0 0 640 640"><path fill="#000" d="M370.766 153.765C377.563 166.716 371.236 182.114 359.357 190.647C323.005 216.758 299.326 259.411 299.326 307.597C299.326 365.997 334.107 416.27 384.077 438.817C395.235 443.852 403.478 454.368 403.478 466.609V640H382.864C224.433 640 96 511.517 96 353.025L96 20.6143C96 9.22934 105.229 0 116.614 0V0C226.982 3.14552e-06 322.792 62.3523 370.766 153.765Z"/><path fill="#000" d="M438.772 411.19C496.335 411.19 543 364.454 543 306.803C543 249.152 496.335 202.417 438.772 202.417C381.208 202.417 334.543 249.152 334.543 306.803C334.543 364.454 381.208 411.19 438.772 411.19Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 702 B After Width: | Height: | Size: 696 B |
Reference in New Issue
Block a user