@kanso-labs/kanso-ui 0.11.21 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/README.md +32 -8
  2. package/dist/components/app-bar/index.d.ts +2 -2
  3. package/dist/components/app-bar/index.js +1 -1
  4. package/dist/components/app-bar/index.js.map +1 -1
  5. package/dist/components/avatar/index.d.ts +4 -4
  6. package/dist/components/avatar/index.js +21 -15
  7. package/dist/components/avatar/index.js.map +1 -1
  8. package/dist/components/badge/index.d.ts +2 -2
  9. package/dist/components/badge/index.js +1 -1
  10. package/dist/components/badge/index.js.map +1 -1
  11. package/dist/components/button/index.d.ts +32 -21
  12. package/dist/components/button/index.js +75 -19
  13. package/dist/components/button/index.js.map +1 -1
  14. package/dist/components/card/index.d.ts +2 -2
  15. package/dist/components/card/index.js +1 -1
  16. package/dist/components/card/index.js.map +1 -1
  17. package/dist/components/chip/index.d.ts +5 -5
  18. package/dist/components/chip/index.js +5 -5
  19. package/dist/components/chip/index.js.map +1 -1
  20. package/dist/components/code/index.d.ts +2 -2
  21. package/dist/components/code/index.js +1 -1
  22. package/dist/components/code/index.js.map +1 -1
  23. package/dist/components/container/index.d.ts +2 -2
  24. package/dist/components/container/index.js +1 -1
  25. package/dist/components/container/index.js.map +1 -1
  26. package/dist/components/copy-field/index.js +1 -1
  27. package/dist/components/currency/index.d.ts +2 -2
  28. package/dist/components/currency/index.js +1 -1
  29. package/dist/components/currency/index.js.map +1 -1
  30. package/dist/components/feed/index.d.ts +2 -2
  31. package/dist/components/feed/index.js +1 -1
  32. package/dist/components/feed/index.js.map +1 -1
  33. package/dist/components/icon-button/index.d.ts +24 -18
  34. package/dist/components/icon-button/index.js +62 -15
  35. package/dist/components/icon-button/index.js.map +1 -1
  36. package/dist/components/index.d.ts +2 -1
  37. package/dist/components/keycap/index.d.ts +2 -2
  38. package/dist/components/keycap/index.js +1 -1
  39. package/dist/components/keycap/index.js.map +1 -1
  40. package/dist/components/link/index.d.ts +2 -2
  41. package/dist/components/link/index.js +1 -1
  42. package/dist/components/link/index.js.map +1 -1
  43. package/dist/components/list-detail/index.d.ts +2 -2
  44. package/dist/components/list-detail/index.js +1 -1
  45. package/dist/components/list-detail/index.js.map +1 -1
  46. package/dist/components/popover/index.d.ts +105 -0
  47. package/dist/components/popover/index.js +246 -0
  48. package/dist/components/popover/index.js.map +1 -0
  49. package/dist/components/product-icon/index.d.ts +3 -3
  50. package/dist/components/product-icon/index.js +23 -17
  51. package/dist/components/product-icon/index.js.map +1 -1
  52. package/dist/components/separator/index.d.ts +14 -4
  53. package/dist/components/separator/index.js +23 -9
  54. package/dist/components/separator/index.js.map +1 -1
  55. package/dist/components/sheet/index.d.ts +26 -28
  56. package/dist/components/sheet/index.js +47 -82
  57. package/dist/components/sheet/index.js.map +1 -1
  58. package/dist/components/stack/index.d.ts +2 -2
  59. package/dist/components/stack/index.js +1 -1
  60. package/dist/components/stack/index.js.map +1 -1
  61. package/dist/components/supporting-pane/index.d.ts +2 -2
  62. package/dist/components/supporting-pane/index.js +1 -1
  63. package/dist/components/supporting-pane/index.js.map +1 -1
  64. package/dist/components/tabs/index.d.ts +15 -8
  65. package/dist/components/tabs/index.js +133 -45
  66. package/dist/components/tabs/index.js.map +1 -1
  67. package/dist/components/text/index.d.ts +2 -2
  68. package/dist/components/text/index.js +1 -1
  69. package/dist/components/text/index.js.map +1 -1
  70. package/dist/components/text-field/index.d.ts +12 -5
  71. package/dist/components/text-field/index.js +48 -33
  72. package/dist/components/text-field/index.js.map +1 -1
  73. package/dist/hooks/useImageLoadingStatus.js +53 -0
  74. package/dist/hooks/useImageLoadingStatus.js.map +1 -0
  75. package/dist/hooks/useRipple.js +1 -1
  76. package/dist/hooks/useRipple.js.map +1 -1
  77. package/dist/index.d.ts +2 -1
  78. package/dist/index.js +2 -1
  79. package/dist/render/aria.js +66 -0
  80. package/dist/render/aria.js.map +1 -0
  81. package/dist/render/useRender.d.ts +28 -0
  82. package/dist/render/useRender.js +132 -0
  83. package/dist/render/useRender.js.map +1 -0
  84. package/dist/styles/merge.js +14 -8
  85. package/dist/styles/merge.js.map +1 -1
  86. package/dist/styles.css +88 -36
  87. package/package.json +4 -4
  88. package/dist/render/nativeButton.js +0 -26
  89. package/dist/render/nativeButton.js.map +0 -1
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Kanso UI
2
2
 
3
3
  A React component library built on [StyleX](https://stylexjs.com) and
4
- [Base UI](https://base-ui.com), with design tokens sourced from a single
4
+ [React Aria Components](https://react-aria.adobe.com), with design tokens
5
+ sourced from a single
5
6
  [W3C Design Tokens (DTCG)](https://design-tokens.github.io/community-group/format/)
6
7
  file and compiled via [Style Dictionary](https://styledictionary.com).
7
8
 
@@ -126,15 +127,38 @@ for it.
126
127
  ```tsx
127
128
  <Text render={<h2 />}>Headline</Text>
128
129
  <Card render={<a href="/items/1" />}>First item</Card>
129
- <Button render={<a href="/items/1" />}>Label</Button>
130
130
  ```
131
131
 
132
- `Button` and `IconButton` read the tag off `render` and tell Base UI whether it
133
- is a real `<button>`, so an anchor needs no `nativeButton={false}` and logs no
134
- error. Base UI then supplies the button semantics that anchor has no native
135
- version of, which means it is announced as a **button** rather than as a link.
136
- For a control that should be announced as the link it is, reach for `Link`, or
137
- `Card` with `render`.
132
+ `Button` and `IconButton` take `href` instead: given one, they render an `<a>`
133
+ with the same styles and ripple, announced as the link it is.
134
+
135
+ ```tsx
136
+ <Button href="/items/1">Label</Button>
137
+ ```
138
+
139
+ The overlays — `Sheet` and `Popover` — are opened by a `Button` or `IconButton`
140
+ placed directly inside them, and closed by any button inside their content given
141
+ `slot="close"`:
142
+
143
+ ```tsx
144
+ <Sheet>
145
+ <Button>Open</Button>
146
+ <Sheet.Content>
147
+ <Sheet.Header>
148
+ <Sheet.Title>Headline</Sheet.Title>
149
+ <IconButton aria-label="Close" slot="close">
150
+ <CloseIcon />
151
+ </IconButton>
152
+ </Sheet.Header>
153
+ </Sheet.Content>
154
+ </Sheet>
155
+ ```
156
+
157
+ An open overlay carries a visually hidden dismiss button for screen readers,
158
+ whose label React Aria ships in some thirty languages. A bundler includes all of
159
+ them unless told which the app supports; React Aria's
160
+ [`@react-aria/optimize-locales-plugin`](https://www.npmjs.com/package/@react-aria/optimize-locales-plugin)
161
+ is how an app says so.
138
162
 
139
163
  ## Theming
140
164
 
@@ -1,7 +1,7 @@
1
- import { useRender } from "@base-ui/react/use-render";
1
+ import { RenderComponentProps } from "../../render/useRender.js";
2
2
  import { ReactNode } from "react";
3
3
  //#region src/components/app-bar/index.d.ts
4
- type AppBarProps = Omit<useRender.ComponentProps<'header'>, 'children'> & {
4
+ type AppBarProps = Omit<RenderComponentProps<'header'>, 'children'> & {
5
5
  /**
6
6
  * Which of M3's alignments the text takes. `center` is the configuration
7
7
  * that replaced the old center-aligned variant.
@@ -1,7 +1,7 @@
1
1
  import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
2
+ import { useRender } from "../../render/useRender.js";
2
3
  import { mergeStyles } from "../../styles/merge.js";
3
4
  import Text from "../text/index.js";
4
- import { useRender } from "@base-ui/react/use-render";
5
5
  import { jsx, jsxs } from "react/jsx-runtime";
6
6
  //#region src/components/app-bar/index.tsx
7
7
  const HEADLINE_OFFSET = "12px";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/app-bar/index.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { useRender } from '@base-ui/react/use-render';\nimport * as stylex from '@stylexjs/stylex';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, motion, spacing } from '../../tokens/design.tokens.stylex';\nimport Text from '../text';\n\n// Material 3's app bar, in the three sizes M3 Expressive recommends.\n//\n// The spec offers four variants. `search` is absent because it opens M3's\n// search view when selected, and that is a component in its own right that\n// this library does not have — half of a search bar would be worse than none.\n// The other three are here.\n//\n// `medium` and `large` are M3 Expressive's *flexible* bars. The baseline\n// medium and large ones they replaced are marked \"not recommended\" in the\n// spec, so there is no ambiguity to protect against and the shorter names are\n// the ones worth having at a call site.\n//\n// M3 also merged the old center-aligned variant into small as a configuration\n// rather than a variant, which is why alignment is a prop here and applies to\n// every size.\n//\n// Heights are M3's, and they are minimums rather than fixed. The spec says the\n// flexible bars \"hug the text contents\", and Expressive added multi-line\n// support and text wrapping, so a headline long enough to wrap has to be able\n// to make its bar taller. Fixing the height would truncate exactly the case\n// the variant was redesigned for.\n\n// How far the headline sits inside whatever comes before it, and the figure\n// an icon button's glyph is inset from the edge of its own touch target. M3\n// leans on the two agreeing, which is what makes a bar with a leading icon\n// and one without start their text in the same place.\nconst HEADLINE_OFFSET = '12px';\n\n// Where the bar's visible content starts by default, which is M3's own\n// margin for a top app bar and the same margin its body content takes.\nconst DEFAULT_CONTENT_INSET = '16px';\nconst HEIGHTS = {\n large: {\n plain: '120px',\n withSubtitle: '152px'\n },\n medium: {\n plain: '112px',\n withSubtitle: '136px'\n },\n small: {\n plain: '64px',\n withSubtitle: '64px'\n }\n} as const;\n\n// What a flexible bar collapses to, which is the small bar exactly. M3 does\n// not describe the collapsed large bar as a state of its own — it describes it\n// as becoming the small bar, which is why this reads off the same table rather\n// than being a fourth height.\nconst COLLAPSED_HEIGHT = HEIGHTS.small.plain;\nconst styles = {\n root: {\n kGNEyG: \"x6s0dn4\",\n kWkggS: \"x14s9m32\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x78zum5\",\n kjj79g: \"xl56j7k\",\n kIyJzY: \"xioksi3\",\n k1ekBW: \"x8u3r54\",\n kAMwcw: \"x1seskgd\",\n $$css: true\n },\n row: {\n kGNEyG: \"x6s0dn4\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x78zum5\",\n kOIVth: \"xzoy561\",\n kzqmXN: \"xh8yej3\",\n kUOVxO: \"xvueqy4\",\n $$css: true\n },\n scrolled: {\n kWkggS: \"x1mjdul6\",\n $$css: true\n }\n};\n\n// One entry per size, and each names a type role rather than a figure. M3's\n// own tokens are `md.comp.app-bar.<size>.title.font`, aliases onto the type\n// scale rather than sizes of their own, so this maps to the scale the same\n// way and lets Text apply it.\nconst HEADLINE_VARIANT = {\n large: 'headlineMedium',\n medium: 'headlineSmall',\n small: 'titleLarge'\n} as const;\n\n// The headline is the page's title in M3's model, so it is the page's <h1>.\n// A bar nested somewhere that already has one can pass its own heading as\n// `headline` instead.\n// oxlint-disable-next-line jsx-a11y/heading-has-content -- filled by useRender\nconst HEADING_1 = <h1 />;\nconst PARAGRAPH = <p />;\n\n// Height is the one value the call site does not choose, and it depends on two\n// props at once, so it is a function style rather than one key per\n// combination. StyleX writes it to a custom property inline, the same\n// mechanism Feed uses for its cell minimum.\nconst _temp = {\n kAzted: \"x1chvrdh\",\n \"$$css\": true\n};\nconst heightStyles = {\n root: (minBlockSize: string) => [_temp, {\n \"--x-minBlockSize\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(minBlockSize)\n }]\n};\n\n// The row's measure and inset both come from the call site, so both are\n// function styles too. The inset is where the bar's visible content starts,\n// and the row carries `contentInset` minus the offset the text block already\n// holds — which is what keeps a leading icon's glyph and a headline with no\n// icon before it starting in the same place.\nconst _temp2 = {\n ks0D6T: \"x13i5qev\",\n kg3NbH: \"x30g6up\",\n \"$$css\": true\n};\nconst rowStyles = {\n root: (contentInset: string, maxInlineSize: string) => [_temp2, {\n \"--x-maxInlineSize\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(maxInlineSize),\n \"--x-paddingInline\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(`calc(${contentInset} - ${HEADLINE_OFFSET})`)\n }]\n};\ntype AppBarProps = Omit<useRender.ComponentProps<'header'>, 'children'> & {\n /**\n * Which of M3's alignments the text takes. `center` is the configuration\n * that replaced the old center-aligned variant.\n * @default 'start'\n */\n align?: 'center' | 'start';\n /**\n * Collapses a flexible bar to the height and headline of the small one, for\n * a pinned bar over content that has been scrolled. M3 specifies it for the\n * flexible sizes, and without it a pinned `large` costs 152px of the\n * viewport for as long as the page is open.\n *\n * Controlled, and for the same reason `scrolled` is: only the app knows\n * which element scrolls, and how far into it the bar should have finished\n * collapsing. Usually set from the same handler.\n *\n * Ignored on `small`, which is already the height this collapses to. The\n * subtitle goes with the height, since there is no room for a second line\n * in the small bar.\n *\n * **The scroll container needs `overflow-anchor: none`.** Collapsing hands\n * the page back the height the bar gives up, and browsers answer a change\n * in height above the viewport by moving the scroll offset by exactly that\n * amount, so the content underneath stays where it was. That offset is\n * what the call site derived `collapsed` from, so it lands back under the\n * threshold, the bar expands, the offset is restored, and the two take\n * turns for as long as the reader stays in that band. Turning anchoring off\n * is what breaks the loop, and it is also the movement you want: the\n * content follows the bar's bottom edge up rather than standing still while\n * the bar shrinks behind it.\n *\n * No threshold avoids this on its own. The unstable band is as tall as the\n * height the bar gives back and it sits directly above wherever the\n * threshold is put, so moving the threshold moves the band with it.\n * @default false\n */\n collapsed?: boolean;\n /**\n * Where the bar's content starts, measured to the headline. A leading slot\n * sits 12px before that, which is the room an icon button's own padding\n * fills around its glyph — M3's arrangement, and what makes a bar with an\n * icon and one without start their text in the same place.\n *\n * The default is M3's own margin for a top app bar, which is also the\n * margin it gives body content, so a bar and the page beneath line up\n * without either being told about the other. Set it to the page's gutter\n * where that differs.\n * @default '16px'\n */\n contentInset?: string;\n /**\n * How wide the bar's contents may run before they stop growing, with the\n * row centred in whatever is left. The bar's own surface still paints edge\n * to edge.\n *\n * This is what lines a full-bleed bar up with a page whose content sits in\n * a measure: give it the page's measure, and `contentInset` the page's\n * gutter. Unset, the row is as wide as the bar.\n */\n contentMaxInlineSize?: string;\n /** The page's title. Wraps rather than truncating, and grows the bar with it. */\n headline?: ReactNode;\n /** Usually a back or menu icon button. Sits before the text. */\n leading?: ReactNode;\n /**\n * Whether the content beneath has been scrolled. M3 separates a bar from\n * scrolled content with a fill colour rather than a shadow, and this is what\n * applies it.\n *\n * Controlled, with no listener of its own: only the app knows which element\n * scrolls, and a bar that watched the window would be wrong inside every\n * pane and dialog that scrolls independently.\n * @default false\n */\n scrolled?: boolean;\n /**\n * Height and headline size. `medium` and `large` are M3 Expressive's\n * flexible bars, which hug their text — the baseline variants they replaced\n * are no longer recommended and are not offered.\n * @default 'small'\n */\n size?: 'large' | 'medium' | 'small';\n /** A second line under the headline. Makes `medium` and `large` taller. */\n subtitle?: ReactNode;\n /** Actions, at the far end. Usually icon buttons. */\n trailing?: ReactNode;\n};\n\n/**\n * Material 3's app bar: the container at the top of a page carrying its title,\n * one or two actions, and the way back out.\n *\n * Three sizes. `small` is a fixed 64px bar for a page whose title is a label;\n * `medium` and `large` are the flexible bars, which give the headline a larger\n * type role and grow to fit a subtitle or a headline that wraps.\n *\n * It paints its own surface, unlike the layout components, because separating\n * itself from the content beneath is the job — which is also why `scrolled`\n * and `collapsed` exist. Neither watches the page: only the app knows which\n * element scrolls, so both are set from its own scroll handler.\n */\nfunction AppBar({\n align = 'start',\n collapsed = false,\n contentInset = DEFAULT_CONTENT_INSET,\n contentMaxInlineSize = 'none',\n headline,\n leading,\n render,\n scrolled = false,\n size = 'small',\n subtitle,\n trailing,\n ...props\n}: AppBarProps) {\n // `small` is already what the flexible bars collapse to, so collapsing it is\n // a no-op rather than an error. A bar whose size is chosen at the call site\n // from a breakpoint would otherwise have to guard the prop as well.\n const collapse = collapsed && size !== 'small';\n const expandedHeight = subtitle === undefined || subtitle === null ? HEIGHTS[size].plain : HEIGHTS[size].withSubtitle;\n const height = collapse ? COLLAPSED_HEIGHT : expandedHeight;\n return useRender({\n defaultTagName: 'header',\n props: {\n ...props,\n children: <div {...stylex.props(styles.row, rowStyles.root(contentInset, contentMaxInlineSize))}>\n {leading === undefined ? null : <div className=\"x6s0dn4 x78zum5 x2lah0s xzoy561\">{leading}</div>}\n <div {...{\n 0: {\n className: \"x9f619 x78zum5 xdt5ytf x1iyjqo2 xl56j7k xeuugli x1cmllll xaope02\"\n },\n 1: {\n className: \"x9f619 x78zum5 xdt5ytf x1iyjqo2 xl56j7k xeuugli x1cmllll xaope02 x2b8uid\"\n }\n }[!!(align === 'center') << 0]}>\n {headline === undefined ? null : <Text className=\"xioksi3 x1nxaspg x1seskgd\" render={HEADING_1} variant={collapse ? HEADLINE_VARIANT.small : HEADLINE_VARIANT[size]}>\n {headline}\n </Text>}\n {subtitle === undefined || collapse ? null : <Text render={PARAGRAPH} tone=\"muted\" variant=\"bodyMedium\">\n {subtitle}\n </Text>}\n </div>\n {trailing === undefined ? null : <div className=\"x6s0dn4 x78zum5 x2lah0s xzoy561\">{trailing}</div>}\n </div>,\n ...mergeStyles(stylex.props(styles.root, heightStyles.root(height), scrolled && styles.scrolled), props)\n },\n render\n });\n}\nexport type { AppBarProps };\nexport default AppBar;"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/app-bar/index.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport * as stylex from '@stylexjs/stylex';\nimport type { RenderComponentProps } from '../../render/useRender';\nimport { useRender } from '../../render/useRender';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, motion, spacing } from '../../tokens/design.tokens.stylex';\nimport Text from '../text';\n\n// Material 3's app bar, in the three sizes M3 Expressive recommends.\n//\n// The spec offers four variants. `search` is absent because it opens M3's\n// search view when selected, and that is a component in its own right that\n// this library does not have — half of a search bar would be worse than none.\n// The other three are here.\n//\n// `medium` and `large` are M3 Expressive's *flexible* bars. The baseline\n// medium and large ones they replaced are marked \"not recommended\" in the\n// spec, so there is no ambiguity to protect against and the shorter names are\n// the ones worth having at a call site.\n//\n// M3 also merged the old center-aligned variant into small as a configuration\n// rather than a variant, which is why alignment is a prop here and applies to\n// every size.\n//\n// Heights are M3's, and they are minimums rather than fixed. The spec says the\n// flexible bars \"hug the text contents\", and Expressive added multi-line\n// support and text wrapping, so a headline long enough to wrap has to be able\n// to make its bar taller. Fixing the height would truncate exactly the case\n// the variant was redesigned for.\n\n// How far the headline sits inside whatever comes before it, and the figure\n// an icon button's glyph is inset from the edge of its own touch target. M3\n// leans on the two agreeing, which is what makes a bar with a leading icon\n// and one without start their text in the same place.\nconst HEADLINE_OFFSET = '12px';\n\n// Where the bar's visible content starts by default, which is M3's own\n// margin for a top app bar and the same margin its body content takes.\nconst DEFAULT_CONTENT_INSET = '16px';\nconst HEIGHTS = {\n large: {\n plain: '120px',\n withSubtitle: '152px'\n },\n medium: {\n plain: '112px',\n withSubtitle: '136px'\n },\n small: {\n plain: '64px',\n withSubtitle: '64px'\n }\n} as const;\n\n// What a flexible bar collapses to, which is the small bar exactly. M3 does\n// not describe the collapsed large bar as a state of its own — it describes it\n// as becoming the small bar, which is why this reads off the same table rather\n// than being a fourth height.\nconst COLLAPSED_HEIGHT = HEIGHTS.small.plain;\nconst styles = {\n root: {\n kGNEyG: \"x6s0dn4\",\n kWkggS: \"x14s9m32\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x78zum5\",\n kjj79g: \"xl56j7k\",\n kIyJzY: \"xioksi3\",\n k1ekBW: \"x8u3r54\",\n kAMwcw: \"x1seskgd\",\n $$css: true\n },\n row: {\n kGNEyG: \"x6s0dn4\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x78zum5\",\n kOIVth: \"xzoy561\",\n kzqmXN: \"xh8yej3\",\n kUOVxO: \"xvueqy4\",\n $$css: true\n },\n scrolled: {\n kWkggS: \"x1mjdul6\",\n $$css: true\n }\n};\n\n// One entry per size, and each names a type role rather than a figure. M3's\n// own tokens are `md.comp.app-bar.<size>.title.font`, aliases onto the type\n// scale rather than sizes of their own, so this maps to the scale the same\n// way and lets Text apply it.\nconst HEADLINE_VARIANT = {\n large: 'headlineMedium',\n medium: 'headlineSmall',\n small: 'titleLarge'\n} as const;\n\n// The headline is the page's title in M3's model, so it is the page's <h1>.\n// A bar nested somewhere that already has one can pass its own heading as\n// `headline` instead.\n// oxlint-disable-next-line jsx-a11y/heading-has-content -- filled by useRender\nconst HEADING_1 = <h1 />;\nconst PARAGRAPH = <p />;\n\n// Height is the one value the call site does not choose, and it depends on two\n// props at once, so it is a function style rather than one key per\n// combination. StyleX writes it to a custom property inline, the same\n// mechanism Feed uses for its cell minimum.\nconst _temp = {\n kAzted: \"x1chvrdh\",\n \"$$css\": true\n};\nconst heightStyles = {\n root: (minBlockSize: string) => [_temp, {\n \"--x-minBlockSize\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(minBlockSize)\n }]\n};\n\n// The row's measure and inset both come from the call site, so both are\n// function styles too. The inset is where the bar's visible content starts,\n// and the row carries `contentInset` minus the offset the text block already\n// holds — which is what keeps a leading icon's glyph and a headline with no\n// icon before it starting in the same place.\nconst _temp2 = {\n ks0D6T: \"x13i5qev\",\n kg3NbH: \"x30g6up\",\n \"$$css\": true\n};\nconst rowStyles = {\n root: (contentInset: string, maxInlineSize: string) => [_temp2, {\n \"--x-maxInlineSize\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(maxInlineSize),\n \"--x-paddingInline\": (val => typeof val === \"number\" ? val + \"px\" : val != null ? val : undefined)(`calc(${contentInset} - ${HEADLINE_OFFSET})`)\n }]\n};\ntype AppBarProps = Omit<RenderComponentProps<'header'>, 'children'> & {\n /**\n * Which of M3's alignments the text takes. `center` is the configuration\n * that replaced the old center-aligned variant.\n * @default 'start'\n */\n align?: 'center' | 'start';\n /**\n * Collapses a flexible bar to the height and headline of the small one, for\n * a pinned bar over content that has been scrolled. M3 specifies it for the\n * flexible sizes, and without it a pinned `large` costs 152px of the\n * viewport for as long as the page is open.\n *\n * Controlled, and for the same reason `scrolled` is: only the app knows\n * which element scrolls, and how far into it the bar should have finished\n * collapsing. Usually set from the same handler.\n *\n * Ignored on `small`, which is already the height this collapses to. The\n * subtitle goes with the height, since there is no room for a second line\n * in the small bar.\n *\n * **The scroll container needs `overflow-anchor: none`.** Collapsing hands\n * the page back the height the bar gives up, and browsers answer a change\n * in height above the viewport by moving the scroll offset by exactly that\n * amount, so the content underneath stays where it was. That offset is\n * what the call site derived `collapsed` from, so it lands back under the\n * threshold, the bar expands, the offset is restored, and the two take\n * turns for as long as the reader stays in that band. Turning anchoring off\n * is what breaks the loop, and it is also the movement you want: the\n * content follows the bar's bottom edge up rather than standing still while\n * the bar shrinks behind it.\n *\n * No threshold avoids this on its own. The unstable band is as tall as the\n * height the bar gives back and it sits directly above wherever the\n * threshold is put, so moving the threshold moves the band with it.\n * @default false\n */\n collapsed?: boolean;\n /**\n * Where the bar's content starts, measured to the headline. A leading slot\n * sits 12px before that, which is the room an icon button's own padding\n * fills around its glyph — M3's arrangement, and what makes a bar with an\n * icon and one without start their text in the same place.\n *\n * The default is M3's own margin for a top app bar, which is also the\n * margin it gives body content, so a bar and the page beneath line up\n * without either being told about the other. Set it to the page's gutter\n * where that differs.\n * @default '16px'\n */\n contentInset?: string;\n /**\n * How wide the bar's contents may run before they stop growing, with the\n * row centred in whatever is left. The bar's own surface still paints edge\n * to edge.\n *\n * This is what lines a full-bleed bar up with a page whose content sits in\n * a measure: give it the page's measure, and `contentInset` the page's\n * gutter. Unset, the row is as wide as the bar.\n */\n contentMaxInlineSize?: string;\n /** The page's title. Wraps rather than truncating, and grows the bar with it. */\n headline?: ReactNode;\n /** Usually a back or menu icon button. Sits before the text. */\n leading?: ReactNode;\n /**\n * Whether the content beneath has been scrolled. M3 separates a bar from\n * scrolled content with a fill colour rather than a shadow, and this is what\n * applies it.\n *\n * Controlled, with no listener of its own: only the app knows which element\n * scrolls, and a bar that watched the window would be wrong inside every\n * pane and dialog that scrolls independently.\n * @default false\n */\n scrolled?: boolean;\n /**\n * Height and headline size. `medium` and `large` are M3 Expressive's\n * flexible bars, which hug their text — the baseline variants they replaced\n * are no longer recommended and are not offered.\n * @default 'small'\n */\n size?: 'large' | 'medium' | 'small';\n /** A second line under the headline. Makes `medium` and `large` taller. */\n subtitle?: ReactNode;\n /** Actions, at the far end. Usually icon buttons. */\n trailing?: ReactNode;\n};\n\n/**\n * Material 3's app bar: the container at the top of a page carrying its title,\n * one or two actions, and the way back out.\n *\n * Three sizes. `small` is a fixed 64px bar for a page whose title is a label;\n * `medium` and `large` are the flexible bars, which give the headline a larger\n * type role and grow to fit a subtitle or a headline that wraps.\n *\n * It paints its own surface, unlike the layout components, because separating\n * itself from the content beneath is the job — which is also why `scrolled`\n * and `collapsed` exist. Neither watches the page: only the app knows which\n * element scrolls, so both are set from its own scroll handler.\n */\nfunction AppBar({\n align = 'start',\n collapsed = false,\n contentInset = DEFAULT_CONTENT_INSET,\n contentMaxInlineSize = 'none',\n headline,\n leading,\n render,\n scrolled = false,\n size = 'small',\n subtitle,\n trailing,\n ...props\n}: AppBarProps) {\n // `small` is already what the flexible bars collapse to, so collapsing it is\n // a no-op rather than an error. A bar whose size is chosen at the call site\n // from a breakpoint would otherwise have to guard the prop as well.\n const collapse = collapsed && size !== 'small';\n const expandedHeight = subtitle === undefined || subtitle === null ? HEIGHTS[size].plain : HEIGHTS[size].withSubtitle;\n const height = collapse ? COLLAPSED_HEIGHT : expandedHeight;\n return useRender({\n defaultTagName: 'header',\n props: {\n ...props,\n children: <div {...stylex.props(styles.row, rowStyles.root(contentInset, contentMaxInlineSize))}>\n {leading === undefined ? null : <div className=\"x6s0dn4 x78zum5 x2lah0s xzoy561\">{leading}</div>}\n <div {...{\n 0: {\n className: \"x9f619 x78zum5 xdt5ytf x1iyjqo2 xl56j7k xeuugli x1cmllll xaope02\"\n },\n 1: {\n className: \"x9f619 x78zum5 xdt5ytf x1iyjqo2 xl56j7k xeuugli x1cmllll xaope02 x2b8uid\"\n }\n }[!!(align === 'center') << 0]}>\n {headline === undefined ? null : <Text className=\"xioksi3 x1nxaspg x1seskgd\" render={HEADING_1} variant={collapse ? HEADLINE_VARIANT.small : HEADLINE_VARIANT[size]}>\n {headline}\n </Text>}\n {subtitle === undefined || collapse ? null : <Text render={PARAGRAPH} tone=\"muted\" variant=\"bodyMedium\">\n {subtitle}\n </Text>}\n </div>\n {trailing === undefined ? null : <div className=\"x6s0dn4 x78zum5 x2lah0s xzoy561\">{trailing}</div>}\n </div>,\n ...mergeStyles(stylex.props(styles.root, heightStyles.root(height), scrolled && styles.scrolled), props)\n },\n render\n });\n}\nexport type { AppBarProps };\nexport default AppBar;"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { AvatarRootProps } from "@base-ui/react/avatar";
1
+ import { RenderComponentProps } from "../../render/useRender.js";
2
2
  //#region src/components/avatar/index.d.ts
3
- type AvatarProps = Omit<AvatarRootProps, 'children'> & {
3
+ type AvatarProps = Omit<RenderComponentProps<'span'>, 'children'> & {
4
4
  /**
5
5
  * The person this represents. Supplies both the initials and the accessible
6
6
  * name, so a screen reader announces who it is rather than reading two
@@ -23,7 +23,7 @@ type AvatarProps = Omit<AvatarRootProps, 'children'> & {
23
23
  */
24
24
  tone?: 'negative' | 'positive' | 'primary' | 'secondary' | 'tertiary';
25
25
  };
26
- declare function Avatar$1({ name, size, src, tone, ...props }: AvatarProps): import("react").JSX.Element;
26
+ declare function Avatar({ name, render, size, src, tone, ...props }: AvatarProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
27
27
  //#endregion
28
- export { type AvatarProps, Avatar$1 as default };
28
+ export { type AvatarProps, Avatar as default };
29
29
  //# sourceMappingURL=index.d.ts.map
@@ -1,7 +1,8 @@
1
1
  import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
2
- import { mergeStatefulStyles } from "../../styles/merge.js";
3
- import { jsx, jsxs } from "react/jsx-runtime";
4
- import { Avatar } from "@base-ui/react/avatar";
2
+ import { useRender } from "../../render/useRender.js";
3
+ import { mergeStyles } from "../../styles/merge.js";
4
+ import { useImageLoadingStatus } from "../../hooks/useImageLoadingStatus.js";
5
+ import { jsx } from "react/jsx-runtime";
5
6
  //#region src/components/avatar/index.tsx
6
7
  const styles = {
7
8
  base: {
@@ -67,17 +68,22 @@ const styles = {
67
68
  $$css: true
68
69
  }
69
70
  };
70
- function Avatar$1({ name, size = "md", src, tone = "primary", ...props$1 }) {
71
- return /* @__PURE__ */ jsxs(Avatar.Root, {
72
- "aria-label": name,
73
- role: "img",
74
- ...props$1,
75
- ...mergeStatefulStyles(props(styles.base, styles[size], styles[tone]), props$1),
76
- children: [src === void 0 ? null : /* @__PURE__ */ jsx(Avatar.Image, {
77
- alt: "",
78
- src,
79
- className: "x5yr21d xh8yej3 xl1xv1r"
80
- }), /* @__PURE__ */ jsx(Avatar.Fallback, { children: initialsFrom(name) })]
71
+ function Avatar({ name, render, size = "md", src, tone = "primary", ...props$1 }) {
72
+ const status = useImageLoadingStatus(src);
73
+ return useRender({
74
+ defaultTagName: "span",
75
+ props: {
76
+ "aria-label": name,
77
+ role: "img",
78
+ ...props$1,
79
+ children: status === "loaded" ? /* @__PURE__ */ jsx("img", {
80
+ alt: "",
81
+ src,
82
+ className: "x5yr21d xh8yej3 xl1xv1r"
83
+ }) : initialsFrom(name),
84
+ ...mergeStyles(props(styles.base, styles[size], styles[tone]), props$1)
85
+ },
86
+ render
81
87
  });
82
88
  }
83
89
  function initialsFrom(name) {
@@ -86,6 +92,6 @@ function initialsFrom(name) {
86
92
  return `${Array.from(words[0] ?? "")[0] ?? ""}${words.length > 1 ? Array.from(words.at(-1) ?? "")[0] ?? "" : ""}`.toLocaleUpperCase();
87
93
  }
88
94
  //#endregion
89
- export { Avatar$1 as default };
95
+ export { Avatar as default };
90
96
 
91
97
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/avatar/index.tsx"],"sourcesContent":["import type { AvatarRootProps as BaseUIAvatarRootProps } from '@base-ui/react/avatar';\nimport { Avatar as BaseUIAvatar } from '@base-ui/react/avatar';\nimport * as stylex from '@stylexjs/stylex';\nimport { mergeStatefulStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, typography } from '../../tokens/design.tokens.stylex';\n\n// Tones are container/on-container pairs rather than one colour each, so the\n// initials are always a role's own foreground and can't end up unreadable on\n// a tint they were never paired with. Which tone a given person gets is the\n// consuming app's decision — the design cycles colours per person, and that\n// cycle belongs where the list of people is, not in this component.\n//\n// Sizes carry their own type size but not their own weight or family: the\n// label face at medium weight holds across all three, so a large avatar is\n// bigger initials rather than differently-styled ones.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x3y1ksm\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x3nfvp2\",\n kmuXW: \"x2lah0s\",\n kMv6JI: \"xkknnkr\",\n k63SB2: \"x1bf3uqz\",\n kjj79g: \"xl56j7k\",\n kVQacm: \"xb3r6kr\",\n kfSwDN: \"x87ps6o\",\n $$css: true\n },\n image: {\n kZKoxP: \"x5yr21d\",\n kzqmXN: \"xh8yej3\",\n kVIFPx: \"xl1xv1r\",\n $$css: true\n },\n lg: {\n kZKoxP: \"xnnlda6\",\n kGuDYH: \"x1848etk\",\n kzqmXN: \"x15yg21f\",\n $$css: true\n },\n md: {\n kZKoxP: \"x1vqgdyp\",\n kGuDYH: \"x14zspaw\",\n kzqmXN: \"x100vrsf\",\n $$css: true\n },\n negative: {\n kWkggS: \"xkz0cvf\",\n kMwMTN: \"xylzu7l\",\n $$css: true\n },\n positive: {\n kWkggS: \"xa3zu90\",\n kMwMTN: \"x1di7zbg\",\n $$css: true\n },\n primary: {\n kWkggS: \"xp89om9\",\n kMwMTN: \"xscdzaa\",\n $$css: true\n },\n secondary: {\n kWkggS: \"x15dekr4\",\n kMwMTN: \"x1sawysd\",\n $$css: true\n },\n sm: {\n kZKoxP: \"xc9qbxq\",\n kGuDYH: \"xkucv67\",\n kzqmXN: \"x14qfxbe\",\n $$css: true\n },\n tertiary: {\n kWkggS: \"xs6yvni\",\n kMwMTN: \"x3cbcwg\",\n $$css: true\n }\n};\ntype AvatarProps = Omit<BaseUIAvatarRootProps, 'children'> & {\n /**\n * The person this represents. Supplies both the initials and the accessible\n * name, so a screen reader announces who it is rather than reading two\n * letters aloud.\n */\n name: string;\n /**\n * Diameter: `sm` 36px, `md` 40px, `lg` 56px.\n * @default 'md'\n */\n size?: 'lg' | 'md' | 'sm';\n /**\n * A photo to show in place of the initials. The initials stay until it has\n * loaded, and come back if it fails.\n */\n src?: string;\n /**\n * Which container/on-container colour pair to tint with.\n * @default 'primary'\n */\n tone?: 'negative' | 'positive' | 'primary' | 'secondary' | 'tertiary';\n};\nfunction Avatar({\n name,\n size = 'md',\n src,\n tone = 'primary',\n ...props\n}: AvatarProps) {\n return (\n // role/aria-label rather than letting the initials be read: \"AL\" is not\n // what anyone means to announce. Marking the root as an image also makes\n // its contents presentational, so the photo and the initials can't be\n // announced a second time underneath the name.\n //\n // The lint rule below wants a real <img> tag instead of the role, which\n // cannot work here: an <img> is void, and this element's whole job is to\n // hold the initials shown when there is no photo, or none has loaded yet.\n <BaseUIAvatar.Root aria-label={name}\n // oxlint-disable-next-line jsx-a11y/prefer-tag-over-role -- <img> takes no children\n role=\"img\" {...props} {...mergeStatefulStyles(stylex.props(styles.base, styles[size], styles[tone]), props)}>\n {src === undefined ? null : <BaseUIAvatar.Image alt=\"\" src={src} className=\"x5yr21d xh8yej3 xl1xv1r\" />}\n <BaseUIAvatar.Fallback>{initialsFrom(name)}</BaseUIAvatar.Fallback>\n </BaseUIAvatar.Root>\n );\n}\n\n// First letter of the first and last word, which handles both \"Ada\" and \"Ada\n// Lovelace\" without a separate prop for how many to take. Array.from rather\n// than indexing, so a name starting outside the Basic Multilingual Plane\n// yields its whole first character instead of half a surrogate pair.\nfunction initialsFrom(name: string) {\n const words = name.split(/\\s+/u).filter(Boolean);\n if (words.length === 0) {\n return '';\n }\n const first = Array.from(words[0] ?? '')[0] ?? '';\n const last = words.length > 1 ? Array.from(words.at(-1) ?? '')[0] ?? '' : '';\n return `${first}${last}`.toLocaleUpperCase();\n}\nexport type { AvatarProps };\nexport default Avatar;"],"mappings":";;;;;AAgBA,MAAM,SAAS;CACb,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,WAAW;EACT,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AAwBA,SAAS,SAAO,EACd,MACA,OAAO,MACP,KACA,OAAO,WACP,GAAG,WACW;CACd,OASE,qBAAC,OAAa,MAAd;EAAmB,cAAY;EAE/B,MAAK;EAAM,GAAI;EAAO,GAAI,oBAAoB,MAAa,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,GAAG,OAAK;EAF1G,UAAA,CAGG,QAAQ,KAAA,IAAY,OAAO,oBAAC,OAAa,OAAd;GAAoB,KAAI;GAAQ;GAAK,WAAU;EAAyB,CAAA,GACpG,oBAAC,OAAa,UAAd,EAAA,UAAwB,aAAa,IAAI,EAAyB,CAAA,CACjD;;AAEvB;AAMA,SAAS,aAAa,MAAc;CAClC,MAAM,QAAQ,KAAK,MAAM,MAAM,CAAC,CAAC,OAAO,OAAO;CAC/C,IAAI,MAAM,WAAW,GACnB,OAAO;CAIT,OAAO,GAFO,MAAM,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,KAClC,MAAM,SAAS,IAAI,MAAM,KAAK,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,KAAK,KACjD,kBAAkB;AAC7C"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/avatar/index.tsx"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\nimport type { RenderComponentProps } from '../../render/useRender';\nimport { useImageLoadingStatus } from '../../hooks/useImageLoadingStatus';\nimport { useRender } from '../../render/useRender';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, typography } from '../../tokens/design.tokens.stylex';\n\n// Tones are container/on-container pairs rather than one colour each, so the\n// initials are always a role's own foreground and can't end up unreadable on\n// a tint they were never paired with. Which tone a given person gets is the\n// consuming app's decision — the design cycles colours per person, and that\n// cycle belongs where the list of people is, not in this component.\n//\n// Sizes carry their own type size but not their own weight or family: the\n// label face at medium weight holds across all three, so a large avatar is\n// bigger initials rather than differently-styled ones.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x3y1ksm\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x3nfvp2\",\n kmuXW: \"x2lah0s\",\n kMv6JI: \"xkknnkr\",\n k63SB2: \"x1bf3uqz\",\n kjj79g: \"xl56j7k\",\n kVQacm: \"xb3r6kr\",\n kfSwDN: \"x87ps6o\",\n $$css: true\n },\n image: {\n kZKoxP: \"x5yr21d\",\n kzqmXN: \"xh8yej3\",\n kVIFPx: \"xl1xv1r\",\n $$css: true\n },\n lg: {\n kZKoxP: \"xnnlda6\",\n kGuDYH: \"x1848etk\",\n kzqmXN: \"x15yg21f\",\n $$css: true\n },\n md: {\n kZKoxP: \"x1vqgdyp\",\n kGuDYH: \"x14zspaw\",\n kzqmXN: \"x100vrsf\",\n $$css: true\n },\n negative: {\n kWkggS: \"xkz0cvf\",\n kMwMTN: \"xylzu7l\",\n $$css: true\n },\n positive: {\n kWkggS: \"xa3zu90\",\n kMwMTN: \"x1di7zbg\",\n $$css: true\n },\n primary: {\n kWkggS: \"xp89om9\",\n kMwMTN: \"xscdzaa\",\n $$css: true\n },\n secondary: {\n kWkggS: \"x15dekr4\",\n kMwMTN: \"x1sawysd\",\n $$css: true\n },\n sm: {\n kZKoxP: \"xc9qbxq\",\n kGuDYH: \"xkucv67\",\n kzqmXN: \"x14qfxbe\",\n $$css: true\n },\n tertiary: {\n kWkggS: \"xs6yvni\",\n kMwMTN: \"x3cbcwg\",\n $$css: true\n }\n};\ntype AvatarProps = Omit<RenderComponentProps<'span'>, 'children'> & {\n /**\n * The person this represents. Supplies both the initials and the accessible\n * name, so a screen reader announces who it is rather than reading two\n * letters aloud.\n */\n name: string;\n /**\n * Diameter: `sm` 36px, `md` 40px, `lg` 56px.\n * @default 'md'\n */\n size?: 'lg' | 'md' | 'sm';\n /**\n * A photo to show in place of the initials. The initials stay until it has\n * loaded, and come back if it fails.\n */\n src?: string;\n /**\n * Which container/on-container colour pair to tint with.\n * @default 'primary'\n */\n tone?: 'negative' | 'positive' | 'primary' | 'secondary' | 'tertiary';\n};\nfunction Avatar({\n name,\n render,\n size = 'md',\n src,\n tone = 'primary',\n ...props\n}: AvatarProps) {\n // The photo is shown only once it has loaded, and the initials stay until\n // then and come back if it fails — see useImageLoadingStatus.\n const status = useImageLoadingStatus(src);\n\n // role/aria-label rather than letting the initials be read: \"AL\" is not\n // what anyone means to announce. Marking the root as an image also makes\n // its contents presentational, so the photo and the initials can't be\n // announced a second time underneath the name. A span rather than an\n // <img>, which is void: this element's whole job is to hold the initials\n // shown when there is no photo, or none has loaded yet.\n return useRender({\n defaultTagName: 'span',\n props: {\n 'aria-label': name,\n role: 'img',\n ...props,\n children: status === 'loaded' ? <img alt=\"\" src={src} className=\"x5yr21d xh8yej3 xl1xv1r\" /> : initialsFrom(name),\n ...mergeStyles(stylex.props(styles.base, styles[size], styles[tone]), props)\n },\n render\n });\n}\n\n// First letter of the first and last word, which handles both \"Ada\" and \"Ada\n// Lovelace\" without a separate prop for how many to take. Array.from rather\n// than indexing, so a name starting outside the Basic Multilingual Plane\n// yields its whole first character instead of half a surrogate pair.\nfunction initialsFrom(name: string) {\n const words = name.split(/\\s+/u).filter(Boolean);\n if (words.length === 0) {\n return '';\n }\n const first = Array.from(words[0] ?? '')[0] ?? '';\n const last = words.length > 1 ? Array.from(words.at(-1) ?? '')[0] ?? '' : '';\n return `${first}${last}`.toLocaleUpperCase();\n}\nexport type { AvatarProps };\nexport default Avatar;"],"mappings":";;;;;;AAiBA,MAAM,SAAS;CACb,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,WAAW;EACT,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AAwBA,SAAS,OAAO,EACd,MACA,QACA,OAAO,MACP,KACA,OAAO,WACP,GAAG,WACW;CAGd,MAAM,SAAS,sBAAsB,GAAG;CAQxC,OAAO,UAAU;EACf,gBAAgB;EAChB,OAAO;GACL,cAAc;GACd,MAAM;GACN,GAAG;GACH,UAAU,WAAW,WAAW,oBAAC,OAAD;IAAK,KAAI;IAAQ;IAAK,WAAU;GAAyB,CAAA,IAAM,aAAa,IAAI;GAChH,GAAG,YAAY,MAAa,OAAO,MAAM,OAAO,OAAO,OAAO,KAAK,GAAG,OAAK;EAC7E;EACA;CACF,CAAC;AACH;AAMA,SAAS,aAAa,MAAc;CAClC,MAAM,QAAQ,KAAK,MAAM,MAAM,CAAC,CAAC,OAAO,OAAO;CAC/C,IAAI,MAAM,WAAW,GACnB,OAAO;CAIT,OAAO,GAFO,MAAM,KAAK,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,KAClC,MAAM,SAAS,IAAI,MAAM,KAAK,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,KAAK,KACjD,kBAAkB;AAC7C"}
@@ -1,6 +1,6 @@
1
- import { useRender } from "@base-ui/react/use-render";
1
+ import { RenderComponentProps } from "../../render/useRender.js";
2
2
  //#region src/components/badge/index.d.ts
3
- type BadgeProps = useRender.ComponentProps<'span'> & {
3
+ type BadgeProps = RenderComponentProps<'span'> & {
4
4
  /**
5
5
  * Which colour role the badge carries. `neutral` states without ranking;
6
6
  * the other three read as good, bad, or worth noticing.
@@ -1,6 +1,6 @@
1
1
  import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
2
+ import { useRender } from "../../render/useRender.js";
2
3
  import { mergeStyles } from "../../styles/merge.js";
3
- import { useRender } from "@base-ui/react/use-render";
4
4
  //#region src/components/badge/index.tsx
5
5
  const styles = { base: {
6
6
  kGNEyG: "x6s0dn4",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/badge/index.tsx"],"sourcesContent":["import { useRender } from '@base-ui/react/use-render';\nimport * as stylex from '@stylexjs/stylex';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, spacing, typography } from '../../tokens/design.tokens.stylex';\n\n// A badge states something about what it sits beside — a count, a status, a\n// version — and is never itself the thing you press. That is the whole line\n// between this and Chip: a chip is a two-state button announcing itself\n// through `aria-pressed`, so reaching for one to render a read-only label\n// puts a control in the accessibility tree that nothing can operate.\n//\n// Both variants carry a 1px border and `filled` makes it transparent rather\n// than dropping it, so a badge is the same size either way. Emphasising one\n// badge in a row otherwise nudges its neighbours by a pixel each side.\n//\n// Tabular figures are unconditional, for the same reason Currency sets them:\n// what ends up in a badge is mostly numeric — counts, versions, ports — and a\n// stack of them down a list wobbles on proportional digits. Text that happens\n// to carry no digits is unaffected.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x1xdvaa9\",\n ksu8eU: \"x1y0btm7\",\n kMzoRj: \"xmkeg23\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x3nfvp2\",\n kmuXW: \"x2lah0s\",\n kMv6JI: \"xs1w0rn\",\n kGuDYH: \"x1n35her\",\n kcqcaj: \"xss6m8b\",\n k63SB2: \"x1su64ma\",\n kOIVth: \"xzoy561\",\n kb6lSQ: \"xdlnduh\",\n kLWn49: \"xaomhve\",\n k8WAf4: \"xpg5a72\",\n kg3NbH: \"xn60lsn\",\n khDVqt: \"xuxw1ft\",\n $$css: true\n }\n};\n\n// `neutral` draws from the surface roles rather than a colour of its own,\n// which is what lets it state something without ranking it. The other three\n// name a direction, and are the same three Currency uses, plus primary for\n// the notable-but-not-good-or-bad case. `error` is deliberately absent:\n// negative already covers it, and offering both would leave the choice\n// between them to taste.\nconst filledTones = {\n negative: {\n kWkggS: \"xkz0cvf\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"xylzu7l\",\n $$css: true\n },\n neutral: {\n kWkggS: \"x1rrhr5q\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"x1w5n3ug\",\n $$css: true\n },\n positive: {\n kWkggS: \"xa3zu90\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"x1di7zbg\",\n $$css: true\n },\n primary: {\n kWkggS: \"xp89om9\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"xscdzaa\",\n $$css: true\n }\n};\n\n// Outlined takes the tone at full strength for both the rule and the text,\n// rather than the on-container pairing filled uses. There is no container\n// here for an on-colour to be legible against — the page is the background —\n// so the container roles would read as washed out.\nconst outlinedTones = {\n negative: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x1xdi1zw\",\n kMwMTN: \"x12gp0t\",\n $$css: true\n },\n neutral: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x133yhug\",\n kMwMTN: \"x1w5n3ug\",\n $$css: true\n },\n positive: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"xm76mbg\",\n kMwMTN: \"x4no3js\",\n $$css: true\n },\n primary: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x1uajy3w\",\n kMwMTN: \"x1mxwv8y\",\n $$css: true\n }\n};\ntype BadgeProps = useRender.ComponentProps<'span'> & {\n /**\n * Which colour role the badge carries. `neutral` states without ranking;\n * the other three read as good, bad, or worth noticing.\n * @default 'neutral'\n */\n tone?: BadgeTone;\n /**\n * How much weight the badge pulls. `filled` sits on a container of its own;\n * `outlined` is a rule on the page, for a badge that should not compete\n * with the thing it labels.\n * @default 'filled'\n */\n variant?: BadgeVariant;\n};\ntype BadgeTone = 'negative' | 'neutral' | 'positive' | 'primary';\ntype BadgeVariant = 'filled' | 'outlined';\n\n/**\n * A short, read-only label attached to something else. It renders a `<span>`\n * and takes no interaction — for a label that can be selected, reach for Chip.\n */\nfunction Badge({\n render,\n tone = 'neutral',\n variant = 'filled',\n ...props\n}: BadgeProps) {\n const toned = variant === 'filled' ? filledTones[tone] : outlinedTones[tone];\n return useRender({\n defaultTagName: 'span',\n props: {\n ...props,\n ...mergeStyles(stylex.props(styles.base, toned), props)\n },\n render\n });\n}\nexport type { BadgeProps };\nexport default Badge;"],"mappings":";;;;AAoBA,MAAM,SAAS,EACb,MAAM;CACJ,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;AACT,EACF;AAQA,MAAM,cAAc;CAClB,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AAMA,MAAM,gBAAgB;CACpB,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;;;;;AAuBA,SAAS,MAAM,EACb,QACA,OAAO,WACP,UAAU,UACV,GAAG,WACU;CACb,MAAM,QAAQ,YAAY,WAAW,YAAY,QAAQ,cAAc;CACvE,OAAO,UAAU;EACf,gBAAgB;EAChB,OAAO;GACL,GAAG;GACH,GAAG,YAAY,MAAa,OAAO,MAAM,KAAK,GAAG,OAAK;EACxD;EACA;CACF,CAAC;AACH"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/badge/index.tsx"],"sourcesContent":["import * as stylex from '@stylexjs/stylex';\nimport type { RenderComponentProps } from '../../render/useRender';\nimport { useRender } from '../../render/useRender';\nimport { mergeStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, spacing, typography } from '../../tokens/design.tokens.stylex';\n\n// A badge states something about what it sits beside — a count, a status, a\n// version — and is never itself the thing you press. That is the whole line\n// between this and Chip: a chip is a two-state button announcing itself\n// through `aria-pressed`, so reaching for one to render a read-only label\n// puts a control in the accessibility tree that nothing can operate.\n//\n// Both variants carry a 1px border and `filled` makes it transparent rather\n// than dropping it, so a badge is the same size either way. Emphasising one\n// badge in a row otherwise nudges its neighbours by a pixel each side.\n//\n// Tabular figures are unconditional, for the same reason Currency sets them:\n// what ends up in a badge is mostly numeric — counts, versions, ports — and a\n// stack of them down a list wobbles on proportional digits. Text that happens\n// to carry no digits is unaffected.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x1xdvaa9\",\n ksu8eU: \"x1y0btm7\",\n kMzoRj: \"xmkeg23\",\n kB7OPa: \"x9f619\",\n k1xSpc: \"x3nfvp2\",\n kmuXW: \"x2lah0s\",\n kMv6JI: \"xs1w0rn\",\n kGuDYH: \"x1n35her\",\n kcqcaj: \"xss6m8b\",\n k63SB2: \"x1su64ma\",\n kOIVth: \"xzoy561\",\n kb6lSQ: \"xdlnduh\",\n kLWn49: \"xaomhve\",\n k8WAf4: \"xpg5a72\",\n kg3NbH: \"xn60lsn\",\n khDVqt: \"xuxw1ft\",\n $$css: true\n }\n};\n\n// `neutral` draws from the surface roles rather than a colour of its own,\n// which is what lets it state something without ranking it. The other three\n// name a direction, and are the same three Currency uses, plus primary for\n// the notable-but-not-good-or-bad case. `error` is deliberately absent:\n// negative already covers it, and offering both would leave the choice\n// between them to taste.\nconst filledTones = {\n negative: {\n kWkggS: \"xkz0cvf\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"xylzu7l\",\n $$css: true\n },\n neutral: {\n kWkggS: \"x1rrhr5q\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"x1w5n3ug\",\n $$css: true\n },\n positive: {\n kWkggS: \"xa3zu90\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"x1di7zbg\",\n $$css: true\n },\n primary: {\n kWkggS: \"xp89om9\",\n kVAM5u: \"x9r1u3d\",\n kMwMTN: \"xscdzaa\",\n $$css: true\n }\n};\n\n// Outlined takes the tone at full strength for both the rule and the text,\n// rather than the on-container pairing filled uses. There is no container\n// here for an on-colour to be legible against — the page is the background —\n// so the container roles would read as washed out.\nconst outlinedTones = {\n negative: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x1xdi1zw\",\n kMwMTN: \"x12gp0t\",\n $$css: true\n },\n neutral: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x133yhug\",\n kMwMTN: \"x1w5n3ug\",\n $$css: true\n },\n positive: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"xm76mbg\",\n kMwMTN: \"x4no3js\",\n $$css: true\n },\n primary: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"x1uajy3w\",\n kMwMTN: \"x1mxwv8y\",\n $$css: true\n }\n};\ntype BadgeProps = RenderComponentProps<'span'> & {\n /**\n * Which colour role the badge carries. `neutral` states without ranking;\n * the other three read as good, bad, or worth noticing.\n * @default 'neutral'\n */\n tone?: BadgeTone;\n /**\n * How much weight the badge pulls. `filled` sits on a container of its own;\n * `outlined` is a rule on the page, for a badge that should not compete\n * with the thing it labels.\n * @default 'filled'\n */\n variant?: BadgeVariant;\n};\ntype BadgeTone = 'negative' | 'neutral' | 'positive' | 'primary';\ntype BadgeVariant = 'filled' | 'outlined';\n\n/**\n * A short, read-only label attached to something else. It renders a `<span>`\n * and takes no interaction — for a label that can be selected, reach for Chip.\n */\nfunction Badge({\n render,\n tone = 'neutral',\n variant = 'filled',\n ...props\n}: BadgeProps) {\n const toned = variant === 'filled' ? filledTones[tone] : outlinedTones[tone];\n return useRender({\n defaultTagName: 'span',\n props: {\n ...props,\n ...mergeStyles(stylex.props(styles.base, toned), props)\n },\n render\n });\n}\nexport type { BadgeProps };\nexport default Badge;"],"mappings":";;;;AAqBA,MAAM,SAAS,EACb,MAAM;CACJ,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,QAAQ;CACR,OAAO;AACT,EACF;AAQA,MAAM,cAAc;CAClB,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AAMA,MAAM,gBAAgB;CACpB,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,SAAS;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;;;;;AAuBA,SAAS,MAAM,EACb,QACA,OAAO,WACP,UAAU,UACV,GAAG,WACU;CACb,MAAM,QAAQ,YAAY,WAAW,YAAY,QAAQ,cAAc;CACvE,OAAO,UAAU;EACf,gBAAgB;EAChB,OAAO;GACL,GAAG;GACH,GAAG,YAAY,MAAa,OAAO,MAAM,KAAK,GAAG,OAAK;EACxD;EACA;CACF,CAAC;AACH"}
@@ -1,6 +1,11 @@
1
- import { ButtonProps } from "@base-ui/react/button";
1
+ import { DOMAttributes, ReactNode } from "react";
2
+ import { ButtonProps, ButtonRenderProps, ClassNameOrFunction, StyleOrFunction } from "react-aria-components";
2
3
  //#region src/components/button/index.d.ts
4
+ type ButtonDOMProps = Omit<ButtonProps, 'children' | 'className' | 'style' | GlobalEventKey> & Pick<DOMAttributes<HTMLElement>, GlobalEventKey>;
3
5
  type ButtonProps$1 = {
6
+ children?: ReactNode;
7
+ /** A function may compute the class from the button's render state. */
8
+ className?: ClassNameOrFunction<ButtonState>;
4
9
  /**
5
10
  * Disables the press ripple. The hover/pressed background state layer is
6
11
  * unaffected.
@@ -8,32 +13,38 @@ type ButtonProps$1 = {
8
13
  */
9
14
  disableRipple?: boolean;
10
15
  /**
11
- * Whether the element `render` produces is a real `<button>`, which decides
12
- * whether Base UI supplies the button role, keyboard activation and
13
- * disabled semantics itself.
14
- *
15
- * Read off `render` when that is a plain element, so
16
- * `render={<a href="…" />}` needs nothing here. Worth setting only when
17
- * `render` is a component or a function, where the tag it will produce
18
- * cannot be read ahead of time.
19
- *
20
- * An anchor rendered this way is announced as a button rather than as a
21
- * link, since that is what Base UI's non-native mode applies. For a control
22
- * that should be announced as the link it is, reach for `Link`, or `Card`
23
- * with `render`, neither of which imposes button semantics.
24
- * @default true
16
+ * Where the button leads. Given one, the button is rendered as a link
17
+ * an `<a>`, announced as the link it is with the same styles and ripple.
18
+ * `render`, `type`, and the form and pending props apply to the button
19
+ * form only.
25
20
  */
26
- nativeButton?: boolean;
21
+ href?: string;
22
+ /** The link's `rel`, when `href` is set. */
23
+ rel?: string;
27
24
  /**
28
25
  * Control height: `xs` 32px, `md` 40px, `lg` 56px, `xl` 80px. Sizes other
29
26
  * than `md` also set their own inline padding.
30
27
  * @default 'md'
31
28
  */
32
- size?: 'lg' | 'md' | 'xl' | 'xs';
29
+ size?: ButtonSize;
30
+ /** A function may compute the style from the button's render state. */
31
+ style?: StyleOrFunction<ButtonState>;
32
+ /** The link's `target`, when `href` is set. */
33
+ target?: string;
33
34
  /** @default 'filled' */
34
- variant?: 'filled' | 'outlined' | 'text' | 'tonal';
35
- } & ButtonProps;
36
- declare function Button$1({ children, disabled, disableRipple, nativeButton, onClick, onContextMenu, onPointerCancel, onPointerDown, onPointerLeave, onPointerUp, size, variant, ...props }: ButtonProps$1): import("react").JSX.Element;
35
+ variant?: ButtonVariant;
36
+ } & ButtonDOMProps;
37
+ type ButtonSize = 'lg' | 'md' | 'xl' | 'xs';
38
+ type ButtonState = Pick<ButtonRenderProps, 'isDisabled' | 'isFocused' | 'isFocusVisible' | 'isHovered' | 'isPressed'>;
39
+ type ButtonVariant = 'filled' | 'outlined' | 'text' | 'tonal';
40
+ type GlobalEventKey = Exclude<keyof DOMAttributes<HTMLElement> & keyof ButtonProps, 'onBlur' | 'onClick' | 'onFocus'>;
41
+ /**
42
+ * The design's button, at four emphasis levels and four control heights.
43
+ * Given `href` it is a link with the same appearance. Every `aria-*` prop is
44
+ * forwarded to the element; React Aria alone would keep only the labelling
45
+ * ones.
46
+ */
47
+ declare function Button$1({ children, disableRipple, href, isDisabled, onClick, onContextMenu, onKeyDown, onKeyUp, onPointerCancel, onPointerDown, onPointerLeave, onPointerUp, rel, render, size, target, variant, ...props }: ButtonProps$1): import("react").JSX.Element;
37
48
  //#endregion
38
- export { type ButtonProps$1 as ButtonProps, Button$1 as default };
49
+ export { type ButtonDOMProps, type ButtonProps$1 as ButtonProps, type ButtonSize, type ButtonState, type ButtonVariant, Button$1 as default };
39
50
  //# sourceMappingURL=index.d.ts.map
@@ -1,9 +1,9 @@
1
1
  import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
2
2
  import { mergeStatefulStyles } from "../../styles/merge.js";
3
3
  import { useRipple } from "../../hooks/useRipple.js";
4
- import { rendersNativeButton } from "../../render/nativeButton.js";
4
+ import { ariaAttributesOf, buttonRenderer, linkRenderer } from "../../render/aria.js";
5
5
  import { jsxs } from "react/jsx-runtime";
6
- import { Button } from "@base-ui/react/button";
6
+ import { Button, Link } from "react-aria-components";
7
7
  //#region src/components/button/index.tsx
8
8
  const styles = {
9
9
  base: {
@@ -11,7 +11,7 @@ const styles = {
11
11
  kaIpWk: "x3y1ksm",
12
12
  kMzoRj: "xc342km",
13
13
  kB7OPa: "x9f619",
14
- kkrTdU: "x1s07b3s x1ypdohk",
14
+ kkrTdU: "x1ypdohk",
15
15
  k1xSpc: "x3nfvp2",
16
16
  kMv6JI: "xkknnkr",
17
17
  kGuDYH: "x14zspaw",
@@ -27,13 +27,23 @@ const styles = {
27
27
  kybGjl: "x1hl2dhg",
28
28
  $$css: true
29
29
  },
30
+ disabled: {
31
+ kkrTdU: "x1h6gzvc",
32
+ $$css: true
33
+ },
30
34
  filled: {
31
- kWkggS: "xm7dzmu x1n0xrd1 x16z974x x197hsbc xnety89",
32
- kGVxlE: "xfj5659 xv7jn9b x1gnnqk1",
33
- kMwMTN: "xhfb3ry xwjo7ld",
35
+ kWkggS: "xpky1to x16z974x xyf7a0q xnety89",
36
+ kGVxlE: "xpk4wdd x1955hzl x1gnnqk1",
37
+ kMwMTN: "xwjo7ld",
34
38
  kg3NbH: "xg0ps1x",
35
39
  $$css: true
36
40
  },
41
+ filledDisabled: {
42
+ kWkggS: "xepofeg",
43
+ kGVxlE: "x1gnnqk1",
44
+ kMwMTN: "x1xj74d8",
45
+ $$css: true
46
+ },
37
47
  lg: {
38
48
  kZKoxP: "xnnlda6",
39
49
  kGuDYH: "x1848etk",
@@ -46,27 +56,44 @@ const styles = {
46
56
  $$css: true
47
57
  },
48
58
  outlined: {
49
- kWkggS: "x117izkb x598nmq x1pi8zt8 xjbqb8w",
50
- kVAM5u: "xd8uu4e x1fwvt7v",
59
+ kWkggS: "x12vej0r x598nmq x14dcer4 xjbqb8w",
60
+ kVAM5u: "x1fwvt7v",
51
61
  ksu8eU: "x1y0btm7",
52
62
  kMzoRj: "xmkeg23",
53
- kMwMTN: "xhfb3ry x1mxwv8y",
63
+ kMwMTN: "x1mxwv8y",
54
64
  kg3NbH: "xg0ps1x",
55
65
  $$css: true
56
66
  },
67
+ outlinedDisabled: {
68
+ kWkggS: "xjbqb8w",
69
+ kVAM5u: "xrh88j3",
70
+ kMwMTN: "x1xj74d8",
71
+ $$css: true
72
+ },
57
73
  text: {
58
- kWkggS: "x117izkb x598nmq x1pi8zt8 xjbqb8w",
59
- kMwMTN: "xhfb3ry x1mxwv8y",
74
+ kWkggS: "x12vej0r x598nmq x14dcer4 xjbqb8w",
75
+ kMwMTN: "x1mxwv8y",
60
76
  kg3NbH: "x790zyz",
61
77
  $$css: true
62
78
  },
79
+ textDisabled: {
80
+ kWkggS: "xjbqb8w",
81
+ kMwMTN: "x1xj74d8",
82
+ $$css: true
83
+ },
63
84
  tonal: {
64
- kWkggS: "x1c3n7wt x1n0xrd1 x1uyy5zb xwypj8i xp89om9",
65
- kGVxlE: "xfj5659 xv7jn9b x1gnnqk1",
66
- kMwMTN: "xhfb3ry xscdzaa",
85
+ kWkggS: "xxwd9aw x1uyy5zb x1hei0q8 xp89om9",
86
+ kGVxlE: "xpk4wdd x1955hzl x1gnnqk1",
87
+ kMwMTN: "xscdzaa",
67
88
  kg3NbH: "xg0ps1x",
68
89
  $$css: true
69
90
  },
91
+ tonalDisabled: {
92
+ kWkggS: "xepofeg",
93
+ kGVxlE: "x1gnnqk1",
94
+ kMwMTN: "x1xj74d8",
95
+ $$css: true
96
+ },
70
97
  xl: {
71
98
  kZKoxP: "xwzfr38",
72
99
  kGuDYH: "xete2z3",
@@ -82,8 +109,20 @@ const styles = {
82
109
  $$css: true
83
110
  }
84
111
  };
85
- function Button$1({ children, disabled = false, disableRipple = false, nativeButton, onClick, onContextMenu, onPointerCancel, onPointerDown, onPointerLeave, onPointerUp, size = "md", variant = "filled", ...props$1 }) {
86
- const ripple = useRipple(!disableRipple, {
112
+ const disabledStyles = {
113
+ filled: styles.filledDisabled,
114
+ outlined: styles.outlinedDisabled,
115
+ text: styles.textDisabled,
116
+ tonal: styles.tonalDisabled
117
+ };
118
+ /**
119
+ * The design's button, at four emphasis levels and four control heights.
120
+ * Given `href` it is a link with the same appearance. Every `aria-*` prop is
121
+ * forwarded to the element; React Aria alone would keep only the labelling
122
+ * ones.
123
+ */
124
+ function Button$1({ children, disableRipple = false, href, isDisabled = false, onClick, onContextMenu, onKeyDown, onKeyUp, onPointerCancel, onPointerDown, onPointerLeave, onPointerUp, rel, render, size = "md", target, variant = "filled", ...props$1 }) {
125
+ const ripple = useRipple(!disableRipple && !isDisabled, {
87
126
  onClick,
88
127
  onContextMenu,
89
128
  onPointerCancel,
@@ -91,12 +130,29 @@ function Button$1({ children, disabled = false, disableRipple = false, nativeBut
91
130
  onPointerLeave,
92
131
  onPointerUp
93
132
  });
133
+ const styleProps = mergeStatefulStyles((state) => props(styles.base, styles[variant], styles[size], state.isDisabled && styles.disabled, state.isDisabled && disabledStyles[variant]), props$1);
134
+ const element = {
135
+ aria: ariaAttributesOf(props$1),
136
+ onKeyDown,
137
+ onKeyUp
138
+ };
139
+ if (href !== void 0) return /* @__PURE__ */ jsxs(Link, {
140
+ href,
141
+ isDisabled,
142
+ rel,
143
+ render: linkRenderer(element),
144
+ target,
145
+ ...ripple.handlers,
146
+ ...props$1,
147
+ ...styleProps,
148
+ children: [children, ripple.surface]
149
+ });
94
150
  return /* @__PURE__ */ jsxs(Button, {
95
- disabled,
96
- nativeButton: nativeButton ?? rendersNativeButton(props$1.render),
151
+ isDisabled,
152
+ render: buttonRenderer(element, render),
97
153
  ...ripple.handlers,
98
154
  ...props$1,
99
- ...mergeStatefulStyles(props(styles.base, styles[variant], styles[size]), props$1),
155
+ ...styleProps,
100
156
  children: [children, ripple.surface]
101
157
  });
102
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/button/index.tsx"],"sourcesContent":["import type { ButtonProps as BaseUIButtonProps } from '@base-ui/react/button';\nimport { Button as BaseUIButton } from '@base-ui/react/button';\nimport * as stylex from '@stylexjs/stylex';\nimport { useRipple } from '../../hooks/useRipple';\nimport { rendersNativeButton } from '../../render/nativeButton';\nimport { mergeStatefulStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, shadows, spacing, stateLayerOpacity, typography } from '../../tokens/design.tokens.stylex';\n\n// Each variant composites an 'on-color' (filled) or the brand color itself\n// (outlined/text, over a transparent container) at the interaction state's\n// opacity, rather than swapping in a separate hover/pressed color.\n// calc(<opacity> * 100%) turns the token's unitless 0-1 ratio into the\n// percentage color-mix() takes. Inlined rather than factored into a helper:\n// @stylexjs/babel-plugin only statically recognizes expressions written\n// directly as property values, and a call to an externally-defined function\n// isn't one of them.\n//\n// :hover and :active still match a disabled native <button>, and stylex's\n// fixed pseudo-class ordering places both after :disabled, so without the\n// :not(:disabled) guard a hovered/pressed disabled button would render with\n// the interaction color instead of the disabled one.\n//\n// Two independent axes, applied base -> variant -> size. The order is what\n// lets `text` keep its tighter inline padding at the default size while the\n// other sizes still set their own: `md` deliberately declares no\n// paddingInline, so a medium button falls through to whatever its variant\n// asked for, and every other size overrides it. That mirrors the source\n// design's own cascade, where the size classes are declared after the\n// variant ones and win on padding for exactly the same reason.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x3y1ksm\",\n kMzoRj: \"xc342km\",\n kB7OPa: \"x9f619\",\n kkrTdU: \"x1s07b3s x1ypdohk\",\n k1xSpc: \"x3nfvp2\",\n kMv6JI: \"xkknnkr\",\n kGuDYH: \"x14zspaw\",\n k63SB2: \"x1v9cpvo\",\n kOIVth: \"x126z3so\",\n kb6lSQ: \"x155f00i\",\n kLWn49: \"x1lz68p4\",\n kjBf7l: \"xrgy624\",\n kInvED: \"x1hl8ikr\",\n k3XXqK: \"x9v5kkp x1t137rt\",\n kMeerF: \"x1de99jn\",\n kVAEAm: \"x1n2onr6\",\n kybGjl: \"x1hl2dhg\",\n $$css: true\n },\n filled: {\n kWkggS: \"xm7dzmu x1n0xrd1 x16z974x x197hsbc xnety89\",\n kGVxlE: \"xfj5659 xv7jn9b x1gnnqk1\",\n kMwMTN: \"xhfb3ry xwjo7ld\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n lg: {\n kZKoxP: \"xnnlda6\",\n kGuDYH: \"x1848etk\",\n kLWn49: \"xh7dzej\",\n kg3NbH: \"x6e1k4i\",\n $$css: true\n },\n md: {\n kZKoxP: \"x1vqgdyp\",\n $$css: true\n },\n outlined: {\n kWkggS: \"x117izkb x598nmq x1pi8zt8 xjbqb8w\",\n kVAM5u: \"xd8uu4e x1fwvt7v\",\n ksu8eU: \"x1y0btm7\",\n kMzoRj: \"xmkeg23\",\n kMwMTN: \"xhfb3ry x1mxwv8y\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n text: {\n kWkggS: \"x117izkb x598nmq x1pi8zt8 xjbqb8w\",\n kMwMTN: \"xhfb3ry x1mxwv8y\",\n kg3NbH: \"x790zyz\",\n $$css: true\n },\n tonal: {\n kWkggS: \"x1c3n7wt x1n0xrd1 x1uyy5zb xwypj8i xp89om9\",\n kGVxlE: \"xfj5659 xv7jn9b x1gnnqk1\",\n kMwMTN: \"xhfb3ry xscdzaa\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n xl: {\n kZKoxP: \"xwzfr38\",\n kGuDYH: \"xete2z3\",\n kLWn49: \"x2divse\",\n kg3NbH: \"x1sdxjpb\",\n $$css: true\n },\n xs: {\n kZKoxP: \"x10w6t97\",\n kGuDYH: \"xkucv67\",\n kLWn49: \"xvzb9hk\",\n kg3NbH: \"x790zyz\",\n $$css: true\n }\n};\ntype ButtonProps = {\n /**\n * Disables the press ripple. The hover/pressed background state layer is\n * unaffected.\n * @default false\n */\n disableRipple?: boolean;\n /**\n * Whether the element `render` produces is a real `<button>`, which decides\n * whether Base UI supplies the button role, keyboard activation and\n * disabled semantics itself.\n *\n * Read off `render` when that is a plain element, so\n * `render={<a href=\"…\" />}` needs nothing here. Worth setting only when\n * `render` is a component or a function, where the tag it will produce\n * cannot be read ahead of time.\n *\n * An anchor rendered this way is announced as a button rather than as a\n * link, since that is what Base UI's non-native mode applies. For a control\n * that should be announced as the link it is, reach for `Link`, or `Card`\n * with `render`, neither of which imposes button semantics.\n * @default true\n */\n nativeButton?: boolean;\n /**\n * Control height: `xs` 32px, `md` 40px, `lg` 56px, `xl` 80px. Sizes other\n * than `md` also set their own inline padding.\n * @default 'md'\n */\n size?: 'lg' | 'md' | 'xl' | 'xs';\n /** @default 'filled' */\n variant?: 'filled' | 'outlined' | 'text' | 'tonal';\n} & BaseUIButtonProps;\nfunction Button({\n children,\n disabled = false,\n disableRipple = false,\n nativeButton,\n onClick,\n onContextMenu,\n onPointerCancel,\n onPointerDown,\n onPointerLeave,\n onPointerUp,\n size = 'md',\n variant = 'filled',\n ...props\n}: ButtonProps) {\n // `props` (className/style/render, etc.) is spread separately: `className`\n // and `style` there may be functions of render state, a Base UI extension\n // ripple's own handler-only merge doesn't need to know about. It is also\n // why the styles below merge through mergeStatefulStyles rather than the\n // plain mergeStyles.\n const ripple = useRipple<HTMLButtonElement>(!disableRipple, {\n onClick,\n onContextMenu,\n onPointerCancel,\n onPointerDown,\n onPointerLeave,\n onPointerUp\n });\n return <BaseUIButton disabled={disabled}\n // Destructured out of `props` above rather than left to flow through\n // with it: spread later, an absent `nativeButton` would arrive as an\n // explicit `undefined` and overwrite what is inferred here.\n nativeButton={nativeButton ?? rendersNativeButton(props.render)} {...ripple.handlers} {...props} {...mergeStatefulStyles(stylex.props(styles.base, styles[variant], styles[size]), props)}>\n {children}\n {ripple.surface}\n </BaseUIButton>;\n}\nexport type { ButtonProps };\nexport default Button;"],"mappings":";;;;;;;AA8BA,MAAM,SAAS;CACb,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AAkCA,SAAS,SAAO,EACd,UACA,WAAW,OACX,gBAAgB,OAChB,cACA,SACA,eACA,iBACA,eACA,gBACA,aACA,OAAO,MACP,UAAU,UACV,GAAG,WACW;CAMd,MAAM,SAAS,UAA6B,CAAC,eAAe;EAC1D;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,OAAO,qBAAC,QAAD;EAAwB;EAI/B,cAAc,gBAAgB,oBAAoB,QAAM,MAAM;EAAG,GAAI,OAAO;EAAU,GAAI;EAAO,GAAI,oBAAoB,MAAa,OAAO,MAAM,OAAO,UAAU,OAAO,KAAK,GAAG,OAAK;EAJjL,UAAA,CAKF,UACA,OAAO,OACI;;AAClB"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/components/button/index.tsx"],"sourcesContent":["import type { DOMAttributes, ReactNode } from 'react';\nimport type { ButtonRenderProps, ClassNameOrFunction, FocusableElement, ButtonProps as RACButtonProps, StyleOrFunction } from 'react-aria-components';\nimport * as stylex from '@stylexjs/stylex';\nimport { Button as RACButton, Link as RACLink } from 'react-aria-components';\nimport { useRipple } from '../../hooks/useRipple';\nimport { ariaAttributesOf, buttonRenderer, linkRenderer } from '../../render/aria';\nimport { mergeStatefulStyles } from '../../styles/merge';\nimport '../../tokens/design.tokens.stylex';\nimport { colors, radii, shadows, spacing, stateLayerOpacity, typography } from '../../tokens/design.tokens.stylex';\n\n// Each variant composites an 'on-color' (filled) or the brand color itself\n// (outlined/text, over a transparent container) at the interaction state's\n// opacity, rather than swapping in a separate hover/pressed color.\n// calc(<opacity> * 100%) turns the token's unitless 0-1 ratio into the\n// percentage color-mix() takes. Inlined rather than factored into a helper:\n// @stylexjs/babel-plugin only statically recognizes expressions written\n// directly as property values, and a call to an externally-defined function\n// isn't one of them.\n//\n// Disabled is a style of its own per variant rather than a `:disabled`\n// branch inside each property: a button given `href` renders as a link,\n// which React Aria turns into a <span> while disabled, and neither matches\n// the pseudo-class. The disabled styles are applied last from the render\n// state's `isDisabled`, and StyleX replaces a property whole, so they also\n// take the hover and pressed branches with them — which is what keeps a\n// hovered disabled button from lighting up.\n//\n// Two independent axes, applied base -> variant -> size. The order is what\n// lets `text` keep its tighter inline padding at the default size while the\n// other sizes still set their own: `md` deliberately declares no\n// paddingInline, so a medium button falls through to whatever its variant\n// asked for, and every other size overrides it. That mirrors the source\n// design's own cascade, where the size classes are declared after the\n// variant ones and win on padding for exactly the same reason.\nconst styles = {\n base: {\n kGNEyG: \"x6s0dn4\",\n kaIpWk: \"x3y1ksm\",\n kMzoRj: \"xc342km\",\n kB7OPa: \"x9f619\",\n kkrTdU: \"x1ypdohk\",\n k1xSpc: \"x3nfvp2\",\n kMv6JI: \"xkknnkr\",\n kGuDYH: \"x14zspaw\",\n k63SB2: \"x1v9cpvo\",\n kOIVth: \"x126z3so\",\n kb6lSQ: \"x155f00i\",\n kLWn49: \"x1lz68p4\",\n kjBf7l: \"xrgy624\",\n kInvED: \"x1hl8ikr\",\n k3XXqK: \"x9v5kkp x1t137rt\",\n kMeerF: \"x1de99jn\",\n kVAEAm: \"x1n2onr6\",\n kybGjl: \"x1hl2dhg\",\n $$css: true\n },\n disabled: {\n kkrTdU: \"x1h6gzvc\",\n $$css: true\n },\n filled: {\n kWkggS: \"xpky1to x16z974x xyf7a0q xnety89\",\n kGVxlE: \"xpk4wdd x1955hzl x1gnnqk1\",\n kMwMTN: \"xwjo7ld\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n filledDisabled: {\n kWkggS: \"xepofeg\",\n kGVxlE: \"x1gnnqk1\",\n kMwMTN: \"x1xj74d8\",\n $$css: true\n },\n lg: {\n kZKoxP: \"xnnlda6\",\n kGuDYH: \"x1848etk\",\n kLWn49: \"xh7dzej\",\n kg3NbH: \"x6e1k4i\",\n $$css: true\n },\n md: {\n kZKoxP: \"x1vqgdyp\",\n $$css: true\n },\n outlined: {\n kWkggS: \"x12vej0r x598nmq x14dcer4 xjbqb8w\",\n kVAM5u: \"x1fwvt7v\",\n ksu8eU: \"x1y0btm7\",\n kMzoRj: \"xmkeg23\",\n kMwMTN: \"x1mxwv8y\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n outlinedDisabled: {\n kWkggS: \"xjbqb8w\",\n kVAM5u: \"xrh88j3\",\n kMwMTN: \"x1xj74d8\",\n $$css: true\n },\n text: {\n kWkggS: \"x12vej0r x598nmq x14dcer4 xjbqb8w\",\n kMwMTN: \"x1mxwv8y\",\n kg3NbH: \"x790zyz\",\n $$css: true\n },\n textDisabled: {\n kWkggS: \"xjbqb8w\",\n kMwMTN: \"x1xj74d8\",\n $$css: true\n },\n tonal: {\n kWkggS: \"xxwd9aw x1uyy5zb x1hei0q8 xp89om9\",\n kGVxlE: \"xpk4wdd x1955hzl x1gnnqk1\",\n kMwMTN: \"xscdzaa\",\n kg3NbH: \"xg0ps1x\",\n $$css: true\n },\n tonalDisabled: {\n kWkggS: \"xepofeg\",\n kGVxlE: \"x1gnnqk1\",\n kMwMTN: \"x1xj74d8\",\n $$css: true\n },\n xl: {\n kZKoxP: \"xwzfr38\",\n kGuDYH: \"xete2z3\",\n kLWn49: \"x2divse\",\n kg3NbH: \"x1sdxjpb\",\n $$css: true\n },\n xs: {\n kZKoxP: \"x10w6t97\",\n kGuDYH: \"xkucv67\",\n kLWn49: \"xvzb9hk\",\n kg3NbH: \"x790zyz\",\n $$css: true\n }\n};\nconst disabledStyles = {\n filled: styles.filledDisabled,\n outlined: styles.outlinedDisabled,\n text: styles.textDisabled,\n tonal: styles.tonalDisabled\n};\ntype ButtonDOMProps = Omit<RACButtonProps, 'children' | 'className' | 'style' | GlobalEventKey> & Pick<DOMAttributes<HTMLElement>, GlobalEventKey>;\ntype ButtonProps = {\n children?: ReactNode;\n /** A function may compute the class from the button's render state. */\n className?: ClassNameOrFunction<ButtonState>;\n /**\n * Disables the press ripple. The hover/pressed background state layer is\n * unaffected.\n * @default false\n */\n disableRipple?: boolean;\n /**\n * Where the button leads. Given one, the button is rendered as a link —\n * an `<a>`, announced as the link it is — with the same styles and ripple.\n * `render`, `type`, and the form and pending props apply to the button\n * form only.\n */\n href?: string;\n /** The link's `rel`, when `href` is set. */\n rel?: string;\n /**\n * Control height: `xs` 32px, `md` 40px, `lg` 56px, `xl` 80px. Sizes other\n * than `md` also set their own inline padding.\n * @default 'md'\n */\n size?: ButtonSize;\n /** A function may compute the style from the button's render state. */\n style?: StyleOrFunction<ButtonState>;\n /** The link's `target`, when `href` is set. */\n target?: string;\n /** @default 'filled' */\n variant?: ButtonVariant;\n} & ButtonDOMProps;\ntype ButtonSize = 'lg' | 'md' | 'xl' | 'xs';\n\n// The render state both of React Aria's elements share. A className or\n// style function written against it serves the button and the link alike;\n// `isPending` belongs to the button alone, and `isCurrent` to the link.\ntype ButtonState = Pick<ButtonRenderProps, 'isDisabled' | 'isFocused' | 'isFocusVisible' | 'isHovered' | 'isPressed'>;\ntype ButtonVariant = 'filled' | 'outlined' | 'text' | 'tonal';\n\n// React Aria types the global DOM events — pointer, mouse, touch, wheel and\n// the rest — against the element each component renders, and a handler\n// written for a <button> does not type-check against an <a>. The keys are\n// retyped against HTMLElement here so one set of props serves both forms;\n// the events React Aria defines itself (press, focus, keyboard) keep its\n// types, since it hands those its own event objects.\ntype GlobalEventKey = Exclude<keyof DOMAttributes<HTMLElement> & keyof RACButtonProps, 'onBlur' | 'onClick' | 'onFocus'>;\n\n/**\n * The design's button, at four emphasis levels and four control heights.\n * Given `href` it is a link with the same appearance. Every `aria-*` prop is\n * forwarded to the element; React Aria alone would keep only the labelling\n * ones.\n */\nfunction Button({\n children,\n disableRipple = false,\n href,\n isDisabled = false,\n onClick,\n onContextMenu,\n onKeyDown,\n onKeyUp,\n onPointerCancel,\n onPointerDown,\n onPointerLeave,\n onPointerUp,\n rel,\n render,\n size = 'md',\n target,\n variant = 'filled',\n ...props\n}: ButtonProps) {\n // `props` (className/style, etc.) is spread separately: `className` and\n // `style` there may be functions of render state, which ripple's own\n // handler-only merge doesn't need to know about. It is also why the styles\n // below merge through mergeStatefulStyles rather than the plain\n // mergeStyles. The ripple is off while disabled: React Aria still forwards\n // pointer events to a disabled element, and a press that changes nothing\n // should not look like one.\n const ripple = useRipple<FocusableElement>(!disableRipple && !isDisabled, {\n onClick,\n onContextMenu,\n onPointerCancel,\n onPointerDown,\n onPointerLeave,\n onPointerUp\n });\n const styleProps = mergeStatefulStyles((state: ButtonState) => stylex.props(styles.base, styles[variant], styles[size], state.isDisabled && styles.disabled, state.isDisabled && disabledStyles[variant]), props);\n const element = {\n aria: ariaAttributesOf(props),\n onKeyDown,\n onKeyUp\n };\n if (href !== undefined) {\n return <RACLink href={href} isDisabled={isDisabled} rel={rel} render={linkRenderer(element)} target={target} {...ripple.handlers} {...props} {...styleProps}>\n {children}\n {ripple.surface}\n </RACLink>;\n }\n return <RACButton isDisabled={isDisabled} render={buttonRenderer(element, render)} {...ripple.handlers} {...props} {...styleProps}>\n {children}\n {ripple.surface}\n </RACButton>;\n}\nexport type { ButtonDOMProps, ButtonProps, ButtonSize, ButtonState, ButtonVariant };\nexport default Button;"],"mappings":";;;;;;;AAkCA,MAAM,SAAS;CACb,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,OAAO;CACT;CACA,QAAQ;EACN,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,gBAAgB;EACd,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,OAAO;CACT;CACA,UAAU;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,kBAAkB;EAChB,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,MAAM;EACJ,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,cAAc;EACZ,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,OAAO;EACL,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,eAAe;EACb,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;CACA,IAAI;EACF,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,OAAO;CACT;AACF;AACA,MAAM,iBAAiB;CACrB,QAAQ,OAAO;CACf,UAAU,OAAO;CACjB,MAAM,OAAO;CACb,OAAO,OAAO;AAChB;;;;;;;AAwDA,SAAS,SAAO,EACd,UACA,gBAAgB,OAChB,MACA,aAAa,OACb,SACA,eACA,WACA,SACA,iBACA,eACA,gBACA,aACA,KACA,QACA,OAAO,MACP,QACA,UAAU,UACV,GAAG,WACW;CAQd,MAAM,SAAS,UAA4B,CAAC,iBAAiB,CAAC,YAAY;EACxE;EACA;EACA;EACA;EACA;EACA;CACF,CAAC;CACD,MAAM,aAAa,qBAAqB,UAAuB,MAAa,OAAO,MAAM,OAAO,UAAU,OAAO,OAAO,MAAM,cAAc,OAAO,UAAU,MAAM,cAAc,eAAe,QAAQ,GAAG,OAAK;CAChN,MAAM,UAAU;EACd,MAAM,iBAAiB,OAAK;EAC5B;EACA;CACF;CACA,IAAI,SAAS,KAAA,GACX,OAAO,qBAAC,MAAD;EAAe;EAAkB;EAAiB;EAAK,QAAQ,aAAa,OAAO;EAAW;EAAQ,GAAI,OAAO;EAAU,GAAI;EAAO,GAAI;EAA1I,UAAA,CACF,UACA,OAAO,OACD;;CAEb,OAAO,qBAAC,QAAD;EAAuB;EAAY,QAAQ,eAAe,SAAS,MAAM;EAAG,GAAI,OAAO;EAAU,GAAI;EAAO,GAAI;EAAhH,UAAA,CACF,UACA,OAAO,OACC;;AACf"}
@@ -1,4 +1,4 @@
1
- import { useRender } from "@base-ui/react/use-render";
1
+ import { RenderComponentProps } from "../../render/useRender.js";
2
2
  //#region src/components/card/index.d.ts
3
3
  type CardProps = {
4
4
  /**
@@ -21,7 +21,7 @@ type CardProps = {
21
21
  * @default 'elevated'
22
22
  */
23
23
  variant?: CardVariant;
24
- } & useRender.ComponentProps<'div'>;
24
+ } & RenderComponentProps<'div'>;
25
25
  type CardVariant = 'elevated' | 'filled' | 'outlined';
26
26
  /**
27
27
  * A surface holding related content. `render` decides which element that
@@ -1,7 +1,7 @@
1
1
  import { props } from "../../node_modules/@stylexjs/stylex/lib/es/stylex.js";
2
+ import { useRender } from "../../render/useRender.js";
2
3
  import { mergeStyles } from "../../styles/merge.js";
3
4
  import { useRipple } from "../../hooks/useRipple.js";
4
- import { useRender } from "@base-ui/react/use-render";
5
5
  import { Fragment, jsxs } from "react/jsx-runtime";
6
6
  //#region src/components/card/index.tsx
7
7
  const styles = {