@mitcsutt/kiln-ui 0.2.1 → 0.3.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.
- package/README.md +6 -6
- package/dist/assets/fonts/archivo-latin-ext-wdth-italic.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-ext-wdth-normal.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-wdth-italic.woff2 +0 -0
- package/dist/assets/fonts/archivo-latin-wdth-normal.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-italic.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-italic.woff2 +0 -0
- package/dist/assets/fonts/atkinson-hyperlegible-mono-latin-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-400-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-400-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-700-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-latin-700-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-400-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-400-normal.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-700-italic.woff2 +0 -0
- package/dist/assets/fonts/b612-mono-latin-700-normal.woff2 +0 -0
- package/dist/assets/fonts/golos-text-latin-ext-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/golos-text-latin-wght-normal.woff2 +0 -0
- package/dist/assets/fonts/licenses/archivo.txt +93 -0
- package/dist/assets/fonts/licenses/atkinson-hyperlegible-mono.txt +93 -0
- package/dist/assets/fonts/licenses/b612-mono.txt +93 -0
- package/dist/assets/fonts/licenses/b612.txt +93 -0
- package/dist/assets/fonts/licenses/golos-text.txt +93 -0
- package/dist/assets/fonts/licenses/shantell-sans.txt +93 -0
- package/dist/assets/fonts/shantell-sans-latin-ext-full-italic.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-ext-full-normal.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-full-italic.woff2 +0 -0
- package/dist/assets/fonts/shantell-sans-latin-full-normal.woff2 +0 -0
- package/dist/components/actions/Button/Button.d.ts +8 -0
- package/dist/components/actions/Button/Button.js.map +1 -1
- package/dist/components/actions/ChipGroup/ChipGroup.d.ts +9 -0
- package/dist/components/actions/ChipGroup/ChipGroup.js.map +1 -1
- package/dist/components/actions/IconButton/IconButton.d.ts +8 -0
- package/dist/components/actions/IconButton/IconButton.js.map +1 -1
- package/dist/components/actions/ModeToggle/ModeToggle.d.ts +8 -0
- package/dist/components/actions/ModeToggle/ModeToggle.js.map +1 -1
- package/dist/components/actions/SegmentedControl/SegmentedControl.d.ts +9 -0
- package/dist/components/actions/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/components/actions/ToggleChip/ToggleChip.d.ts +9 -0
- package/dist/components/actions/ToggleChip/ToggleChip.js.map +1 -1
- package/dist/components/display/Avatar/Avatar.d.ts +11 -1
- package/dist/components/display/Avatar/Avatar.js.map +1 -1
- package/dist/components/display/AvatarGroup/AvatarGroup.d.ts +7 -0
- package/dist/components/display/AvatarGroup/AvatarGroup.js.map +1 -1
- package/dist/components/display/Badge/Badge.d.ts +7 -0
- package/dist/components/display/Badge/Badge.js.map +1 -1
- package/dist/components/display/Card/Card.js.map +1 -1
- package/dist/components/display/CodeBlock/CodeBlock.d.ts +9 -0
- package/dist/components/display/CodeBlock/CodeBlock.js.map +1 -1
- package/dist/components/display/DataList/DataList.js.map +1 -1
- package/dist/components/display/Delta/Delta.d.ts +9 -0
- package/dist/components/display/Delta/Delta.js.map +1 -1
- package/dist/components/display/EmptyState/EmptyState.d.ts +8 -0
- package/dist/components/display/EmptyState/EmptyState.js.map +1 -1
- package/dist/components/display/List/List.js.map +1 -1
- package/dist/components/display/Marquee/Marquee.d.ts +8 -0
- package/dist/components/display/Marquee/Marquee.js.map +1 -1
- package/dist/components/display/Media/Media.d.ts +7 -0
- package/dist/components/display/Media/Media.js.map +1 -1
- package/dist/components/display/Meter/Meter.d.ts +9 -0
- package/dist/components/display/Meter/Meter.js.map +1 -1
- package/dist/components/display/Progress/Progress.d.ts +8 -0
- package/dist/components/display/Progress/Progress.js.map +1 -1
- package/dist/components/display/Skeleton/Skeleton.js.map +1 -1
- package/dist/components/display/Stamp/Stamp.d.ts +8 -0
- package/dist/components/display/Stamp/Stamp.js.map +1 -1
- package/dist/components/display/Stat/Stat.d.ts +8 -0
- package/dist/components/display/Stat/Stat.js.map +1 -1
- package/dist/components/display/Table/Table.js.map +1 -1
- package/dist/components/display/Tag/Tag.d.ts +7 -0
- package/dist/components/display/Tag/Tag.js.map +1 -1
- package/dist/components/feedback/Alert/Alert.d.ts +10 -0
- package/dist/components/feedback/Alert/Alert.js.map +1 -1
- package/dist/components/feedback/LiveIndicator/LiveIndicator.d.ts +7 -0
- package/dist/components/feedback/LiveIndicator/LiveIndicator.js.map +1 -1
- package/dist/components/feedback/RelativeTime/RelativeTime.d.ts +8 -0
- package/dist/components/feedback/RelativeTime/RelativeTime.js.map +1 -1
- package/dist/components/feedback/Spinner/Spinner.d.ts +10 -1
- package/dist/components/feedback/Spinner/Spinner.js.map +1 -1
- package/dist/components/feedback/StatusDot/StatusDot.d.ts +10 -1
- package/dist/components/feedback/StatusDot/StatusDot.js.map +1 -1
- package/dist/components/inputs/AmountField/AmountField.d.ts +7 -0
- package/dist/components/inputs/AmountField/AmountField.js.map +1 -1
- package/dist/components/inputs/AmountInput/AmountInput.d.ts +8 -0
- package/dist/components/inputs/AmountInput/AmountInput.js.map +1 -1
- package/dist/components/inputs/Checkbox/Checkbox.d.ts +8 -0
- package/dist/components/inputs/Checkbox/Checkbox.js.map +1 -1
- package/dist/components/inputs/CheckboxField/CheckboxField.d.ts +7 -0
- package/dist/components/inputs/CheckboxField/CheckboxField.js.map +1 -1
- package/dist/components/inputs/CheckboxGroup/CheckboxGroup.d.ts +8 -0
- package/dist/components/inputs/CheckboxGroup/CheckboxGroup.js.map +1 -1
- package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.d.ts +7 -0
- package/dist/components/inputs/CheckboxGroupField/CheckboxGroupField.js.map +1 -1
- package/dist/components/inputs/ChipGroupField/ChipGroupField.d.ts +7 -0
- package/dist/components/inputs/ChipGroupField/ChipGroupField.js.map +1 -1
- package/dist/components/inputs/ChoiceCards/ChoiceCards.d.ts +9 -0
- package/dist/components/inputs/ChoiceCards/ChoiceCards.js.map +1 -1
- package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.d.ts +7 -0
- package/dist/components/inputs/ChoiceCardsField/ChoiceCardsField.js.map +1 -1
- package/dist/components/inputs/ColorField/ColorField.d.ts +6 -0
- package/dist/components/inputs/ColorField/ColorField.js.map +1 -1
- package/dist/components/inputs/ColorInput/ColorInput.d.ts +8 -0
- package/dist/components/inputs/ColorInput/ColorInput.js.map +1 -1
- package/dist/components/inputs/Combobox/Combobox.d.ts +9 -0
- package/dist/components/inputs/Combobox/Combobox.js.map +1 -1
- package/dist/components/inputs/ComboboxField/ComboboxField.d.ts +7 -0
- package/dist/components/inputs/ComboboxField/ComboboxField.js.map +1 -1
- package/dist/components/inputs/DateRangeField/DateRangeField.d.ts +7 -0
- package/dist/components/inputs/DateRangeField/DateRangeField.js.map +1 -1
- package/dist/components/inputs/Field/Field.d.ts +26 -0
- package/dist/components/inputs/Field/Field.js.map +1 -1
- package/dist/components/inputs/Fieldset/Fieldset.d.ts +8 -0
- package/dist/components/inputs/Fieldset/Fieldset.js.map +1 -1
- package/dist/components/inputs/FileDrop/FileDrop.d.ts +10 -0
- package/dist/components/inputs/FileDrop/FileDrop.js.map +1 -1
- package/dist/components/inputs/FileField/FileField.d.ts +7 -0
- package/dist/components/inputs/FileField/FileField.js.map +1 -1
- package/dist/components/inputs/Input/Input.d.ts +9 -0
- package/dist/components/inputs/Input/Input.js.map +1 -1
- package/dist/components/inputs/NumberField/NumberField.d.ts +7 -0
- package/dist/components/inputs/NumberField/NumberField.js.map +1 -1
- package/dist/components/inputs/NumberInput/NumberInput.d.ts +8 -0
- package/dist/components/inputs/NumberInput/NumberInput.js.map +1 -1
- package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.d.ts +7 -0
- package/dist/components/inputs/OneTimeCodeField/OneTimeCodeField.js.map +1 -1
- package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.d.ts +8 -0
- package/dist/components/inputs/OneTimeCodeInput/OneTimeCodeInput.js.map +1 -1
- package/dist/components/inputs/PasswordField/PasswordField.d.ts +7 -0
- package/dist/components/inputs/PasswordField/PasswordField.js.map +1 -1
- package/dist/components/inputs/PasswordInput/PasswordInput.d.ts +10 -0
- package/dist/components/inputs/PasswordInput/PasswordInput.js.map +1 -1
- package/dist/components/inputs/RadioGroup/RadioGroup.d.ts +9 -0
- package/dist/components/inputs/RadioGroup/RadioGroup.js.map +1 -1
- package/dist/components/inputs/RadioGroupField/RadioGroupField.d.ts +8 -0
- package/dist/components/inputs/RadioGroupField/RadioGroupField.js.map +1 -1
- package/dist/components/inputs/RangeSlider/RangeSlider.d.ts +8 -0
- package/dist/components/inputs/RangeSlider/RangeSlider.js.map +1 -1
- package/dist/components/inputs/RangeSliderField/RangeSliderField.d.ts +7 -0
- package/dist/components/inputs/RangeSliderField/RangeSliderField.js.map +1 -1
- package/dist/components/inputs/Rating/Rating.d.ts +8 -0
- package/dist/components/inputs/Rating/Rating.js.map +1 -1
- package/dist/components/inputs/RatingField/RatingField.d.ts +7 -0
- package/dist/components/inputs/RatingField/RatingField.js.map +1 -1
- package/dist/components/inputs/SegmentedField/SegmentedField.d.ts +7 -0
- package/dist/components/inputs/SegmentedField/SegmentedField.js.map +1 -1
- package/dist/components/inputs/Select/Select.d.ts +9 -0
- package/dist/components/inputs/Select/Select.js.map +1 -1
- package/dist/components/inputs/SelectField/SelectField.d.ts +7 -0
- package/dist/components/inputs/SelectField/SelectField.js.map +1 -1
- package/dist/components/inputs/Slider/Slider.d.ts +8 -0
- package/dist/components/inputs/Slider/Slider.js.map +1 -1
- package/dist/components/inputs/SliderField/SliderField.d.ts +7 -0
- package/dist/components/inputs/SliderField/SliderField.js.map +1 -1
- package/dist/components/inputs/Switch/Switch.d.ts +8 -0
- package/dist/components/inputs/Switch/Switch.js.map +1 -1
- package/dist/components/inputs/SwitchField/SwitchField.d.ts +8 -0
- package/dist/components/inputs/SwitchField/SwitchField.js.map +1 -1
- package/dist/components/inputs/TagsField/TagsField.d.ts +6 -0
- package/dist/components/inputs/TagsField/TagsField.js.map +1 -1
- package/dist/components/inputs/TagsInput/TagsInput.d.ts +8 -0
- package/dist/components/inputs/TagsInput/TagsInput.js.map +1 -1
- package/dist/components/inputs/TextField/TextField.d.ts +8 -0
- package/dist/components/inputs/TextField/TextField.js.map +1 -1
- package/dist/components/inputs/Textarea/Textarea.d.ts +8 -0
- package/dist/components/inputs/Textarea/Textarea.js.map +1 -1
- package/dist/components/inputs/TextareaField/TextareaField.d.ts +7 -0
- package/dist/components/inputs/TextareaField/TextareaField.js.map +1 -1
- package/dist/components/layout/ActionBar/ActionBar.d.ts +7 -0
- package/dist/components/layout/ActionBar/ActionBar.js.map +1 -1
- package/dist/components/layout/AppShell/AppShell.js.map +1 -1
- package/dist/components/layout/AspectRatio/AspectRatio.d.ts +8 -0
- package/dist/components/layout/AspectRatio/AspectRatio.js.map +1 -1
- package/dist/components/layout/Box/Box.d.ts +8 -0
- package/dist/components/layout/Box/Box.js.map +1 -1
- package/dist/components/layout/Container/Container.d.ts +17 -0
- package/dist/components/layout/Container/Container.js.map +1 -1
- package/dist/components/layout/Divider/Divider.d.ts +7 -0
- package/dist/components/layout/Divider/Divider.js.map +1 -1
- package/dist/components/layout/Grid/Grid.d.ts +9 -0
- package/dist/components/layout/Grid/Grid.js.map +1 -1
- package/dist/components/layout/Inline/Inline.d.ts +8 -0
- package/dist/components/layout/Inline/Inline.js.map +1 -1
- package/dist/components/layout/Section/Section.d.ts +15 -0
- package/dist/components/layout/Section/Section.js.map +1 -1
- package/dist/components/layout/Split/Split.d.ts +9 -0
- package/dist/components/layout/Split/Split.js.map +1 -1
- package/dist/components/layout/Stack/Stack.d.ts +12 -0
- package/dist/components/layout/Stack/Stack.js.map +1 -1
- package/dist/components/layout/VisuallyHidden/VisuallyHidden.d.ts +7 -0
- package/dist/components/layout/VisuallyHidden/VisuallyHidden.js.map +1 -1
- package/dist/components/navigation/Accordion/Accordion.d.ts +7 -0
- package/dist/components/navigation/Accordion/Accordion.js.map +1 -1
- package/dist/components/navigation/BottomNav/BottomNav.d.ts +9 -0
- package/dist/components/navigation/BottomNav/BottomNav.js.map +1 -1
- package/dist/components/navigation/NavLinks/NavLinks.d.ts +9 -0
- package/dist/components/navigation/NavLinks/NavLinks.js.map +1 -1
- package/dist/components/navigation/Stepper/Stepper.d.ts +7 -0
- package/dist/components/navigation/Stepper/Stepper.js.map +1 -1
- package/dist/components/navigation/Tabs/Tabs.d.ts +8 -0
- package/dist/components/navigation/Tabs/Tabs.js.map +1 -1
- package/dist/components/overlays/Dialog/Dialog.d.ts +13 -0
- package/dist/components/overlays/Dialog/Dialog.js.map +1 -1
- package/dist/components/overlays/DropdownMenu/DropdownMenu.d.ts +8 -0
- package/dist/components/overlays/DropdownMenu/DropdownMenu.js.map +1 -1
- package/dist/components/overlays/Popover/Popover.d.ts +7 -0
- package/dist/components/overlays/Popover/Popover.js.map +1 -1
- package/dist/components/overlays/Sheet/Sheet.d.ts +8 -0
- package/dist/components/overlays/Sheet/Sheet.js.map +1 -1
- package/dist/components/overlays/Tooltip/Tooltip.d.ts +8 -0
- package/dist/components/overlays/Tooltip/Tooltip.js.map +1 -1
- package/dist/components/typography/Amount/Amount.d.ts +8 -0
- package/dist/components/typography/Amount/Amount.js.map +1 -1
- package/dist/components/typography/Code/Code.d.ts +8 -0
- package/dist/components/typography/Code/Code.js.map +1 -1
- package/dist/components/typography/Heading/Heading.d.ts +8 -0
- package/dist/components/typography/Heading/Heading.js.map +1 -1
- package/dist/components/typography/Kbd/Kbd.d.ts +6 -0
- package/dist/components/typography/Kbd/Kbd.js.map +1 -1
- package/dist/components/typography/Link/Link.d.ts +9 -0
- package/dist/components/typography/Link/Link.js.map +1 -1
- package/dist/components/typography/Numeral/Numeral.d.ts +8 -0
- package/dist/components/typography/Numeral/Numeral.js.map +1 -1
- package/dist/components/typography/Prose/Prose.d.ts +9 -0
- package/dist/components/typography/Prose/Prose.js.map +1 -1
- package/dist/components/typography/Quote/Quote.d.ts +7 -0
- package/dist/components/typography/Quote/Quote.js.map +1 -1
- package/dist/components/typography/SectionHeader/SectionHeader.d.ts +8 -0
- package/dist/components/typography/SectionHeader/SectionHeader.js.map +1 -1
- package/dist/components/typography/Text/Text.d.ts +8 -0
- package/dist/components/typography/Text/Text.js.map +1 -1
- package/dist/icons/Icon.d.ts +1 -1
- package/dist/icons/Icon.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme/ThemeProvider.d.ts +4 -4
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/theme/themes.d.ts +1 -1
- package/dist/theme/themes.js +15 -3
- package/dist/theme/themes.js.map +1 -1
- package/dist/themes/fiesta.css +1 -1
- package/dist/themes/flightdeck.css +1 -0
- package/dist/themes/ledger.css +1 -1
- package/dist/themes/monograph.css +1 -1
- package/dist/themes/riso.css +1 -0
- package/package.json +4 -2
- package/skills/custom-theme/SKILL.md +45 -43
- package/skills/custom-theme/references/colour.md +1 -1
- package/skills/custom-theme/references/motion.md +2 -2
- package/skills/custom-theme/references/tokens.md +42 -42
- package/skills/custom-theme/references/type.md +13 -10
- package/skills/design-rules/SKILL.md +31 -3
- package/skills/layout-composition/SKILL.md +27 -88
- package/skills/layout-composition/references/action-bar.md +3 -3
- package/skills/layout-composition/references/app-shell.md +10 -3
- package/skills/layout-composition/references/aspect-ratio.md +2 -0
- package/skills/layout-composition/references/box.md +6 -6
- package/skills/layout-composition/references/container.md +14 -12
- package/skills/layout-composition/references/divider.md +4 -4
- package/skills/layout-composition/references/section.md +69 -0
- package/skills/layout-composition/references/visually-hidden.md +3 -3
- package/skills/setup-and-theming/SKILL.md +10 -6
- package/skills/setup-and-theming/references/fiesta.md +3 -1
- package/skills/setup-and-theming/references/flightdeck.md +39 -0
- package/skills/setup-and-theming/references/ledger.md +7 -7
- package/skills/setup-and-theming/references/monograph.md +4 -0
- package/skills/setup-and-theming/references/paper.md +7 -7
- package/skills/setup-and-theming/references/riso.md +32 -0
|
@@ -23,4 +23,12 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, Vi
|
|
|
23
23
|
*/
|
|
24
24
|
asChild?: boolean;
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* The way to act. One solid button per view is the primary action; everything else steps down.
|
|
28
|
+
*
|
|
29
|
+
* @remarks
|
|
30
|
+
* A `Button` starts something: saving, publishing, opening a dialog. If it takes the reader
|
|
31
|
+
* somewhere instead, it's a link, and `asChild` lets a link look like a button when it's the
|
|
32
|
+
* primary way forward.
|
|
33
|
+
*/
|
|
26
34
|
export declare const Button: import("react").ForwardRefExoticComponent<ButtonProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","names":["styles","Button_module_default","Button","forwardRef","rest","Comp","asChild","Slot","cx","visibilityClass","className","variant","tone","size","loading","fullWidth","disabled","type","leadingIcon","trailingIcon","Spinner"],"sources":["../../../../src/components/actions/Button/Button.tsx"],"sourcesContent":["import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { visibilityClass, type VisibilityProps } from '#utils/visibility'\nimport { Spinner } from '#components/feedback/Spinner'\nimport styles from './Button.module.css'\n\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost'\nexport type ButtonTone = 'accent' | 'neutral' | 'critical'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VisibilityProps {\n /** Visual weight. One `solid` per view is the norm — it *is* the primary action. */\n variant?: ButtonVariant\n /** Colour intent. `critical` is for destructive actions only. */\n tone?: ButtonTone\n size?: Size\n /** Icon before the label. Pass a library icon or any SVG node. */\n leadingIcon?: ReactNode\n /** Icon after the label. Not a decorative \"→\" — only when it adds meaning (e.g. external). */\n trailingIcon?: ReactNode\n /** Shows a spinner, sets `aria-busy` and disables interaction while keeping the width. */\n loading?: boolean\n /** Stretch to the container's width. */\n fullWidth?: boolean\n /**\n * Render the single child element instead of a <button>, merging props and styles.\n * Use for links: `<Button asChild><Link to=\"/work\">Work</Link></Button>`.\n */\n asChild?: boolean\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = 'solid',\n tone = 'accent',\n size = 'md',\n leadingIcon,\n trailingIcon,\n loading = false,\n fullWidth = false,\n asChild = false,\n disabled,\n hideBelow,\n hideAbove,\n className,\n children,\n type,\n ...rest\n },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'button'\n return (\n <Comp\n ref={ref}\n className={cx(styles.button, visibilityClass({ hideBelow, hideAbove }), className)}\n data-variant={variant}\n data-tone={tone}\n data-size={size}\n data-loading={loading || undefined}\n data-full-width={fullWidth || undefined}\n aria-busy={loading || undefined}\n disabled={asChild ? undefined : Boolean(disabled) || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n type={asChild ? undefined : (type ?? 'button')}\n {...rest}\n >\n {leadingIcon ? <span className={styles.icon}>{leadingIcon}</span> : null}\n <Slot.Slottable>{children}</Slot.Slottable>\n {trailingIcon ? <span className={styles.icon}>{trailingIcon}</span> : null}\n {loading ? (\n <span className={styles.spinner}>\n <Spinner label={null} />\n </span>\n ) : null}\n </Comp>\n )\n})\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"Button.js","names":["styles","Button_module_default","Button","forwardRef","rest","Comp","asChild","Slot","cx","visibilityClass","className","variant","tone","size","loading","fullWidth","disabled","type","leadingIcon","trailingIcon","Spinner"],"sources":["../../../../src/components/actions/Button/Button.tsx"],"sourcesContent":["import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport type { Size } from '#utils/tokens'\nimport { visibilityClass, type VisibilityProps } from '#utils/visibility'\nimport { Spinner } from '#components/feedback/Spinner'\nimport styles from './Button.module.css'\n\nexport type ButtonVariant = 'solid' | 'outline' | 'ghost'\nexport type ButtonTone = 'accent' | 'neutral' | 'critical'\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement>, VisibilityProps {\n /** Visual weight. One `solid` per view is the norm — it *is* the primary action. */\n variant?: ButtonVariant\n /** Colour intent. `critical` is for destructive actions only. */\n tone?: ButtonTone\n size?: Size\n /** Icon before the label. Pass a library icon or any SVG node. */\n leadingIcon?: ReactNode\n /** Icon after the label. Not a decorative \"→\" — only when it adds meaning (e.g. external). */\n trailingIcon?: ReactNode\n /** Shows a spinner, sets `aria-busy` and disables interaction while keeping the width. */\n loading?: boolean\n /** Stretch to the container's width. */\n fullWidth?: boolean\n /**\n * Render the single child element instead of a <button>, merging props and styles.\n * Use for links: `<Button asChild><Link to=\"/work\">Work</Link></Button>`.\n */\n asChild?: boolean\n}\n\n/**\n * The way to act. One solid button per view is the primary action; everything else steps down.\n *\n * @remarks\n * A `Button` starts something: saving, publishing, opening a dialog. If it takes the reader\n * somewhere instead, it's a link, and `asChild` lets a link look like a button when it's the\n * primary way forward.\n */\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(\n {\n variant = 'solid',\n tone = 'accent',\n size = 'md',\n leadingIcon,\n trailingIcon,\n loading = false,\n fullWidth = false,\n asChild = false,\n disabled,\n hideBelow,\n hideAbove,\n className,\n children,\n type,\n ...rest\n },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'button'\n return (\n <Comp\n ref={ref}\n className={cx(styles.button, visibilityClass({ hideBelow, hideAbove }), className)}\n data-variant={variant}\n data-tone={tone}\n data-size={size}\n data-loading={loading || undefined}\n data-full-width={fullWidth || undefined}\n aria-busy={loading || undefined}\n disabled={asChild ? undefined : Boolean(disabled) || loading}\n aria-disabled={asChild && (disabled || loading) ? true : undefined}\n type={asChild ? undefined : (type ?? 'button')}\n {...rest}\n >\n {leadingIcon ? <span className={styles.icon}>{leadingIcon}</span> : null}\n <Slot.Slottable>{children}</Slot.Slottable>\n {trailingIcon ? <span className={styles.icon}>{trailingIcon}</span> : null}\n {loading ? (\n <span className={styles.spinner}>\n <Spinner label={null} />\n </span>\n ) : null}\n </Comp>\n )\n})\n"],"mappings":";;;;;;;;AAwCA,IAAaE,IAASC,EAA2C,SAC/D,EACE,aAAU,SACV,UAAO,UACP,UAAO,MACP,gBACA,iBACA,aAAU,IACV,eAAY,IACZ,aAAU,IACV,aACA,cACA,cACA,cACA,aACA,SACA,GAAGC,KAEL,GACA;CACA,IAAMC,IAAOC,IAAUC,EAAK,OAAO;CACnC,OACE,kBAACF,GAAD;EACO;EACL,WAAWG,EAAGR,EAAO,QAAQS,EAAgB;GAAE;GAAW;EAAU,CAAC,GAAGC,CAAS;EACjF,gBAAcC;EACd,aAAWC;EACX,aAAWC;EACX,gBAAcC,KAAW,KAAA;EACzB,mBAAiBC,KAAa,KAAA;EAC9B,aAAWD,KAAW,KAAA;EACtB,UAAUR,IAAU,KAAA,IAAY,EAAQU,KAAaF;EACrD,iBAAeR,MAAYU,KAAYF,KAAW,KAAO,KAAA;EACzD,MAAMR,IAAU,KAAA,IAAaW,KAAQ;EACrC,GAAIb;EAZN,UAAA;GAcGc,IAAc,kBAAC,QAAD;IAAM,WAAWlB,EAAO;IAAO,UAAAkB;GAAkB,CAAA,IAAI;GACpE,kBAACX,EAAK,WAAN,EAAiB,YAAyB,CAAA;GACzCY,IAAe,kBAAC,QAAD;IAAM,WAAWnB,EAAO;IAAO,UAAAmB;GAAmB,CAAA,IAAI;GACrEL,IACC,kBAAC,QAAD;IAAM,WAAWd,EAAO;IACtB,UAAA,kBAACoB,GAAD,EAAS,OAAO,KAAO,CAAA;GACnB,CAAA,IACJ;EACA;;AAEV,CAAC"}
|
|
@@ -34,6 +34,15 @@ export interface ChipGroupMultipleProps {
|
|
|
34
34
|
}
|
|
35
35
|
export type ChipGroupProps = ChipGroupBaseProps & Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue' | 'dir'> & (ChipGroupSingleProps | ChipGroupMultipleProps);
|
|
36
36
|
/**
|
|
37
|
+
* A set of chips that picks one value or several, as a single field.
|
|
38
|
+
*
|
|
39
|
+
* @remarks
|
|
40
|
+
* `ChipGroup` is one field made of chips: `type="multiple"` for several of a set, `type="single"`
|
|
41
|
+
* for at most one (pressing the chosen chip again clears it). Arrow keys move between chips, and
|
|
42
|
+
* `name` adds hidden inputs so it submits with a native form. Give it a name with a `Fieldset`
|
|
43
|
+
* legend or an `aria-label`.
|
|
44
|
+
*
|
|
45
|
+
* @privateRemarks
|
|
37
46
|
* A set of chips that together make one answer — "Email alerts", "Labels". The chips look
|
|
38
47
|
* exactly like `ToggleChip` (they share its style module) but behave as a group (Radix
|
|
39
48
|
* ToggleGroup): one tab stop, arrow keys move between chips, Space/Enter presses.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChipGroup.js","names":["_Fragment","chipStyles","messageStyles","styles","chip_module_default","messages_module_default","ChipGroup_module_default","Fragment$1","EMPTY","ChipContent","option","ChipGroup","markFieldAware","forwardRef","valueProp","disabledProp","invalidProp","readOnlyProp","idProp","describedByProp","ariaInvalid","labelledByProp","rest","props","autoId","useId","field","useResolvedField","handleBlur","useFocusLeave","onBlur","toArray","v","emit","onValueChange","type","next","values","setValues","useControllableValue","defaultValue","order","options","o","items","descriptionId","i","ToggleGroup","size","descriptions","common","cx","className","hiddenInputs","name","a","b"],"sources":["../../../../src/components/actions/ChipGroup/ChipGroup.tsx"],"sourcesContent":["import { forwardRef, useId, type HTMLAttributes, type ReactNode } from 'react'\nimport { ToggleGroup } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport chipStyles from '#components/actions/ToggleChip/chip.module.css'\nimport {\n hiddenInputs,\n useControllableValue,\n useFocusLeave,\n type ChoiceOption,\n} from '#components/inputs/CheckboxGroup/choice'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport styles from './ChipGroup.module.css'\n\nexport interface ChipOption extends ChoiceOption {\n /** A leading glyph. One icon per chip at most — and only when it carries meaning. */\n icon?: ReactNode\n /** How many items the chip matches, in tabular figures after the label. */\n count?: number\n}\n\nexport type ChipGroupSize = 'sm' | 'md'\n\ninterface ChipGroupBaseProps {\n options: readonly ChipOption[]\n size?: ChipGroupSize\n disabled?: boolean\n /** Critical edge on the resting chips + `aria-invalid`. Set for you inside a `<Fieldset error>`. */\n invalid?: boolean\n /** Focusable but not editable: presses are ignored. */\n readOnly?: boolean\n /** Emits one hidden input per selected value, for native form submission. */\n name?: string\n}\n\nexport interface ChipGroupSingleProps {\n /** One chip at most; pressing the selected chip clears it (`''`). */\n type: 'single'\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\nexport interface ChipGroupMultipleProps {\n /** Any number of chips. */\n type: 'multiple'\n value?: readonly string[]\n defaultValue?: readonly string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ChipGroupProps = ChipGroupBaseProps &\n Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue' | 'dir'> &\n (ChipGroupSingleProps | ChipGroupMultipleProps)\n\nconst EMPTY: readonly string[] = []\n\nfunction ChipContent({ option }: { option: ChipOption }) {\n return (\n <>\n {option.icon ? (\n <span className={chipStyles.icon} aria-hidden=\"true\">\n {option.icon}\n </span>\n ) : null}\n <span className={chipStyles.label}>{option.label}</span>\n {option.count !== undefined ? <span className={chipStyles.count}>{option.count}</span> : null}\n </>\n )\n}\n\n/**\n * A set of chips that together make one answer — \"Email alerts\", \"Labels\". The chips look\n * exactly like `ToggleChip` (they share its style module) but behave as a group (Radix\n * ToggleGroup): one tab stop, arrow keys move between chips, Space/Enter presses.\n *\n * `type=\"multiple\"` — any number pressed; chips are toggle buttons in a `role=\"group\"`.\n * `type=\"single\"` — at most one; chips are radios in a `role=\"radiogroup\"`.\n *\n * Label it with a `<Fieldset legend>` (see `ChipGroupField`), a `<Field>` or `aria-label`.\n * `onBlur` fires once, when focus leaves the whole group.\n *\n * <ChipGroup type=\"multiple\" aria-label=\"Email alerts\" options={alerts} defaultValue={['mentions']} />\n */\nexport const ChipGroup = markFieldAware(\n forwardRef<HTMLDivElement, ChipGroupProps>(function ChipGroup(props, ref) {\n const {\n type,\n value: valueProp,\n defaultValue,\n onValueChange,\n options,\n size = 'md',\n disabled: disabledProp,\n invalid: invalidProp,\n readOnly: readOnlyProp,\n name,\n id: idProp,\n className,\n onBlur,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n } = props\n const autoId = useId()\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const handleBlur = useFocusLeave(onBlur)\n\n // Normalise both modes to an array internally.\n const toArray = (v: string | readonly string[] | undefined): readonly string[] | undefined =>\n v === undefined ? undefined : typeof v === 'string' ? (v ? [v] : EMPTY) : v\n const emit = onValueChange\n ? (next: readonly string[]) => {\n if (type === 'single') onValueChange(next[0] ?? '')\n else onValueChange([...next])\n }\n : undefined\n const [values, setValues] = useControllableValue<readonly string[]>(\n toArray(valueProp),\n toArray(defaultValue) ?? EMPTY,\n emit,\n field.readOnly,\n )\n const order = options.map((o) => o.value)\n\n const items = options.map((o, i) => {\n const descriptionId = `${autoId}chip${String(i)}-description`\n return (\n <ToggleGroup.Item\n key={o.value}\n value={o.value}\n disabled={o.disabled}\n className={chipStyles.chip}\n data-size={size}\n data-invalid={field.invalid || undefined}\n aria-describedby={o.description != null ? descriptionId : undefined}\n >\n <ChipContent option={o} />\n </ToggleGroup.Item>\n )\n })\n\n // Descriptions live outside the chips: inside, they'd become part of each chip's name.\n const descriptions = options.some((o) => o.description != null) ? (\n <span className={messageStyles.visuallyHidden}>\n {options.map((o, i) =>\n o.description != null ? (\n <span key={o.value} id={`${autoId}chip${String(i)}-description`}>\n {o.description}\n </span>\n ) : null,\n )}\n </span>\n ) : null\n\n const common = {\n ref,\n id: field.id,\n className: cx(styles.group, className),\n disabled: field.disabled,\n 'data-size': size,\n 'data-invalid': field.invalid || undefined,\n 'data-readonly': field.readOnly || undefined,\n 'aria-labelledby': labelledByProp ?? (rest['aria-label'] ? undefined : field.groupLabelId),\n 'aria-describedby': field.describedBy,\n 'aria-invalid': field.invalid || undefined,\n 'aria-busy': field.busy || undefined,\n onBlur: handleBlur,\n ...rest,\n }\n\n return type === 'single' ? (\n <ToggleGroup.Root\n {...common}\n type=\"single\"\n role=\"radiogroup\"\n aria-required={field.required || undefined}\n aria-readonly={field.readOnly || undefined}\n value={values[0] ?? ''}\n onValueChange={(v) => {\n setValues(v ? [v] : EMPTY)\n }}\n >\n {items}\n {descriptions}\n {hiddenInputs(name, values, field.disabled)}\n </ToggleGroup.Root>\n ) : (\n <ToggleGroup.Root\n {...common}\n type=\"multiple\"\n role=\"group\"\n value={[...values]}\n onValueChange={(v) => {\n setValues([...v].sort((a, b) => order.indexOf(a) - order.indexOf(b)))\n }}\n >\n {items}\n {descriptions}\n {hiddenInputs(name, values, field.disabled)}\n </ToggleGroup.Root>\n )\n }),\n)\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ChipGroup.js","names":["_Fragment","chipStyles","messageStyles","styles","chip_module_default","messages_module_default","ChipGroup_module_default","Fragment$1","EMPTY","ChipContent","option","ChipGroup","markFieldAware","forwardRef","valueProp","disabledProp","invalidProp","readOnlyProp","idProp","describedByProp","ariaInvalid","labelledByProp","rest","props","autoId","useId","field","useResolvedField","handleBlur","useFocusLeave","onBlur","toArray","v","emit","onValueChange","type","next","values","setValues","useControllableValue","defaultValue","order","options","o","items","descriptionId","i","ToggleGroup","size","descriptions","common","cx","className","hiddenInputs","name","a","b"],"sources":["../../../../src/components/actions/ChipGroup/ChipGroup.tsx"],"sourcesContent":["import { forwardRef, useId, type HTMLAttributes, type ReactNode } from 'react'\nimport { ToggleGroup } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport chipStyles from '#components/actions/ToggleChip/chip.module.css'\nimport {\n hiddenInputs,\n useControllableValue,\n useFocusLeave,\n type ChoiceOption,\n} from '#components/inputs/CheckboxGroup/choice'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport messageStyles from '#components/inputs/internal/messages.module.css'\nimport styles from './ChipGroup.module.css'\n\nexport interface ChipOption extends ChoiceOption {\n /** A leading glyph. One icon per chip at most — and only when it carries meaning. */\n icon?: ReactNode\n /** How many items the chip matches, in tabular figures after the label. */\n count?: number\n}\n\nexport type ChipGroupSize = 'sm' | 'md'\n\ninterface ChipGroupBaseProps {\n options: readonly ChipOption[]\n size?: ChipGroupSize\n disabled?: boolean\n /** Critical edge on the resting chips + `aria-invalid`. Set for you inside a `<Fieldset error>`. */\n invalid?: boolean\n /** Focusable but not editable: presses are ignored. */\n readOnly?: boolean\n /** Emits one hidden input per selected value, for native form submission. */\n name?: string\n}\n\nexport interface ChipGroupSingleProps {\n /** One chip at most; pressing the selected chip clears it (`''`). */\n type: 'single'\n value?: string\n defaultValue?: string\n onValueChange?: (value: string) => void\n}\n\nexport interface ChipGroupMultipleProps {\n /** Any number of chips. */\n type: 'multiple'\n value?: readonly string[]\n defaultValue?: readonly string[]\n onValueChange?: (value: string[]) => void\n}\n\nexport type ChipGroupProps = ChipGroupBaseProps &\n Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue' | 'dir'> &\n (ChipGroupSingleProps | ChipGroupMultipleProps)\n\nconst EMPTY: readonly string[] = []\n\nfunction ChipContent({ option }: { option: ChipOption }) {\n return (\n <>\n {option.icon ? (\n <span className={chipStyles.icon} aria-hidden=\"true\">\n {option.icon}\n </span>\n ) : null}\n <span className={chipStyles.label}>{option.label}</span>\n {option.count !== undefined ? <span className={chipStyles.count}>{option.count}</span> : null}\n </>\n )\n}\n\n/**\n * A set of chips that picks one value or several, as a single field.\n *\n * @remarks\n * `ChipGroup` is one field made of chips: `type=\"multiple\"` for several of a set, `type=\"single\"`\n * for at most one (pressing the chosen chip again clears it). Arrow keys move between chips, and\n * `name` adds hidden inputs so it submits with a native form. Give it a name with a `Fieldset`\n * legend or an `aria-label`.\n *\n * @privateRemarks\n * A set of chips that together make one answer — \"Email alerts\", \"Labels\". The chips look\n * exactly like `ToggleChip` (they share its style module) but behave as a group (Radix\n * ToggleGroup): one tab stop, arrow keys move between chips, Space/Enter presses.\n *\n * `type=\"multiple\"` — any number pressed; chips are toggle buttons in a `role=\"group\"`.\n * `type=\"single\"` — at most one; chips are radios in a `role=\"radiogroup\"`.\n *\n * Label it with a `<Fieldset legend>` (see `ChipGroupField`), a `<Field>` or `aria-label`.\n * `onBlur` fires once, when focus leaves the whole group.\n *\n * <ChipGroup type=\"multiple\" aria-label=\"Email alerts\" options={alerts} defaultValue={['mentions']} />\n */\nexport const ChipGroup = markFieldAware(\n forwardRef<HTMLDivElement, ChipGroupProps>(function ChipGroup(props, ref) {\n const {\n type,\n value: valueProp,\n defaultValue,\n onValueChange,\n options,\n size = 'md',\n disabled: disabledProp,\n invalid: invalidProp,\n readOnly: readOnlyProp,\n name,\n id: idProp,\n className,\n onBlur,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n } = props\n const autoId = useId()\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const handleBlur = useFocusLeave(onBlur)\n\n // Normalise both modes to an array internally.\n const toArray = (v: string | readonly string[] | undefined): readonly string[] | undefined =>\n v === undefined ? undefined : typeof v === 'string' ? (v ? [v] : EMPTY) : v\n const emit = onValueChange\n ? (next: readonly string[]) => {\n if (type === 'single') onValueChange(next[0] ?? '')\n else onValueChange([...next])\n }\n : undefined\n const [values, setValues] = useControllableValue<readonly string[]>(\n toArray(valueProp),\n toArray(defaultValue) ?? EMPTY,\n emit,\n field.readOnly,\n )\n const order = options.map((o) => o.value)\n\n const items = options.map((o, i) => {\n const descriptionId = `${autoId}chip${String(i)}-description`\n return (\n <ToggleGroup.Item\n key={o.value}\n value={o.value}\n disabled={o.disabled}\n className={chipStyles.chip}\n data-size={size}\n data-invalid={field.invalid || undefined}\n aria-describedby={o.description != null ? descriptionId : undefined}\n >\n <ChipContent option={o} />\n </ToggleGroup.Item>\n )\n })\n\n // Descriptions live outside the chips: inside, they'd become part of each chip's name.\n const descriptions = options.some((o) => o.description != null) ? (\n <span className={messageStyles.visuallyHidden}>\n {options.map((o, i) =>\n o.description != null ? (\n <span key={o.value} id={`${autoId}chip${String(i)}-description`}>\n {o.description}\n </span>\n ) : null,\n )}\n </span>\n ) : null\n\n const common = {\n ref,\n id: field.id,\n className: cx(styles.group, className),\n disabled: field.disabled,\n 'data-size': size,\n 'data-invalid': field.invalid || undefined,\n 'data-readonly': field.readOnly || undefined,\n 'aria-labelledby': labelledByProp ?? (rest['aria-label'] ? undefined : field.groupLabelId),\n 'aria-describedby': field.describedBy,\n 'aria-invalid': field.invalid || undefined,\n 'aria-busy': field.busy || undefined,\n onBlur: handleBlur,\n ...rest,\n }\n\n return type === 'single' ? (\n <ToggleGroup.Root\n {...common}\n type=\"single\"\n role=\"radiogroup\"\n aria-required={field.required || undefined}\n aria-readonly={field.readOnly || undefined}\n value={values[0] ?? ''}\n onValueChange={(v) => {\n setValues(v ? [v] : EMPTY)\n }}\n >\n {items}\n {descriptions}\n {hiddenInputs(name, values, field.disabled)}\n </ToggleGroup.Root>\n ) : (\n <ToggleGroup.Root\n {...common}\n type=\"multiple\"\n role=\"group\"\n value={[...values]}\n onValueChange={(v) => {\n setValues([...v].sort((a, b) => order.indexOf(a) - order.indexOf(b)))\n }}\n >\n {items}\n {descriptions}\n {hiddenInputs(name, values, field.disabled)}\n </ToggleGroup.Root>\n )\n }),\n)\n"],"mappings":";;;;;;;;;;AAuDA,IAAMQ,IAA2B,CAAC;AAElC,SAASC,EAAY,EAAE,aAAkC;CACvD,OACE,kBAAAT,GAAA,EAAA,UAAA;EACGU,EAAO,OACN,kBAAC,QAAD;GAAM,WAAWT,EAAW;GAAM,eAAY;GAC3C,UAAAS,EAAO;EACJ,CAAA,IACJ;EACJ,kBAAC,QAAD;GAAM,WAAWT,EAAW;GAAQ,UAAAS,EAAO;EAAY,CAAA;EACtDA,EAAO,UAAU,KAAA,IAAuE,OAA3D,kBAAC,QAAD;GAAM,WAAWT,EAAW;GAAQ,UAAAS,EAAO;EAAY,CAAA;CACrF,EAAA,CAAA;AAEN;AAwBA,IAAaC,IAAYC,EACvBC,EAA2C,SAAmB,GAAO,GAAK;CACxE,IAAM,EACJ,SACA,OAAOC,GACP,iBACA,kBACA,YACA,UAAO,MACP,UAAUC,GACV,SAASC,GACT,UAAUC,GACV,SACA,IAAIC,GACJ,cACA,WACA,oBAAoBC,GACpB,gBAAgBC,GAChB,mBAAmBC,GACnB,GAAGC,MACDC,GACEC,IAASC,EAAM,GACfC,IAAQC,EAAiB;EAC7B,IAAIT;EACJ,aAAaC;EACb,SAASH;EACT;EACA,UAAUD;EACV,UAAUE;CACZ,CAAC,GACKW,IAAaC,EAAcC,CAAM,GAGjCC,KAAW,MACfC,MAAM,KAAA,IAAY,KAAA,IAAY,OAAOA,KAAM,WAAYA,IAAI,CAACA,CAAC,IAAIxB,IAASwB,GACtEC,IAAOC,KACR,MAA4B;EAC3B,AAAuBA,EAAnBC,MAAS,WAAwBC,EAAK,MAAM,KAC7B,CAAC,GAAGA,CAAI,CAAC;CAC9B,IACA,KAAA,GACE,CAACC,GAAQC,KAAaC,EAC1BR,EAAQjB,CAAS,GACjBiB,EAAQS,CAAY,KAAKhC,GACzByB,GACAP,EAAM,QACR,GACMe,IAAQC,EAAQ,KAAK,MAAMC,EAAE,KAAK,GAElCC,IAAQF,EAAQ,KAAK,GAAG,MAAM;EAClC,IAAMG,IAAgB,GAAGrB,EAAO,MAAM,OAAOsB,CAAC,EAAE;EAChD,OACE,kBAACC,EAAY,MAAb;GAEE,OAAOJ,EAAE;GACT,UAAUA,EAAE;GACZ,WAAW1C,EAAW;GACtB,aAAW+C;GACX,gBAActB,EAAM,WAAW,KAAA;GAC/B,oBAAkBiB,EAAE,eAAe,OAAuB,KAAA,IAAhBE;GAE1C,UAAA,kBAACpC,GAAD,EAAa,QAAQkC,EAAI,CAAA;EACT,GATXA,EAAE,KASS;CAEtB,CAAC,GAGKM,IAAeP,EAAQ,MAAM,MAAMC,EAAE,eAAe,IAAI,IAC5D,kBAAC,QAAD;EAAM,WAAWzC,EAAc;EAC5B,UAAAwC,EAAQ,KAAK,GAAG,MACfC,EAAE,eAAe,OAIb,OAHF,kBAAC,QAAD;GAAoB,IAAI,GAAGnB,EAAO,MAAM,OAAOsB,CAAC,EAAE;GAC/C,UAAAH,EAAE;EACC,GAFKA,EAAE,KAEP,CAEV;CACI,CAAA,IACJ,MAEEO,IAAS;EACb;EACA,IAAIxB,EAAM;EACV,WAAWyB,EAAGhD,EAAO,OAAOiD,CAAS;EACrC,UAAU1B,EAAM;EAChB,aAAasB;EACb,gBAAgBtB,EAAM,WAAW,KAAA;EACjC,iBAAiBA,EAAM,YAAY,KAAA;EACnC,mBAAmBL,MAAmBC,EAAK,gBAAgB,KAAA,IAAYI,EAAM;EAC7E,oBAAoBA,EAAM;EAC1B,gBAAgBA,EAAM,WAAW,KAAA;EACjC,aAAaA,EAAM,QAAQ,KAAA;EAC3B,QAAQE;EACR,GAAGN;CACL;CAEA,OAAOa,MAAS,WACd,kBAACY,EAAY,MAAb;EACE,GAAIG;EACJ,MAAK;EACL,MAAK;EACL,iBAAexB,EAAM,YAAY,KAAA;EACjC,iBAAeA,EAAM,YAAY,KAAA;EACjC,OAAOW,EAAO,MAAM;EACpB,gBAAgB,MAAM;GACpB,EAAUL,IAAI,CAACA,CAAC,IAAIxB,CAAK;EAC3B;EATF,UAAA;GAWGoC;GACAK;GACAI,EAAaC,GAAMjB,GAAQX,EAAM,QAAQ;EAC1B;CAElB,CAAA,IAAA,kBAACqB,EAAY,MAAb;EACE,GAAIG;EACJ,MAAK;EACL,MAAK;EACL,OAAO,CAAC,GAAGb,CAAM;EACjB,gBAAgB,MAAM;GACpB,EAAU,CAAC,GAAGL,CAAC,CAAC,CAAC,MAAM,GAAG,MAAMS,EAAM,QAAQc,CAAC,IAAId,EAAM,QAAQe,CAAC,CAAC,CAAC;EACtE;EAPF,UAAA;GASGZ;GACAK;GACAI,EAAaC,GAAMjB,GAAQX,EAAM,QAAQ;EAC1B;;AAEtB,CAAC,CACH"}
|
|
@@ -18,6 +18,14 @@ export interface IconButtonProps extends Omit<ButtonProps, 'leadingIcon' | 'trai
|
|
|
18
18
|
showTitle?: boolean;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
+
* A square button that's just an icon, with the accessible name it can't show.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* `IconButton` is for toolbars and dense rows where the icon is universally understood: search,
|
|
25
|
+
* close, copy, more. Its `label` is required, because the label is the button's only name for
|
|
26
|
+
* screen reader users. If the icon isn't obvious, use a `Button` with words.
|
|
27
|
+
*
|
|
28
|
+
* @privateRemarks
|
|
21
29
|
* A square, icon-only Button. Shares Button's variants, tones, sizes, `asChild` and
|
|
22
30
|
* `loading` — it *is* a Button with the padding taken out — so the two always match.
|
|
23
31
|
* Defaults to the quiet `ghost` + `neutral` pairing used in toolbars and headers.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"IconButton.js","names":["styles","IconButton_module_default","IconButton","forwardRef","rest","Button","cx","className","shape","label","title","showTitle","icon"],"sources":["../../../../src/components/actions/IconButton/IconButton.tsx"],"sourcesContent":["import { forwardRef, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { Button, type ButtonProps } from '#components/actions/Button'\nimport styles from './IconButton.module.css'\n\nexport type IconButtonShape = 'auto' | 'round' | 'square'\n\nexport interface IconButtonProps extends Omit<\n ButtonProps,\n 'leadingIcon' | 'trailingIcon' | 'fullWidth' | 'aria-label'\n> {\n /**\n * Accessible name — required, because there is no visible text. Say what it does\n * (\"Close\", \"Copy link\"), not what it looks like (\"X\").\n */\n label: string\n /** The glyph. A library icon or any SVG node; it is hidden from assistive tech. */\n icon: ReactNode\n /**\n * `auto` (default) follows the theme's action radius — a circle where actions are\n * pills, a small square in Ledger. `round` forces a circle; `square` uses the field radius.\n */\n shape?: IconButtonShape\n /** Also set the native `title`, so pointer users get the name on hover. Default `false`. */\n showTitle?: boolean\n}\n\n/**\n * A square, icon-only Button. Shares Button's variants, tones, sizes, `asChild` and\n * `loading` — it *is* a Button with the padding taken out — so the two always match.\n * Defaults to the quiet `ghost` + `neutral` pairing used in toolbars and headers.\n *\n * <IconButton label=\"Close\" icon={<CloseIcon />} />\n * <IconButton asChild label=\"GitHub\" icon={<ArrowUpRightIcon />}><a href=\"…\" /></IconButton>\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n {\n label,\n icon,\n shape = 'auto',\n showTitle = false,\n variant = 'ghost',\n tone = 'neutral',\n title,\n className,\n children,\n ...rest\n },\n ref,\n) {\n return (\n <Button\n ref={ref}\n variant={variant}\n tone={tone}\n className={cx(styles.iconButton, className)}\n data-shape={shape}\n aria-label={label}\n title={title ?? (showTitle ? label : undefined)}\n leadingIcon={\n <span className={styles.glyph} aria-hidden=\"true\">\n {icon}\n </span>\n }\n {...rest}\n >\n {children}\n </Button>\n )\n})\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"IconButton.js","names":["styles","IconButton_module_default","IconButton","forwardRef","rest","Button","cx","className","shape","label","title","showTitle","icon"],"sources":["../../../../src/components/actions/IconButton/IconButton.tsx"],"sourcesContent":["import { forwardRef, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { Button, type ButtonProps } from '#components/actions/Button'\nimport styles from './IconButton.module.css'\n\nexport type IconButtonShape = 'auto' | 'round' | 'square'\n\nexport interface IconButtonProps extends Omit<\n ButtonProps,\n 'leadingIcon' | 'trailingIcon' | 'fullWidth' | 'aria-label'\n> {\n /**\n * Accessible name — required, because there is no visible text. Say what it does\n * (\"Close\", \"Copy link\"), not what it looks like (\"X\").\n */\n label: string\n /** The glyph. A library icon or any SVG node; it is hidden from assistive tech. */\n icon: ReactNode\n /**\n * `auto` (default) follows the theme's action radius — a circle where actions are\n * pills, a small square in Ledger. `round` forces a circle; `square` uses the field radius.\n */\n shape?: IconButtonShape\n /** Also set the native `title`, so pointer users get the name on hover. Default `false`. */\n showTitle?: boolean\n}\n\n/**\n * A square button that's just an icon, with the accessible name it can't show.\n *\n * @remarks\n * `IconButton` is for toolbars and dense rows where the icon is universally understood: search,\n * close, copy, more. Its `label` is required, because the label is the button's only name for\n * screen reader users. If the icon isn't obvious, use a `Button` with words.\n *\n * @privateRemarks\n * A square, icon-only Button. Shares Button's variants, tones, sizes, `asChild` and\n * `loading` — it *is* a Button with the padding taken out — so the two always match.\n * Defaults to the quiet `ghost` + `neutral` pairing used in toolbars and headers.\n *\n * <IconButton label=\"Close\" icon={<CloseIcon />} />\n * <IconButton asChild label=\"GitHub\" icon={<ArrowUpRightIcon />}><a href=\"…\" /></IconButton>\n */\nexport const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>(function IconButton(\n {\n label,\n icon,\n shape = 'auto',\n showTitle = false,\n variant = 'ghost',\n tone = 'neutral',\n title,\n className,\n children,\n ...rest\n },\n ref,\n) {\n return (\n <Button\n ref={ref}\n variant={variant}\n tone={tone}\n className={cx(styles.iconButton, className)}\n data-shape={shape}\n aria-label={label}\n title={title ?? (showTitle ? label : undefined)}\n leadingIcon={\n <span className={styles.glyph} aria-hidden=\"true\">\n {icon}\n </span>\n }\n {...rest}\n >\n {children}\n </Button>\n )\n})\n"],"mappings":";;;;;;AA2CA,IAAaE,IAAaC,EAA+C,SACvE,EACE,UACA,SACA,WAAQ,QACR,eAAY,IACZ,aAAU,SACV,UAAO,WACP,UACA,cACA,aACA,GAAGC,KAEL,GACA;CACA,OACE,kBAACC,GAAD;EACO;EACI;EACH;EACN,WAAWC,EAAGN,EAAO,YAAYO,CAAS;EAC1C,cAAYC;EACZ,cAAYC;EACZ,OAAOC,MAAUC,IAAYF,IAAQ,KAAA;EACrC,aACE,kBAAC,QAAD;GAAM,WAAWT,EAAO;GAAO,eAAY;GACxC,UAAAY;EACG,CAAA;EAER,GAAIR;EAEH;CACK,CAAA;AAEZ,CAAC"}
|
|
@@ -18,6 +18,14 @@ export interface ModeToggleProps {
|
|
|
18
18
|
id?: string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
+
* Switches the colour mode between light, dark and the system setting, through the theme provider.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* `ModeToggle` reads and sets the mode from `useTheme()`, so it needs a `ThemeProvider` above it,
|
|
25
|
+
* and it throws without one: a toggle that silently does nothing is worse than a loud error. The
|
|
26
|
+
* choice is remembered, and `themeScript` applies it before the first paint on the next visit.
|
|
27
|
+
*
|
|
28
|
+
* @privateRemarks
|
|
21
29
|
* Reads and sets the colour mode from `useTheme()`. **Requires `<ThemeProvider>`** —
|
|
22
30
|
* like `useTheme`, it throws outside one, because a mode toggle that silently does
|
|
23
31
|
* nothing is worse than a loud error.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ModeToggle.js","names":["ORDER","MODE_META","SunIcon","MoonIcon","SystemIcon","ModeToggle","forwardRef","ariaLabel","useTheme","variant","SegmentedControl","mode","v","m","next","IconButton","buttonVariant"],"sources":["../../../../src/components/actions/ModeToggle/ModeToggle.tsx"],"sourcesContent":["import { forwardRef, type ForwardedRef, type ReactNode } from 'react'\nimport { useTheme, type ColorMode } from '#theme'\nimport { MoonIcon, SunIcon, SystemIcon } from '#icons'\nimport type { Size } from '#utils/tokens'\nimport { IconButton, type IconButtonProps } from '#components/actions/IconButton'\nimport { SegmentedControl } from '#components/actions/SegmentedControl'\n\nexport type ModeToggleVariant = 'icon' | 'segmented'\n\nconst ORDER: readonly ColorMode[] = ['light', 'dark', 'system']\n\nconst MODE_META: Record<ColorMode, { label: string; icon: ReactNode }> = {\n light: { label: 'Light', icon: <SunIcon /> },\n dark: { label: 'Dark', icon: <MoonIcon /> },\n system: { label: 'System', icon: <SystemIcon /> },\n}\n\nexport interface ModeToggleProps {\n /**\n * `icon` (default): one IconButton that cycles light → dark → system.\n * `segmented`: all three modes visible at once, for settings pages and footers.\n */\n variant?: ModeToggleVariant\n size?: Size\n /** Segmented only: show icons without labels (labels stay as accessible names). */\n iconOnly?: boolean\n /** Icon only: button styling, forwarded to IconButton. Default `ghost`. */\n buttonVariant?: IconButtonProps['variant']\n /** Accessible name for the segmented group. Default \"Colour mode\". */\n 'aria-label'?: string\n className?: string\n id?: string\n}\n\n/**\n * Reads and sets the colour mode from `useTheme()`. **Requires `<ThemeProvider>`** —\n * like `useTheme`, it throws outside one, because a mode toggle that silently does\n * nothing is worse than a loud error.\n *\n * The icon shows the *current* mode; the accessible name says what it is and what\n * a press will switch to.\n */\nexport const ModeToggle = forwardRef<HTMLElement, ModeToggleProps>(function ModeToggle(\n {\n variant = 'icon',\n size = 'md',\n iconOnly = false,\n buttonVariant = 'ghost',\n 'aria-label': ariaLabel,\n className,\n id,\n },\n ref,\n) {\n const { mode, setMode } = useTheme()\n\n if (variant === 'segmented') {\n return (\n <SegmentedControl\n ref={ref as ForwardedRef<HTMLDivElement>}\n id={id}\n className={className}\n aria-label={ariaLabel ?? 'Colour mode'}\n size={size}\n iconOnly={iconOnly}\n value={mode}\n onValueChange={(v) => {\n setMode(v as ColorMode)\n }}\n options={ORDER.map((m) => ({\n value: m,\n label: MODE_META[m].label,\n icon: MODE_META[m].icon,\n }))}\n />\n )\n }\n\n const next = ORDER[(ORDER.indexOf(mode) + 1) % ORDER.length] ?? 'light'\n return (\n <IconButton\n ref={ref as ForwardedRef<HTMLButtonElement>}\n id={id}\n className={className}\n variant={buttonVariant}\n size={size}\n label={\n ariaLabel ??\n `Colour mode: ${MODE_META[mode].label}. Switch to ${MODE_META[next].label.toLowerCase()}`\n }\n title={`Colour mode: ${MODE_META[mode].label}`}\n icon={MODE_META[mode].icon}\n data-mode-value={mode}\n onClick={() => {\n setMode(next)\n }}\n />\n )\n})\n"],"mappings":";;;;;;;AASA,IAAMA,IAA8B;CAAC;CAAS;CAAQ;AAAQ,GAExDC,IAAmE;CACvE,OAAO;EAAE,OAAO;EAAS,MAAM,kBAACC,GAAD,CAAU,CAAA;CAAE;CAC3C,MAAM;EAAE,OAAO;EAAQ,MAAM,kBAACC,GAAD,CAAW,CAAA;CAAE;CAC1C,QAAQ;EAAE,OAAO;EAAU,MAAM,kBAACC,GAAD,CAAa,CAAA;CAAE;AAClD,
|
|
1
|
+
{"version":3,"file":"ModeToggle.js","names":["ORDER","MODE_META","SunIcon","MoonIcon","SystemIcon","ModeToggle","forwardRef","ariaLabel","useTheme","variant","SegmentedControl","mode","v","m","next","IconButton","buttonVariant"],"sources":["../../../../src/components/actions/ModeToggle/ModeToggle.tsx"],"sourcesContent":["import { forwardRef, type ForwardedRef, type ReactNode } from 'react'\nimport { useTheme, type ColorMode } from '#theme'\nimport { MoonIcon, SunIcon, SystemIcon } from '#icons'\nimport type { Size } from '#utils/tokens'\nimport { IconButton, type IconButtonProps } from '#components/actions/IconButton'\nimport { SegmentedControl } from '#components/actions/SegmentedControl'\n\nexport type ModeToggleVariant = 'icon' | 'segmented'\n\nconst ORDER: readonly ColorMode[] = ['light', 'dark', 'system']\n\nconst MODE_META: Record<ColorMode, { label: string; icon: ReactNode }> = {\n light: { label: 'Light', icon: <SunIcon /> },\n dark: { label: 'Dark', icon: <MoonIcon /> },\n system: { label: 'System', icon: <SystemIcon /> },\n}\n\nexport interface ModeToggleProps {\n /**\n * `icon` (default): one IconButton that cycles light → dark → system.\n * `segmented`: all three modes visible at once, for settings pages and footers.\n */\n variant?: ModeToggleVariant\n size?: Size\n /** Segmented only: show icons without labels (labels stay as accessible names). */\n iconOnly?: boolean\n /** Icon only: button styling, forwarded to IconButton. Default `ghost`. */\n buttonVariant?: IconButtonProps['variant']\n /** Accessible name for the segmented group. Default \"Colour mode\". */\n 'aria-label'?: string\n className?: string\n id?: string\n}\n\n/**\n * Switches the colour mode between light, dark and the system setting, through the theme provider.\n *\n * @remarks\n * `ModeToggle` reads and sets the mode from `useTheme()`, so it needs a `ThemeProvider` above it,\n * and it throws without one: a toggle that silently does nothing is worse than a loud error. The\n * choice is remembered, and `themeScript` applies it before the first paint on the next visit.\n *\n * @privateRemarks\n * Reads and sets the colour mode from `useTheme()`. **Requires `<ThemeProvider>`** —\n * like `useTheme`, it throws outside one, because a mode toggle that silently does\n * nothing is worse than a loud error.\n *\n * The icon shows the *current* mode; the accessible name says what it is and what\n * a press will switch to.\n */\nexport const ModeToggle = forwardRef<HTMLElement, ModeToggleProps>(function ModeToggle(\n {\n variant = 'icon',\n size = 'md',\n iconOnly = false,\n buttonVariant = 'ghost',\n 'aria-label': ariaLabel,\n className,\n id,\n },\n ref,\n) {\n const { mode, setMode } = useTheme()\n\n if (variant === 'segmented') {\n return (\n <SegmentedControl\n ref={ref as ForwardedRef<HTMLDivElement>}\n id={id}\n className={className}\n aria-label={ariaLabel ?? 'Colour mode'}\n size={size}\n iconOnly={iconOnly}\n value={mode}\n onValueChange={(v) => {\n setMode(v as ColorMode)\n }}\n options={ORDER.map((m) => ({\n value: m,\n label: MODE_META[m].label,\n icon: MODE_META[m].icon,\n }))}\n />\n )\n }\n\n const next = ORDER[(ORDER.indexOf(mode) + 1) % ORDER.length] ?? 'light'\n return (\n <IconButton\n ref={ref as ForwardedRef<HTMLButtonElement>}\n id={id}\n className={className}\n variant={buttonVariant}\n size={size}\n label={\n ariaLabel ??\n `Colour mode: ${MODE_META[mode].label}. Switch to ${MODE_META[next].label.toLowerCase()}`\n }\n title={`Colour mode: ${MODE_META[mode].label}`}\n icon={MODE_META[mode].icon}\n data-mode-value={mode}\n onClick={() => {\n setMode(next)\n }}\n />\n )\n})\n"],"mappings":";;;;;;;AASA,IAAMA,IAA8B;CAAC;CAAS;CAAQ;AAAQ,GAExDC,IAAmE;CACvE,OAAO;EAAE,OAAO;EAAS,MAAM,kBAACC,GAAD,CAAU,CAAA;CAAE;CAC3C,MAAM;EAAE,OAAO;EAAQ,MAAM,kBAACC,GAAD,CAAW,CAAA;CAAE;CAC1C,QAAQ;EAAE,OAAO;EAAU,MAAM,kBAACC,GAAD,CAAa,CAAA;CAAE;AAClD,GAmCaC,IAAaC,EAAyC,SACjE,EACE,aAAU,QACV,UAAO,MACP,cAAW,IACX,mBAAgB,SAChB,cAAcC,GACd,cACA,SAEF,GACA;CACA,IAAM,EAAE,SAAM,eAAYC,EAAS;CAEnC,IAAIC,MAAY,aACd,OACE,kBAACC,GAAD;EACO;EACD;EACO;EACX,cAAYH,KAAa;EACnB;EACI;EACV,OAAOI;EACP,gBAAgB,MAAM;GACpB,EAAQC,CAAc;EACxB;EACA,SAASZ,EAAM,KAAK,OAAO;GACzB,OAAOa;GACP,OAAOZ,EAAUY,EAAE,CAAC;GACpB,MAAMZ,EAAUY,EAAE,CAAC;EACrB,EAAE;CACH,CAAA;CAIL,IAAMC,IAAOd,GAAOA,EAAM,QAAQW,CAAI,IAAI,KAAKX,EAAM,WAAW;CAChE,OACE,kBAACe,GAAD;EACO;EACD;EACO;EACX,SAASC;EACH;EACN,OACET,KACA,gBAAgBN,EAAUU,EAAK,CAAC,MAAM,cAAcV,EAAUa,EAAK,CAAC,MAAM,YAAY;EAExF,OAAO,gBAAgBb,EAAUU,EAAK,CAAC;EACvC,MAAMV,EAAUU,EAAK,CAAC;EACtB,mBAAiBA;EACjB,eAAe;GACb,EAAQG,CAAI;EACd;CACD,CAAA;AAEL,CAAC"}
|
|
@@ -40,6 +40,15 @@ export interface SegmentedControlItemProps extends Omit<ComponentPropsWithoutRef
|
|
|
40
40
|
children: ReactNode;
|
|
41
41
|
icon?: ReactNode;
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* A small set of mutually exclusive views or modes, side by side, always one selected.
|
|
45
|
+
*
|
|
46
|
+
* @remarks
|
|
47
|
+
* Use a `SegmentedControl` to switch between two to five views of the same thing: list or map, day
|
|
48
|
+
* or week. Exactly one segment is always selected. If the choice changes what content is shown
|
|
49
|
+
* below, consider {@link Tabs | Tabs}; if it's a value in a form, use {@link SegmentedField |
|
|
50
|
+
* SegmentedField}.
|
|
51
|
+
*/
|
|
43
52
|
export declare const SegmentedControl: import("react").ForwardRefExoticComponent<SegmentedControlProps & import("react").RefAttributes<HTMLDivElement>> & {
|
|
44
53
|
Item: import("react").ForwardRefExoticComponent<SegmentedControlItemProps & import("react").RefAttributes<HTMLButtonElement>>;
|
|
45
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SegmentedControl.js","names":["styles","SegmentedControl_module_default","IconOnlyContext","createContext","SegmentedControlItem","forwardRef","rest","iconOnly","useContext","ToggleGroup","cx","className","icon","breakpointAttrs","value","name","toAttr","attrs","base","bp","BREAKPOINTS","v","childValues","values","Children","children","isValidElement","child","SegmentedControlRoot","valueProp","invalidProp","readOnlyProp","disabledProp","idProp","describedByProp","ariaInvalid","labelledByProp","field","useResolvedField","handleBlur","useFocusLeave","onBlur","options","o","uncontrolled","setUncontrolled","useState","defaultValue","handleChange","useCallback","next","onValueChange","index","vars","size","fullWidth","mergeStyles","style","SegmentedControl","markFieldAware"],"sources":["../../../../src/components/actions/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import {\n Children,\n createContext,\n forwardRef,\n isValidElement,\n useCallback,\n useContext,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n} from 'react'\nimport { ToggleGroup } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { BREAKPOINTS, mergeStyles, type Responsive } from '#utils/responsive'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useFocusLeave } from '#components/inputs/CheckboxGroup/choice'\nimport styles from './SegmentedControl.module.css'\n\nexport interface SegmentedControlOption {\n value: string\n label: string\n icon?: ReactNode\n disabled?: boolean\n}\n\ntype RootPrimitiveProps = Omit<\n ComponentPropsWithoutRef<typeof ToggleGroup.Root>,\n 'type' | 'value' | 'defaultValue' | 'onValueChange' | 'children' | 'asChild'\n>\n\nexport interface SegmentedControlProps extends RootPrimitiveProps {\n /** Segments as data. Alternatively pass `SegmentedControl.Item` children. */\n options?: readonly SegmentedControlOption[]\n children?: ReactNode\n /** Controlled selected value. */\n value?: string\n /** Initial value when uncontrolled. Defaults to the first segment. */\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Control size. Responsive: `{ base: 'lg', md: 'md' }` for a bigger touch target on phones. */\n size?: Responsive<Size>\n /**\n * Stretch to the container's width. Segments are always equal widths. Responsive:\n * `{ base: true, md: false }` fills a phone screen and sits inline from `md` up.\n */\n fullWidth?: Responsive<boolean>\n /** Show only icons; labels become each segment's accessible name. */\n iconOnly?: boolean\n /** Emits a hidden input with the selected value, for native form submission. */\n name?: string\n /** Critical edge + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /** Focusable but not editable: sets `aria-readonly` and ignores selection changes. */\n readOnly?: boolean\n}\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentPropsWithoutRef<typeof ToggleGroup.Item>,\n 'children'\n> {\n value: string\n /** Visible label (or accessible name when the control is `iconOnly`). */\n children: ReactNode\n icon?: ReactNode\n}\n\nconst IconOnlyContext = createContext(false)\n\nconst SegmentedControlItem = forwardRef<HTMLButtonElement, SegmentedControlItemProps>(\n function SegmentedControlItem({ icon, className, children, ...rest }, ref) {\n const iconOnly = useContext(IconOnlyContext)\n return (\n <ToggleGroup.Item ref={ref} className={cx(styles.item, className)} {...rest}>\n {icon ? (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className={iconOnly ? styles.srOnly : styles.label}>{children}</span>\n </ToggleGroup.Item>\n )\n },\n)\n\n/**\n * Per-breakpoint data attributes: the base value on `data-<name>`, and each explicitly\n * set larger breakpoint on `data-<name>-<bp>`. CSS applies them in mobile-first media\n * queries, so a later breakpoint overrides an earlier one.\n */\nfunction breakpointAttrs<T extends string | boolean>(\n name: string,\n value: Responsive<T>,\n toAttr: (v: T) => string | undefined,\n): Record<string, string | undefined> {\n if (typeof value !== 'object') return { [`data-${name}`]: toAttr(value) }\n const attrs: Record<string, string | undefined> = {}\n const base = value.base\n attrs[`data-${name}`] = base === undefined ? undefined : toAttr(base)\n for (const bp of BREAKPOINTS) {\n const v = value[bp]\n if (bp !== 'base' && v !== undefined) attrs[`data-${name}-${bp}`] = String(v)\n }\n return attrs\n}\n\nfunction childValues(children: ReactNode): string[] {\n const values: string[] = []\n Children.forEach(children, (child) => {\n if (isValidElement<{ value?: unknown }>(child) && typeof child.props.value === 'string') {\n values.push(child.props.value)\n }\n })\n return values\n}\n\n/**\n * Single-select segmented control: pick one of 2–5 peer views or periods\n * (Month / Quarter / Year · Board / Table / Timeline). One segment is always selected —\n * clicking the current one does nothing.\n *\n * Radix ToggleGroup underneath: segments are `role=\"radio\"` inside a `radiogroup`,\n * arrow keys move focus (roving tabindex), Space/Enter selects. Give the group a name\n * with `aria-label`.\n *\n * Segments are equal widths, so the selection indicator is pure CSS — it slides by\n * index with no measuring, and renders correctly on the server.\n *\n * Field-aware: inside a `<Field>` it is labelled by the Field's label and picks up its\n * description, error, `disabled`, `readOnly` and `validating` (see `SegmentedField`).\n * `onBlur` fires once, when focus leaves the whole control.\n */\nconst SegmentedControlRoot = forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n options,\n children,\n value: valueProp,\n defaultValue,\n onValueChange,\n size = 'md',\n fullWidth = false,\n iconOnly = false,\n name,\n invalid: invalidProp,\n readOnly: readOnlyProp,\n disabled: disabledProp,\n id: idProp,\n onBlur,\n className,\n style,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const handleBlur = useFocusLeave(onBlur)\n const values = options ? options.map((o) => o.value) : childValues(children)\n const [uncontrolled, setUncontrolled] = useState<string | undefined>(defaultValue ?? values[0])\n const value = valueProp ?? uncontrolled\n\n const handleChange = useCallback(\n (next: string) => {\n // ToggleGroup lets you deselect the pressed item; a segmented control can't.\n if (!next || field.readOnly) return\n if (valueProp === undefined) setUncontrolled(next)\n onValueChange?.(next)\n },\n [valueProp, onValueChange, field.readOnly],\n )\n\n const index = value === undefined ? -1 : values.indexOf(value)\n const vars = {\n '--_count': Math.max(values.length, 1),\n '--_index': Math.max(index, 0),\n } as CSSProperties\n\n return (\n <IconOnlyContext.Provider value={iconOnly}>\n <ToggleGroup.Root\n ref={ref}\n type=\"single\"\n role=\"radiogroup\"\n value={value ?? ''}\n onValueChange={handleChange}\n id={field.id}\n disabled={field.disabled}\n aria-labelledby={labelledByProp ?? (rest['aria-label'] ? undefined : field.labelId)}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-readonly={field.readOnly || undefined}\n aria-busy={field.busy || undefined}\n data-invalid={field.invalid || undefined}\n data-readonly={field.readOnly || undefined}\n onBlur={handleBlur}\n className={cx(styles.root, className)}\n {...breakpointAttrs('size', size, String)}\n {...breakpointAttrs('full-width', fullWidth, (v) => (v ? '' : undefined))}\n data-icon-only={iconOnly || undefined}\n style={mergeStyles(vars, style)}\n {...rest}\n >\n {index >= 0 ? <span className={styles.indicator} aria-hidden=\"true\" /> : null}\n {options\n ? options.map((o) => (\n <SegmentedControlItem\n key={o.value}\n value={o.value}\n icon={o.icon}\n disabled={o.disabled}\n >\n {o.label}\n </SegmentedControlItem>\n ))\n : children}\n {name && value ? (\n <input type=\"hidden\" name={name} value={value} disabled={field.disabled || undefined} />\n ) : null}\n </ToggleGroup.Root>\n </IconOnlyContext.Provider>\n )\n },\n)\n\nexport const SegmentedControl = markFieldAware(\n Object.assign(SegmentedControlRoot, { Item: SegmentedControlItem }),\n)\n"],"mappings":";;;;;;;;;AAoEA,IAAME,IAAkBC,EAAc,EAAK,GAErCC,IAAuBC,EAC3B,SAA8B,EAAE,SAAM,cAAW,aAAU,GAAGC,KAAQ,GAAK;CACzE,IAAMC,IAAWC,EAAWN,CAAe;CAC3C,OACE,kBAACO,EAAY,MAAb;EAAuB;EAAK,WAAWC,EAAGV,EAAO,MAAMW,CAAS;EAAG,GAAIL;EAAvE,UAAA,CACGM,IACC,kBAAC,QAAD;GAAM,WAAWZ,EAAO;GAAM,eAAY;GACvC,UAAAY;EACG,CAAA,IACJ,MACJ,kBAAC,QAAD;GAAM,WAAWL,IAAWP,EAAO,SAASA,EAAO;GAAQ;EAAe,CAAA,CAC1D;;AAEtB,CACF;AAOA,SAASa,EACP,GACA,GACA,GACoC;CACpC,IAAI,OAAOC,KAAU,UAAU,OAAO,GAAG,QAAQC,MAASC,EAAOF,CAAK,EAAE;CACxE,IAAMG,IAA4C,CAAC,GAC7CC,IAAOJ,EAAM;CACnB,EAAM,QAAQC,OAAUG,MAAS,KAAA,IAAY,KAAA,IAAYF,EAAOE,CAAI;CACpE,KAAK,IAAMC,KAAMC,GAAa;EAC5B,IAAMC,IAAIP,EAAMK;EAChB,AAAIA,MAAO,UAAUE,MAAM,KAAA,MAAW,EAAM,QAAQN,EAAK,GAAGI,OAAQ,OAAOE,CAAC;CAC9E;CACA,OAAOJ;AACT;AAEA,SAASK,EAAY,GAA+B;CAClD,IAAMC,IAAmB,CAAC;CAM1B,OALAC,EAAS,QAAQC,IAAW,MAAU;EACpC,AAAIC,EAAoCC,CAAK,KAAK,OAAOA,EAAM,MAAM,SAAU,YAC7EJ,EAAO,KAAKI,EAAM,MAAM,KAAK;CAEjC,CAAC,GACMJ;AACT;AAkBA,IAAMK,IAAuBvB,EAC3B,SACE,EACE,YACA,aACA,OAAOwB,GACP,iBACA,kBACA,UAAO,MACP,eAAY,IACZ,cAAW,IACX,SACA,SAASC,GACT,UAAUC,GACV,UAAUC,GACV,IAAIC,GACJ,WACA,cACA,UACA,oBAAoBC,GACpB,gBAAgBC,GAChB,mBAAmBC,GACnB,GAAG9B,KAEL,GACA;CACA,IAAM+B,IAAQC,EAAiB;EAC7B,IAAIL;EACJ,aAAaC;EACb,SAASJ;EACT;EACA,UAAUE;EACV,UAAUD;CACZ,CAAC,GACKQ,IAAaC,EAAcC,CAAM,GACjClB,IAASmB,IAAUA,EAAQ,KAAK,MAAMC,EAAE,KAAK,IAAIrB,EAAYG,CAAQ,GACrE,CAACmB,GAAcC,KAAmBC,EAA6BC,KAAgBxB,EAAO,EAAE,GACxFT,IAAQe,KAAae,GAErBI,IAAeC,GAClB,MAAiB;EAEhB,AAAKC,KAAQ,GAAM,aACfrB,MAAc,KAAA,KAAWgB,EAAgBK,CAAI,GACjDC,IAAgBD,CAAI;CACtB,GACA;EAACrB;EAAWsB;EAAed,EAAM;CAAQ,CAC3C,GAEMe,IAAQtC,MAAU,KAAA,IAAY,KAAKS,EAAO,QAAQT,CAAK,GACvDuC,IAAO;EACX,YAAY,KAAK,IAAI9B,EAAO,QAAQ,CAAC;EACrC,YAAY,KAAK,IAAI6B,GAAO,CAAC;CAC/B;CAEA,OACE,kBAAClD,EAAgB,UAAjB;EAA0B,OAAOK;EAC/B,UAAA,kBAACE,EAAY,MAAb;GACO;GACL,MAAK;GACL,MAAK;GACL,OAAOK,KAAS;GAChB,eAAekC;GACf,IAAIX,EAAM;GACV,UAAUA,EAAM;GAChB,mBAAiBD,MAAmB9B,EAAK,gBAAgB,KAAA,IAAY+B,EAAM;GAC3E,oBAAkBA,EAAM;GACxB,gBAAcA,EAAM,WAAW,KAAA;GAC/B,iBAAeA,EAAM,YAAY,KAAA;GACjC,iBAAeA,EAAM,YAAY,KAAA;GACjC,aAAWA,EAAM,QAAQ,KAAA;GACzB,gBAAcA,EAAM,WAAW,KAAA;GAC/B,iBAAeA,EAAM,YAAY,KAAA;GACjC,QAAQE;GACR,WAAW7B,EAAGV,EAAO,MAAMW,CAAS;GACpC,GAAIE,EAAgB,QAAQyC,GAAM,MAAM;GACxC,GAAIzC,EAAgB,cAAc0C,IAAY,MAAOlC,IAAI,KAAK,KAAA,CAAU;GACxE,kBAAgBd,KAAY,KAAA;GAC5B,OAAOiD,EAAYH,GAAMI,CAAK;GAC9B,GAAInD;GAtBN,UAAA;IAwBG8C,KAAS,IAAI,kBAAC,QAAD;KAAM,WAAWpD,EAAO;KAAW,eAAY;IAAQ,CAAA,IAAI;IACxE0C,IACGA,EAAQ,KAAK,MACX,kBAACtC,GAAD;KAEE,OAAOuC,EAAE;KACT,MAAMA,EAAE;KACR,UAAUA,EAAE;KAEX,UAAAA,EAAE;IACiB,GANfA,EAAE,KAMa,CACvB,IACDlB;IACHV,KAAQD,IACP,kBAAC,SAAD;KAAO,MAAK;KAAe;KAAa;KAAO,UAAUuB,EAAM,YAAY,KAAA;IAAY,CAAA,IACrF;GACY;;CACM,CAAA;AAE9B,CACF,GAEaqB,IAAmBC,EAC9B,OAAO,OAAO/B,GAAsB,EAAE,MAAMxB,EAAqB,CAAC,CACpE"}
|
|
1
|
+
{"version":3,"file":"SegmentedControl.js","names":["styles","SegmentedControl_module_default","IconOnlyContext","createContext","SegmentedControlItem","forwardRef","rest","iconOnly","useContext","ToggleGroup","cx","className","icon","breakpointAttrs","value","name","toAttr","attrs","base","bp","BREAKPOINTS","v","childValues","values","Children","children","isValidElement","child","SegmentedControlRoot","valueProp","invalidProp","readOnlyProp","disabledProp","idProp","describedByProp","ariaInvalid","labelledByProp","field","useResolvedField","handleBlur","useFocusLeave","onBlur","options","o","uncontrolled","setUncontrolled","useState","defaultValue","handleChange","useCallback","next","onValueChange","index","vars","size","fullWidth","mergeStyles","style","SegmentedControl","markFieldAware"],"sources":["../../../../src/components/actions/SegmentedControl/SegmentedControl.tsx"],"sourcesContent":["import {\n Children,\n createContext,\n forwardRef,\n isValidElement,\n useCallback,\n useContext,\n useState,\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type ReactNode,\n} from 'react'\nimport { ToggleGroup } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { BREAKPOINTS, mergeStyles, type Responsive } from '#utils/responsive'\nimport type { Size } from '#utils/tokens'\nimport { markFieldAware, useResolvedField } from '#components/inputs/internal/FieldContext'\nimport { useFocusLeave } from '#components/inputs/CheckboxGroup/choice'\nimport styles from './SegmentedControl.module.css'\n\nexport interface SegmentedControlOption {\n value: string\n label: string\n icon?: ReactNode\n disabled?: boolean\n}\n\ntype RootPrimitiveProps = Omit<\n ComponentPropsWithoutRef<typeof ToggleGroup.Root>,\n 'type' | 'value' | 'defaultValue' | 'onValueChange' | 'children' | 'asChild'\n>\n\nexport interface SegmentedControlProps extends RootPrimitiveProps {\n /** Segments as data. Alternatively pass `SegmentedControl.Item` children. */\n options?: readonly SegmentedControlOption[]\n children?: ReactNode\n /** Controlled selected value. */\n value?: string\n /** Initial value when uncontrolled. Defaults to the first segment. */\n defaultValue?: string\n onValueChange?: (value: string) => void\n /** Control size. Responsive: `{ base: 'lg', md: 'md' }` for a bigger touch target on phones. */\n size?: Responsive<Size>\n /**\n * Stretch to the container's width. Segments are always equal widths. Responsive:\n * `{ base: true, md: false }` fills a phone screen and sits inline from `md` up.\n */\n fullWidth?: Responsive<boolean>\n /** Show only icons; labels become each segment's accessible name. */\n iconOnly?: boolean\n /** Emits a hidden input with the selected value, for native form submission. */\n name?: string\n /** Critical edge + `aria-invalid`. Inside a `<Field error>` this is set for you. */\n invalid?: boolean\n /** Focusable but not editable: sets `aria-readonly` and ignores selection changes. */\n readOnly?: boolean\n}\n\nexport interface SegmentedControlItemProps extends Omit<\n ComponentPropsWithoutRef<typeof ToggleGroup.Item>,\n 'children'\n> {\n value: string\n /** Visible label (or accessible name when the control is `iconOnly`). */\n children: ReactNode\n icon?: ReactNode\n}\n\nconst IconOnlyContext = createContext(false)\n\nconst SegmentedControlItem = forwardRef<HTMLButtonElement, SegmentedControlItemProps>(\n function SegmentedControlItem({ icon, className, children, ...rest }, ref) {\n const iconOnly = useContext(IconOnlyContext)\n return (\n <ToggleGroup.Item ref={ref} className={cx(styles.item, className)} {...rest}>\n {icon ? (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className={iconOnly ? styles.srOnly : styles.label}>{children}</span>\n </ToggleGroup.Item>\n )\n },\n)\n\n/**\n * Per-breakpoint data attributes: the base value on `data-<name>`, and each explicitly\n * set larger breakpoint on `data-<name>-<bp>`. CSS applies them in mobile-first media\n * queries, so a later breakpoint overrides an earlier one.\n */\nfunction breakpointAttrs<T extends string | boolean>(\n name: string,\n value: Responsive<T>,\n toAttr: (v: T) => string | undefined,\n): Record<string, string | undefined> {\n if (typeof value !== 'object') return { [`data-${name}`]: toAttr(value) }\n const attrs: Record<string, string | undefined> = {}\n const base = value.base\n attrs[`data-${name}`] = base === undefined ? undefined : toAttr(base)\n for (const bp of BREAKPOINTS) {\n const v = value[bp]\n if (bp !== 'base' && v !== undefined) attrs[`data-${name}-${bp}`] = String(v)\n }\n return attrs\n}\n\nfunction childValues(children: ReactNode): string[] {\n const values: string[] = []\n Children.forEach(children, (child) => {\n if (isValidElement<{ value?: unknown }>(child) && typeof child.props.value === 'string') {\n values.push(child.props.value)\n }\n })\n return values\n}\n\n/**\n * Single-select segmented control: pick one of 2–5 peer views or periods\n * (Month / Quarter / Year · Board / Table / Timeline). One segment is always selected —\n * clicking the current one does nothing.\n *\n * Radix ToggleGroup underneath: segments are `role=\"radio\"` inside a `radiogroup`,\n * arrow keys move focus (roving tabindex), Space/Enter selects. Give the group a name\n * with `aria-label`.\n *\n * Segments are equal widths, so the selection indicator is pure CSS — it slides by\n * index with no measuring, and renders correctly on the server.\n *\n * Field-aware: inside a `<Field>` it is labelled by the Field's label and picks up its\n * description, error, `disabled`, `readOnly` and `validating` (see `SegmentedField`).\n * `onBlur` fires once, when focus leaves the whole control.\n */\nconst SegmentedControlRoot = forwardRef<HTMLDivElement, SegmentedControlProps>(\n function SegmentedControl(\n {\n options,\n children,\n value: valueProp,\n defaultValue,\n onValueChange,\n size = 'md',\n fullWidth = false,\n iconOnly = false,\n name,\n invalid: invalidProp,\n readOnly: readOnlyProp,\n disabled: disabledProp,\n id: idProp,\n onBlur,\n className,\n style,\n 'aria-describedby': describedByProp,\n 'aria-invalid': ariaInvalid,\n 'aria-labelledby': labelledByProp,\n ...rest\n },\n ref,\n ) {\n const field = useResolvedField({\n id: idProp,\n describedBy: describedByProp,\n invalid: invalidProp,\n ariaInvalid,\n disabled: disabledProp,\n readOnly: readOnlyProp,\n })\n const handleBlur = useFocusLeave(onBlur)\n const values = options ? options.map((o) => o.value) : childValues(children)\n const [uncontrolled, setUncontrolled] = useState<string | undefined>(defaultValue ?? values[0])\n const value = valueProp ?? uncontrolled\n\n const handleChange = useCallback(\n (next: string) => {\n // ToggleGroup lets you deselect the pressed item; a segmented control can't.\n if (!next || field.readOnly) return\n if (valueProp === undefined) setUncontrolled(next)\n onValueChange?.(next)\n },\n [valueProp, onValueChange, field.readOnly],\n )\n\n const index = value === undefined ? -1 : values.indexOf(value)\n const vars = {\n '--_count': Math.max(values.length, 1),\n '--_index': Math.max(index, 0),\n } as CSSProperties\n\n return (\n <IconOnlyContext.Provider value={iconOnly}>\n <ToggleGroup.Root\n ref={ref}\n type=\"single\"\n role=\"radiogroup\"\n value={value ?? ''}\n onValueChange={handleChange}\n id={field.id}\n disabled={field.disabled}\n aria-labelledby={labelledByProp ?? (rest['aria-label'] ? undefined : field.labelId)}\n aria-describedby={field.describedBy}\n aria-invalid={field.invalid || undefined}\n aria-required={field.required || undefined}\n aria-readonly={field.readOnly || undefined}\n aria-busy={field.busy || undefined}\n data-invalid={field.invalid || undefined}\n data-readonly={field.readOnly || undefined}\n onBlur={handleBlur}\n className={cx(styles.root, className)}\n {...breakpointAttrs('size', size, String)}\n {...breakpointAttrs('full-width', fullWidth, (v) => (v ? '' : undefined))}\n data-icon-only={iconOnly || undefined}\n style={mergeStyles(vars, style)}\n {...rest}\n >\n {index >= 0 ? <span className={styles.indicator} aria-hidden=\"true\" /> : null}\n {options\n ? options.map((o) => (\n <SegmentedControlItem\n key={o.value}\n value={o.value}\n icon={o.icon}\n disabled={o.disabled}\n >\n {o.label}\n </SegmentedControlItem>\n ))\n : children}\n {name && value ? (\n <input type=\"hidden\" name={name} value={value} disabled={field.disabled || undefined} />\n ) : null}\n </ToggleGroup.Root>\n </IconOnlyContext.Provider>\n )\n },\n)\n\n/**\n * A small set of mutually exclusive views or modes, side by side, always one selected.\n *\n * @remarks\n * Use a `SegmentedControl` to switch between two to five views of the same thing: list or map, day\n * or week. Exactly one segment is always selected. If the choice changes what content is shown\n * below, consider {@link Tabs | Tabs}; if it's a value in a form, use {@link SegmentedField |\n * SegmentedField}.\n */\nexport const SegmentedControl = markFieldAware(\n Object.assign(SegmentedControlRoot, { Item: SegmentedControlItem }),\n)\n"],"mappings":"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"}
|
|
@@ -9,6 +9,15 @@ export interface ToggleChipProps extends ComponentPropsWithoutRef<typeof Toggle.
|
|
|
9
9
|
count?: number;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
+
* An independent on-off filter that sits in a row with others, with an optional count.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* A `ToggleChip` is a pressed or unpressed button, announced as a toggle (`aria-pressed`). Each
|
|
16
|
+
* chip in a row is independent, so a row of them is a set of filters, not a choice of one. For a
|
|
17
|
+
* set of chips that act together (one of, or several of, one field), use {@link ChipGroup |
|
|
18
|
+
* ChipGroup}.
|
|
19
|
+
*
|
|
20
|
+
* @privateRemarks
|
|
12
21
|
* A pressable filter chip — an on/off switch that reads as a word ("Mine", "Overdue
|
|
13
22
|
* only"). Built on Radix Toggle, so it is a real `<button aria-pressed>`; use
|
|
14
23
|
* `pressed`/`onPressedChange` (controlled) or `defaultPressed`.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ToggleChip.js","names":["styles","chip_module_default","ToggleChip","forwardRef","rest","Toggle","type","cx","className","size","icon","count"],"sources":["../../../../src/components/actions/ToggleChip/ToggleChip.tsx"],"sourcesContent":["import { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react'\nimport { Toggle } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport styles from './chip.module.css'\n\nexport type ToggleChipSize = 'sm' | 'md'\n\nexport interface ToggleChipProps extends ComponentPropsWithoutRef<typeof Toggle.Root> {\n size?: ToggleChipSize\n /** A leading glyph. Keep it meaningful — one icon per row at most. */\n icon?: ReactNode\n /** How many items the filter matches. Rendered in tabular figures after the label. */\n count?: number\n}\n\n/**\n * A pressable filter chip — an on/off switch that reads as a word (\"Mine\", \"Overdue\n * only\"). Built on Radix Toggle, so it is a real `<button aria-pressed>`; use\n * `pressed`/`onPressedChange` (controlled) or `defaultPressed`.\n *\n * Pressed chips take the accent's soft fill. Where the theme has a hard press shadow\n * (Fiesta), resting chips stand proud and pressed chips sit flat — literally pushed in.\n */\nexport const ToggleChip = forwardRef<HTMLButtonElement, ToggleChipProps>(function ToggleChip(\n { size = 'md', icon, count, className, children, type, ...rest },\n ref,\n) {\n return (\n <Toggle.Root\n ref={ref}\n type={type ?? 'button'}\n className={cx(styles.chip, className)}\n data-size={size}\n {...rest}\n >\n {icon ? (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className={styles.label}>{children}</span>\n {count !== undefined ? <span className={styles.count}>{count}</span> : null}\n </Toggle.Root>\n )\n})\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"ToggleChip.js","names":["styles","chip_module_default","ToggleChip","forwardRef","rest","Toggle","type","cx","className","size","icon","count"],"sources":["../../../../src/components/actions/ToggleChip/ToggleChip.tsx"],"sourcesContent":["import { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from 'react'\nimport { Toggle } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport styles from './chip.module.css'\n\nexport type ToggleChipSize = 'sm' | 'md'\n\nexport interface ToggleChipProps extends ComponentPropsWithoutRef<typeof Toggle.Root> {\n size?: ToggleChipSize\n /** A leading glyph. Keep it meaningful — one icon per row at most. */\n icon?: ReactNode\n /** How many items the filter matches. Rendered in tabular figures after the label. */\n count?: number\n}\n\n/**\n * An independent on-off filter that sits in a row with others, with an optional count.\n *\n * @remarks\n * A `ToggleChip` is a pressed or unpressed button, announced as a toggle (`aria-pressed`). Each\n * chip in a row is independent, so a row of them is a set of filters, not a choice of one. For a\n * set of chips that act together (one of, or several of, one field), use {@link ChipGroup |\n * ChipGroup}.\n *\n * @privateRemarks\n * A pressable filter chip — an on/off switch that reads as a word (\"Mine\", \"Overdue\n * only\"). Built on Radix Toggle, so it is a real `<button aria-pressed>`; use\n * `pressed`/`onPressedChange` (controlled) or `defaultPressed`.\n *\n * Pressed chips take the accent's soft fill. Where the theme has a hard press shadow\n * (Fiesta), resting chips stand proud and pressed chips sit flat — literally pushed in.\n */\nexport const ToggleChip = forwardRef<HTMLButtonElement, ToggleChipProps>(function ToggleChip(\n { size = 'md', icon, count, className, children, type, ...rest },\n ref,\n) {\n return (\n <Toggle.Root\n ref={ref}\n type={type ?? 'button'}\n className={cx(styles.chip, className)}\n data-size={size}\n {...rest}\n >\n {icon ? (\n <span className={styles.icon} aria-hidden=\"true\">\n {icon}\n </span>\n ) : null}\n <span className={styles.label}>{children}</span>\n {count !== undefined ? <span className={styles.count}>{count}</span> : null}\n </Toggle.Root>\n )\n})\n"],"mappings":";;;;;;AAgCA,IAAaE,IAAaC,EAA+C,SACvE,EAAE,UAAO,MAAM,SAAM,UAAO,cAAW,aAAU,SAAM,GAAGC,KAC1D,GACA;CACA,OACE,kBAACC,EAAO,MAAR;EACO;EACL,MAAMC,KAAQ;EACd,WAAWC,EAAGP,EAAO,MAAMQ,CAAS;EACpC,aAAWC;EACX,GAAIL;EALN,UAAA;GAOGM,IACC,kBAAC,QAAD;IAAM,WAAWV,EAAO;IAAM,eAAY;IACvC,UAAAU;GACG,CAAA,IACJ;GACJ,kBAAC,QAAD;IAAM,WAAWV,EAAO;IAAQ;GAAe,CAAA;GAC9CW,MAAU,KAAA,IAA4D,OAAhD,kBAAC,QAAD;IAAM,WAAWX,EAAO;IAAQ,UAAAW;GAAY,CAAA;EACxD;;AAEjB,CAAC"}
|
|
@@ -20,8 +20,18 @@ export interface AvatarProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'colo
|
|
|
20
20
|
initials?: string;
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
|
+
* A person or organisation, as an image with an initials fallback on a colour that's always the
|
|
24
|
+
* same for that name.
|
|
25
|
+
*
|
|
26
|
+
* @remarks
|
|
27
|
+
* `Avatar` shows a picture when there is one and falls back to initials when there isn't (or when
|
|
28
|
+
* the image fails to load). The fallback colour comes from the name, so the same person gets the
|
|
29
|
+
* same colour on every screen. Its shape is the theme's `--radius-avatar`: round in Paper, Fiesta
|
|
30
|
+
* and Riso, a rounded square in Monograph and Ledger, and nearly square in Flightdeck.
|
|
31
|
+
*
|
|
32
|
+
* @privateRemarks
|
|
23
33
|
* A person or entity. Image with an initials fallback on a deterministic categorical
|
|
24
34
|
* colour. Shape comes from `--radius-avatar` (rounded square in Monograph/Ledger, round in
|
|
25
|
-
* Fiesta).
|
|
35
|
+
* Paper/Fiesta/Riso, nearly square in Flightdeck).
|
|
26
36
|
*/
|
|
27
37
|
export declare const Avatar: import("react").ForwardRefExoticComponent<AvatarProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Avatar.js","names":["Avatar","AvatarPrimitive","styles","Avatar_module_default","Avatar$1","forwardRef","rest","groupSize","useContext","AvatarSizeContext","label","alt","name","decorative","text","initials","getInitials","glyphs","Avatar","cx","className","size","color","avatarColor","ring","src"],"sources":["../../../../src/components/display/Avatar/Avatar.tsx"],"sourcesContent":["import { forwardRef, useContext, type HTMLAttributes } from 'react'\nimport { Avatar as AvatarPrimitive } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { AvatarSizeContext } from './AvatarContext'\nimport { avatarColor, getInitials } from './avatarUtils'\nimport type { AvatarColor, AvatarSize } from './avatarUtils'\nimport styles from './Avatar.module.css'\n\nexport interface AvatarProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {\n /** Image URL. While loading, or if it fails, the initials fallback shows. */\n src?: string\n /** Accessible name. Defaults to `name`. Pass `\"\"` when a visible label sits beside it. */\n alt?: string\n /** Person or thing. Drives the initials and the fallback colour. */\n name: string\n /** Default `md`, or the enclosing `AvatarGroup`'s size. */\n size?: AvatarSize\n /** Override the name-derived fallback colour (e.g. a member's assigned colour). */\n color?: AvatarColor\n /** A highlight ring — marks \"you\" in a leaderboard or roster. */\n ring?: boolean\n /**\n * Replace the derived initials — a company's short code (\"NWS\"), a logo's letters. Up to three\n * characters; three are set smaller so they fit the frame.\n */\n initials?: string\n}\n\n/**\n * A person or entity. Image with an initials fallback on a deterministic categorical\n * colour. Shape comes from `--radius-avatar` (rounded square in Monograph/Ledger, round in\n * Fiesta).\n */\nexport const Avatar = forwardRef<HTMLSpanElement, AvatarProps>(function Avatar(\n { src, alt, name, size, color, ring = false, initials, className, ...rest },\n ref,\n) {\n const groupSize = useContext(AvatarSizeContext)\n const label = alt ?? name\n const decorative = label === ''\n const text = initials?.trim() ? initials.trim().toLocaleUpperCase() : getInitials(name)\n const glyphs = Array.from(text).length\n return (\n <AvatarPrimitive.Root\n ref={ref}\n className={cx(styles.avatar, className)}\n data-size={size ?? groupSize ?? 'md'}\n data-color={color ?? avatarColor(name)}\n data-ring={ring || undefined}\n {...rest}\n >\n {src ? <AvatarPrimitive.Image className={styles.image} src={src} alt={label} /> : null}\n <AvatarPrimitive.Fallback\n className={styles.fallback}\n role={decorative ? undefined : 'img'}\n aria-label={decorative ? undefined : label}\n aria-hidden={decorative || undefined}\n delayMs={src ? 300 : undefined}\n data-glyphs={glyphs > 2 ? 'many' : undefined}\n >\n <span aria-hidden=\"true\">{text}</span>\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n )\n})\n"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"Avatar.js","names":["Avatar","AvatarPrimitive","styles","Avatar_module_default","Avatar$1","forwardRef","rest","groupSize","useContext","AvatarSizeContext","label","alt","name","decorative","text","initials","getInitials","glyphs","Avatar","cx","className","size","color","avatarColor","ring","src"],"sources":["../../../../src/components/display/Avatar/Avatar.tsx"],"sourcesContent":["import { forwardRef, useContext, type HTMLAttributes } from 'react'\nimport { Avatar as AvatarPrimitive } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { AvatarSizeContext } from './AvatarContext'\nimport { avatarColor, getInitials } from './avatarUtils'\nimport type { AvatarColor, AvatarSize } from './avatarUtils'\nimport styles from './Avatar.module.css'\n\nexport interface AvatarProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {\n /** Image URL. While loading, or if it fails, the initials fallback shows. */\n src?: string\n /** Accessible name. Defaults to `name`. Pass `\"\"` when a visible label sits beside it. */\n alt?: string\n /** Person or thing. Drives the initials and the fallback colour. */\n name: string\n /** Default `md`, or the enclosing `AvatarGroup`'s size. */\n size?: AvatarSize\n /** Override the name-derived fallback colour (e.g. a member's assigned colour). */\n color?: AvatarColor\n /** A highlight ring — marks \"you\" in a leaderboard or roster. */\n ring?: boolean\n /**\n * Replace the derived initials — a company's short code (\"NWS\"), a logo's letters. Up to three\n * characters; three are set smaller so they fit the frame.\n */\n initials?: string\n}\n\n/**\n * A person or organisation, as an image with an initials fallback on a colour that's always the\n * same for that name.\n *\n * @remarks\n * `Avatar` shows a picture when there is one and falls back to initials when there isn't (or when\n * the image fails to load). The fallback colour comes from the name, so the same person gets the\n * same colour on every screen. Its shape is the theme's `--radius-avatar`: round in Paper, Fiesta\n * and Riso, a rounded square in Monograph and Ledger, and nearly square in Flightdeck.\n *\n * @privateRemarks\n * A person or entity. Image with an initials fallback on a deterministic categorical\n * colour. Shape comes from `--radius-avatar` (rounded square in Monograph/Ledger, round in\n * Paper/Fiesta/Riso, nearly square in Flightdeck).\n */\nexport const Avatar = forwardRef<HTMLSpanElement, AvatarProps>(function Avatar(\n { src, alt, name, size, color, ring = false, initials, className, ...rest },\n ref,\n) {\n const groupSize = useContext(AvatarSizeContext)\n const label = alt ?? name\n const decorative = label === ''\n const text = initials?.trim() ? initials.trim().toLocaleUpperCase() : getInitials(name)\n const glyphs = Array.from(text).length\n return (\n <AvatarPrimitive.Root\n ref={ref}\n className={cx(styles.avatar, className)}\n data-size={size ?? groupSize ?? 'md'}\n data-color={color ?? avatarColor(name)}\n data-ring={ring || undefined}\n {...rest}\n >\n {src ? <AvatarPrimitive.Image className={styles.image} src={src} alt={label} /> : null}\n <AvatarPrimitive.Fallback\n className={styles.fallback}\n role={decorative ? undefined : 'img'}\n aria-label={decorative ? undefined : label}\n aria-hidden={decorative || undefined}\n delayMs={src ? 300 : undefined}\n data-glyphs={glyphs > 2 ? 'many' : undefined}\n >\n <span aria-hidden=\"true\">{text}</span>\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n )\n})\n"],"mappings":";;;;;;;;AA2CA,IAAaA,IAASK,EAAyC,SAC7D,EAAE,QAAK,QAAK,SAAM,SAAM,UAAO,UAAO,IAAO,aAAU,cAAW,GAAGC,KACrE,GACA;CACA,IAAMC,IAAYC,EAAWC,CAAiB,GACxCC,IAAQC,KAAOC,GACfC,IAAaH,MAAU,IACvBI,IAAOC,GAAU,KAAK,IAAIA,EAAS,KAAK,CAAC,CAAC,kBAAkB,IAAIC,EAAYJ,CAAI,GAChFK,IAAS,MAAM,KAAKH,CAAI,CAAC,CAAC;CAChC,OACE,kBAACb,EAAgB,MAAjB;EACO;EACL,WAAWkB,EAAGjB,EAAO,QAAQkB,CAAS;EACtC,aAAWC,KAAQd,KAAa;EAChC,cAAYe,KAASC,EAAYX,CAAI;EACrC,aAAWY,KAAQ,KAAA;EACnB,GAAIlB;EANN,UAAA,CAQGmB,IAAM,kBAACxB,EAAgB,OAAjB;GAAuB,WAAWC,EAAO;GAAY;GAAK,KAAKQ;EAAQ,CAAA,IAAI,MAClF,kBAACT,EAAgB,UAAjB;GACE,WAAWC,EAAO;GAClB,MAAMW,IAAa,KAAA,IAAY;GAC/B,cAAYA,IAAa,KAAA,IAAYH;GACrC,eAAaG,KAAc,KAAA;GAC3B,SAASY,IAAM,MAAM,KAAA;GACrB,eAAaR,IAAS,IAAI,SAAS,KAAA;GAEnC,UAAA,kBAAC,QAAD;IAAM,eAAY;IAAQ,UAAAH;GAAW,CAAA;EACb,CAAA,CACN;;AAE1B,CAAC"}
|
|
@@ -9,6 +9,13 @@ export interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
|
9
9
|
'aria-label'?: string;
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
+
* An overlapping row of avatars, like who's on a crew or who reacted, with a count for the rest.
|
|
13
|
+
*
|
|
14
|
+
* @remarks
|
|
15
|
+
* `AvatarGroup` overlaps its `Avatar` children and, past `max`, replaces the rest with a count.
|
|
16
|
+
* Give it an `aria-label` that names the group.
|
|
17
|
+
*
|
|
18
|
+
* @privateRemarks
|
|
12
19
|
* An overlapping stack of `Avatar`s — who reacted, who owns a project, who's in a group.
|
|
13
20
|
*
|
|
14
21
|
* <AvatarGroup max={4} aria-label="Members"><Avatar name="Noor" />…</AvatarGroup>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AvatarGroup.js","names":["styles","AvatarGroup_module_default","AvatarGroup","forwardRef","rest","items","Children","children","isValidElement","limit","max","overflow","AvatarSizeContext","size","cx","className"],"sources":["../../../../src/components/display/AvatarGroup/AvatarGroup.tsx"],"sourcesContent":["import { Children, forwardRef, isValidElement, type HTMLAttributes } from 'react'\nimport { cx } from '#utils/cx'\nimport { AvatarSizeContext, type AvatarSize } from '#components/display/Avatar'\nimport styles from './AvatarGroup.module.css'\n\nexport interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Show at most this many avatars; the rest collapse into a \"+N\" count. */\n max?: number\n /** Size for every avatar in the stack. Default `sm`. */\n size?: AvatarSize\n /** Accessible name for the group, e.g. \"Reacted: Noor, Kofi and 3 others\". */\n 'aria-label'?: string\n}\n\n/**\n * An overlapping stack of `Avatar`s — who reacted, who owns a project, who's in a group.\n *\n * <AvatarGroup max={4} aria-label=\"Members\"><Avatar name=\"Noor\" />…</AvatarGroup>\n */\nexport const AvatarGroup = forwardRef<HTMLDivElement, AvatarGroupProps>(function AvatarGroup(\n { max, size = 'sm', className, children, ...rest },\n ref,\n) {\n const items = Children.toArray(children).filter(isValidElement)\n const limit = max !== undefined && max >= 0 && items.length > max ? max : items.length\n const overflow = items.length - limit\n return (\n <AvatarSizeContext.Provider value={size}>\n <div\n ref={ref}\n role=\"group\"\n className={cx(styles.group, className)}\n data-size={size}\n {...rest}\n >\n {items.slice(0, limit)}\n {overflow > 0 ? (\n <span\n className={styles.more}\n data-size={size}\n role=\"img\"\n aria-label={`${String(overflow)} more`}\n >\n <span aria-hidden=\"true\">+{overflow}</span>\n </span>\n ) : null}\n </div>\n </AvatarSizeContext.Provider>\n )\n})\n"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"AvatarGroup.js","names":["styles","AvatarGroup_module_default","AvatarGroup","forwardRef","rest","items","Children","children","isValidElement","limit","max","overflow","AvatarSizeContext","size","cx","className"],"sources":["../../../../src/components/display/AvatarGroup/AvatarGroup.tsx"],"sourcesContent":["import { Children, forwardRef, isValidElement, type HTMLAttributes } from 'react'\nimport { cx } from '#utils/cx'\nimport { AvatarSizeContext, type AvatarSize } from '#components/display/Avatar'\nimport styles from './AvatarGroup.module.css'\n\nexport interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Show at most this many avatars; the rest collapse into a \"+N\" count. */\n max?: number\n /** Size for every avatar in the stack. Default `sm`. */\n size?: AvatarSize\n /** Accessible name for the group, e.g. \"Reacted: Noor, Kofi and 3 others\". */\n 'aria-label'?: string\n}\n\n/**\n * An overlapping row of avatars, like who's on a crew or who reacted, with a count for the rest.\n *\n * @remarks\n * `AvatarGroup` overlaps its `Avatar` children and, past `max`, replaces the rest with a count.\n * Give it an `aria-label` that names the group.\n *\n * @privateRemarks\n * An overlapping stack of `Avatar`s — who reacted, who owns a project, who's in a group.\n *\n * <AvatarGroup max={4} aria-label=\"Members\"><Avatar name=\"Noor\" />…</AvatarGroup>\n */\nexport const AvatarGroup = forwardRef<HTMLDivElement, AvatarGroupProps>(function AvatarGroup(\n { max, size = 'sm', className, children, ...rest },\n ref,\n) {\n const items = Children.toArray(children).filter(isValidElement)\n const limit = max !== undefined && max >= 0 && items.length > max ? max : items.length\n const overflow = items.length - limit\n return (\n <AvatarSizeContext.Provider value={size}>\n <div\n ref={ref}\n role=\"group\"\n className={cx(styles.group, className)}\n data-size={size}\n {...rest}\n >\n {items.slice(0, limit)}\n {overflow > 0 ? (\n <span\n className={styles.more}\n data-size={size}\n role=\"img\"\n aria-label={`${String(overflow)} more`}\n >\n <span aria-hidden=\"true\">+{overflow}</span>\n </span>\n ) : null}\n </div>\n </AvatarSizeContext.Provider>\n )\n})\n"],"mappings":";;;;;;AA0BA,IAAaE,IAAcC,EAA6C,SACtE,EAAE,QAAK,UAAO,MAAM,cAAW,aAAU,GAAGC,KAC5C,GACA;CACA,IAAMC,IAAQC,EAAS,QAAQC,CAAQ,CAAC,CAAC,OAAOC,CAAc,GACxDC,IAAQC,MAAQ,KAAA,KAAaA,KAAO,KAAKL,EAAM,SAASK,IAAMA,IAAML,EAAM,QAC1EM,IAAWN,EAAM,SAASI;CAChC,OACE,kBAACG,EAAkB,UAAnB;EAA4B,OAAOC;EACjC,UAAA,kBAAC,OAAD;GACO;GACL,MAAK;GACL,WAAWC,EAAGd,EAAO,OAAOe,CAAS;GACrC,aAAWF;GACX,GAAIT;GALN,UAAA,CAOGC,EAAM,MAAM,GAAGI,CAAK,GACpBE,IAAW,IACV,kBAAC,QAAD;IACE,WAAWX,EAAO;IAClB,aAAWa;IACX,MAAK;IACL,cAAY,GAAG,OAAOF,CAAQ,EAAE;IAEhC,UAAA,kBAAC,QAAD;KAAM,eAAY;KAAlB,UAAA,CAAyB,KAAEA,CAAe;;GACtC,CAAA,IACJ,IACD;;CACqB,CAAA;AAEhC,CAAC"}
|
|
@@ -12,6 +12,13 @@ export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
12
12
|
dot?: boolean;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
+
* A short word or count that says what state something is in. Live, delayed, 3 new.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* A `Badge` is status: what state a thing is in right now. For categories and metadata (a line, a
|
|
19
|
+
* facility, a topic) use a {@link Tag | Tag}, which is quieter.
|
|
20
|
+
*
|
|
21
|
+
* @privateRemarks
|
|
15
22
|
* A short status or count: "Live", "Over quota", "3 new", "Overdue".
|
|
16
23
|
* Metadata and categories are Tags; a Badge says what *state* something is in.
|
|
17
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Badge.js","names":["styles","Badge_module_default","Badge","forwardRef","rest","cx","className","tone","variant","size","dot","children"],"sources":["../../../../src/components/display/Badge/Badge.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes } from 'react'\nimport { cx } from '#utils/cx'\nimport type { Tone } from '#utils/tokens'\nimport styles from './Badge.module.css'\n\nexport type BadgeVariant = 'soft' | 'solid' | 'outline'\nexport type BadgeSize = 'sm' | 'md'\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Status colour. `neutral` for plain counts and labels. Default `neutral`. */\n tone?: Tone\n /** `soft` (default) for most status; `solid` for the one that must be seen; `outline` for quiet. */\n variant?: BadgeVariant\n size?: BadgeSize\n /** Leading status dot in the tone colour. Decorative — the label carries the meaning. */\n dot?: boolean\n}\n\n/**\n * A short status or count: \"Live\", \"Over quota\", \"3 new\", \"Overdue\".\n * Metadata and categories are Tags; a Badge says what *state* something is in.\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { tone = 'neutral', variant = 'soft', size = 'sm', dot = false, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cx(styles.badge, className)}\n data-tone={tone}\n data-variant={variant}\n data-size={size}\n {...rest}\n >\n {dot ? <span className={styles.dot} aria-hidden=\"true\" /> : null}\n {children}\n </span>\n )\n})\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"Badge.js","names":["styles","Badge_module_default","Badge","forwardRef","rest","cx","className","tone","variant","size","dot","children"],"sources":["../../../../src/components/display/Badge/Badge.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes } from 'react'\nimport { cx } from '#utils/cx'\nimport type { Tone } from '#utils/tokens'\nimport styles from './Badge.module.css'\n\nexport type BadgeVariant = 'soft' | 'solid' | 'outline'\nexport type BadgeSize = 'sm' | 'md'\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Status colour. `neutral` for plain counts and labels. Default `neutral`. */\n tone?: Tone\n /** `soft` (default) for most status; `solid` for the one that must be seen; `outline` for quiet. */\n variant?: BadgeVariant\n size?: BadgeSize\n /** Leading status dot in the tone colour. Decorative — the label carries the meaning. */\n dot?: boolean\n}\n\n/**\n * A short word or count that says what state something is in. Live, delayed, 3 new.\n *\n * @remarks\n * A `Badge` is status: what state a thing is in right now. For categories and metadata (a line, a\n * facility, a topic) use a {@link Tag | Tag}, which is quieter.\n *\n * @privateRemarks\n * A short status or count: \"Live\", \"Over quota\", \"3 new\", \"Overdue\".\n * Metadata and categories are Tags; a Badge says what *state* something is in.\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { tone = 'neutral', variant = 'soft', size = 'sm', dot = false, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cx(styles.badge, className)}\n data-tone={tone}\n data-variant={variant}\n data-size={size}\n {...rest}\n >\n {dot ? <span className={styles.dot} aria-hidden=\"true\" /> : null}\n {children}\n </span>\n )\n})\n"],"mappings":";;;;;AA6BA,IAAaE,IAAQC,EAAwC,SAC3D,EAAE,UAAO,WAAW,aAAU,QAAQ,UAAO,MAAM,SAAM,IAAO,cAAW,aAAU,GAAGC,KACxF,GACA;CACA,OACE,kBAAC,QAAD;EACO;EACL,WAAWC,EAAGL,EAAO,OAAOM,CAAS;EACrC,aAAWC;EACX,gBAAcC;EACd,aAAWC;EACX,GAAIL;EANN,UAAA,CAQGM,IAAM,kBAAC,QAAD;GAAM,WAAWV,EAAO;GAAK,eAAY;EAAQ,CAAA,IAAI,MAC3DW,CACG;;AAEV,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Card.js","names":["styles","Card_module_default","CardRoot","forwardRef","rest","Comp","asChild","Slot","cx","className","variant","interactive","mergeStyles","responsiveVars","padding","space","style","CardMedia","ratio","inset","CardHeader","CardTitle","Tag","headingTag","level","CardDescription","CardBody","CardFooter","CardMeta","Card"],"sources":["../../../../src/components/display/Card/Card.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { mergeStyles, responsiveVars, type Responsive } from '#utils/responsive'\nimport { space, type Space } from '#utils/tokens'\nimport styles from './Card.module.css'\nimport { headingTag } from '#utils/heading'\n\nexport type CardVariant = 'plain' | 'outline' | 'raised'\nexport type CardTitleLevel = 2 | 3 | 4 | 5 | 6\nexport type CardMediaRatio = '1/1' | '4/3' | '3/2' | '16/9' | '21/9'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Edge treatment. `outline` (default) is a hairline; `plain` is a fill with no edge;\n * `raised` stands on `--shadow-surface` (nothing extra in Monograph/Ledger, the print\n * offset in Fiesta).\n */\n variant?: CardVariant\n /** Inner padding as a space step. Responsive. Default `5`. */\n padding?: Responsive<Space>\n /** Hover/press affordance for cards that *are* the action. Implied by `asChild`. */\n interactive?: boolean\n /**\n * Render the single child element as the card — usually a link, so the whole card is\n * one target: `<Card asChild><a href=\"/projects/atlas\">…</a></Card>`. Don't nest other\n * links or buttons inside a linked card.\n */\n asChild?: boolean\n}\n\n/**\n * A self-contained object: a project, a release, a plan summary. Use sparingly —\n * most content reads better as a List, Table or plain section (DESIGN §2).\n *\n * <Card><Card.Header><Card.Title>Atlas</Card.Title><Card.Meta>2026</Card.Meta></Card.Header>…</Card>\n */\nconst CardRoot = forwardRef<HTMLDivElement, CardProps>(function Card(\n { variant = 'outline', padding, interactive = false, asChild = false, className, style, ...rest },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'div'\n return (\n <Comp\n ref={ref}\n className={cx(styles.card, className)}\n data-kiln-component=\"\"\n data-variant={variant}\n data-interactive={interactive || asChild || undefined}\n style={mergeStyles(responsiveVars('card-padding', padding, space), style)}\n {...rest}\n />\n )\n})\n\nexport interface CardMediaProps extends HTMLAttributes<HTMLDivElement> {\n /** Crop the media to a fixed ratio (the child img/video is fitted with `cover`). */\n ratio?: CardMediaRatio\n /**\n * Sit inside the padding instead of bleeding to the edges. The corner radius is then\n * the nested radius (outer − padding).\n */\n inset?: boolean\n}\n\n/** Image/video slot. Bleeds to the card's edges by default; put it first (or last). */\nconst CardMedia = forwardRef<HTMLDivElement, CardMediaProps>(function CardMedia(\n { ratio, inset = false, className, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cx(styles.media, className)}\n data-ratio={ratio}\n data-inset={inset || undefined}\n {...rest}\n />\n )\n})\n\nconst CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardHeader(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.header, className)} {...rest} />\n})\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n /** Heading element, to fit the page outline. Visual size doesn't change. Default `3`. */\n level?: CardTitleLevel\n}\n\nconst CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(function CardTitle(\n { level = 3, className, ...rest },\n ref,\n) {\n const Tag = headingTag(level)\n return <Tag ref={ref} className={cx(styles.title, className)} {...rest} />\n})\n\nconst CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n function CardDescription({ className, ...rest }, ref) {\n return <p ref={ref} className={cx(styles.description, className)} {...rest} />\n },\n)\n\nconst CardBody = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardBody(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.body, className)} {...rest} />\n})\n\n/** Actions or summary pinned to the bottom of the card (pushes down in equal-height grids). */\nconst CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardFooter(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.footer, className)} {...rest} />\n})\n\n/** Small secondary facts — year, stack, due date. Numbers are tabular. */\nconst CardMeta = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardMeta(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.meta, className)} {...rest} />\n})\n\nexport const Card = Object.assign(CardRoot, {\n Media: CardMedia,\n Header: CardHeader,\n Title: CardTitle,\n Description: CardDescription,\n Body: CardBody,\n Footer: CardFooter,\n Meta: CardMeta,\n})\n"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"Card.js","names":["styles","Card_module_default","CardRoot","forwardRef","rest","Comp","asChild","Slot","cx","className","variant","interactive","mergeStyles","responsiveVars","padding","space","style","CardMedia","ratio","inset","CardHeader","CardTitle","Tag","headingTag","level","CardDescription","CardBody","CardFooter","CardMeta","Card"],"sources":["../../../../src/components/display/Card/Card.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes } from 'react'\nimport { Slot } from 'radix-ui'\nimport { cx } from '#utils/cx'\nimport { mergeStyles, responsiveVars, type Responsive } from '#utils/responsive'\nimport { space, type Space } from '#utils/tokens'\nimport styles from './Card.module.css'\nimport { headingTag } from '#utils/heading'\n\nexport type CardVariant = 'plain' | 'outline' | 'raised'\nexport type CardTitleLevel = 2 | 3 | 4 | 5 | 6\nexport type CardMediaRatio = '1/1' | '4/3' | '3/2' | '16/9' | '21/9'\n\nexport interface CardProps extends HTMLAttributes<HTMLDivElement> {\n /**\n * Edge treatment. `outline` (default) is a hairline; `plain` is a fill with no edge;\n * `raised` stands on `--shadow-surface` (nothing extra in Monograph/Ledger, the print\n * offset in Fiesta).\n */\n variant?: CardVariant\n /** Inner padding as a space step. Responsive. Default `5`. */\n padding?: Responsive<Space>\n /** Hover/press affordance for cards that *are* the action. Implied by `asChild`. */\n interactive?: boolean\n /**\n * Render the single child element as the card — usually a link, so the whole card is\n * one target: `<Card asChild><a href=\"/projects/atlas\">…</a></Card>`. Don't nest other\n * links or buttons inside a linked card.\n */\n asChild?: boolean\n}\n\n/**\n * A self-contained object, like a route, a ticket or a project. Use it sparingly; most content\n * reads better without a box.\n *\n * @remarks\n * A `Card` frames one object you could pick up, open or drag. Before reaching for one, try a\n * `List`, a `Table` or a plain section: three identical cards in a row is the most generic layout\n * there is, and most content is better as a document than as a set of boxes.\n *\n * ## Slots\n *\n * `Card.Media`, `Card.Header` (with `Card.Title` and `Card.Meta`), `Card.Description`, `Card.Body`\n * and `Card.Footer`. Use only the ones the object needs. `variant` is `outline` (the default, a\n * hairline), `raised` (the theme's surface shadow) or `plain` (a fill and no edge). `padding`\n * takes a space step.\n *\n * @privateRemarks\n * A self-contained object: a project, a release, a plan summary. Use sparingly —\n * most content reads better as a List, Table or plain section (DESIGN §2).\n *\n * <Card><Card.Header><Card.Title>Atlas</Card.Title><Card.Meta>2026</Card.Meta></Card.Header>…</Card>\n */\nconst CardRoot = forwardRef<HTMLDivElement, CardProps>(function Card(\n { variant = 'outline', padding, interactive = false, asChild = false, className, style, ...rest },\n ref,\n) {\n const Comp = asChild ? Slot.Root : 'div'\n return (\n <Comp\n ref={ref}\n className={cx(styles.card, className)}\n data-kiln-component=\"\"\n data-variant={variant}\n data-interactive={interactive || asChild || undefined}\n style={mergeStyles(responsiveVars('card-padding', padding, space), style)}\n {...rest}\n />\n )\n})\n\nexport interface CardMediaProps extends HTMLAttributes<HTMLDivElement> {\n /** Crop the media to a fixed ratio (the child img/video is fitted with `cover`). */\n ratio?: CardMediaRatio\n /**\n * Sit inside the padding instead of bleeding to the edges. The corner radius is then\n * the nested radius (outer − padding).\n */\n inset?: boolean\n}\n\n/** Image/video slot. Bleeds to the card's edges by default; put it first (or last). */\nconst CardMedia = forwardRef<HTMLDivElement, CardMediaProps>(function CardMedia(\n { ratio, inset = false, className, ...rest },\n ref,\n) {\n return (\n <div\n ref={ref}\n className={cx(styles.media, className)}\n data-ratio={ratio}\n data-inset={inset || undefined}\n {...rest}\n />\n )\n})\n\nconst CardHeader = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardHeader(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.header, className)} {...rest} />\n})\n\nexport interface CardTitleProps extends HTMLAttributes<HTMLHeadingElement> {\n /** Heading element, to fit the page outline. Visual size doesn't change. Default `3`. */\n level?: CardTitleLevel\n}\n\nconst CardTitle = forwardRef<HTMLHeadingElement, CardTitleProps>(function CardTitle(\n { level = 3, className, ...rest },\n ref,\n) {\n const Tag = headingTag(level)\n return <Tag ref={ref} className={cx(styles.title, className)} {...rest} />\n})\n\nconst CardDescription = forwardRef<HTMLParagraphElement, HTMLAttributes<HTMLParagraphElement>>(\n function CardDescription({ className, ...rest }, ref) {\n return <p ref={ref} className={cx(styles.description, className)} {...rest} />\n },\n)\n\nconst CardBody = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardBody(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.body, className)} {...rest} />\n})\n\n/** Actions or summary pinned to the bottom of the card (pushes down in equal-height grids). */\nconst CardFooter = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardFooter(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.footer, className)} {...rest} />\n})\n\n/** Small secondary facts — year, stack, due date. Numbers are tabular. */\nconst CardMeta = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(function CardMeta(\n { className, ...rest },\n ref,\n) {\n return <div ref={ref} className={cx(styles.meta, className)} {...rest} />\n})\n\nexport const Card = Object.assign(CardRoot, {\n Media: CardMedia,\n Header: CardHeader,\n Title: CardTitle,\n Description: CardDescription,\n Body: CardBody,\n Footer: CardFooter,\n Meta: CardMeta,\n})\n"],"mappings":";;;;;;;;;AAqDA,IAAME,IAAWC,EAAsC,SACrD,EAAE,aAAU,WAAW,YAAS,iBAAc,IAAO,aAAU,IAAO,cAAW,UAAO,GAAGC,KAC3F,GACA;CACA,IAAMC,IAAOC,IAAUC,EAAK,OAAO;CACnC,OACE,kBAACF,GAAD;EACO;EACL,WAAWG,EAAGR,EAAO,MAAMS,CAAS;EACpC,uBAAoB;EACpB,gBAAcC;EACd,oBAAkBC,KAAeL,KAAW,KAAA;EAC5C,OAAOM,EAAYC,EAAe,gBAAgBC,GAASC,CAAK,GAAGC,CAAK;EACxE,GAAIZ;CACL,CAAA;AAEL,CAAC,GAaKa,IAAYd,EAA2C,SAC3D,EAAE,UAAO,WAAQ,IAAO,cAAW,GAAGC,KACtC,GACA;CACA,OACE,kBAAC,OAAD;EACO;EACL,WAAWI,EAAGR,EAAO,OAAOS,CAAS;EACrC,cAAYS;EACZ,cAAYC,KAAS,KAAA;EACrB,GAAIf;CACL,CAAA;AAEL,CAAC,GAEKgB,IAAajB,EAA2D,SAC5E,EAAE,cAAW,GAAGC,KAChB,GACA;CACA,OAAO,kBAAC,OAAD;EAAU;EAAK,WAAWI,EAAGR,EAAO,QAAQS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC5E,CAAC,GAOKiB,IAAYlB,EAA+C,SAC/D,EAAE,WAAQ,GAAG,cAAW,GAAGC,KAC3B,GACA;CACA,IAAMkB,IAAMC,EAAWC,CAAK;CAC5B,OAAO,kBAACF,GAAD;EAAU;EAAK,WAAWd,EAAGR,EAAO,OAAOS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC3E,CAAC,GAEKqB,IAAkBtB,EACtB,SAAyB,EAAE,cAAW,GAAGC,KAAQ,GAAK;CACpD,OAAO,kBAAC,KAAD;EAAQ;EAAK,WAAWI,EAAGR,EAAO,aAAaS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC/E,CACF,GAEMsB,IAAWvB,EAA2D,SAC1E,EAAE,cAAW,GAAGC,KAChB,GACA;CACA,OAAO,kBAAC,OAAD;EAAU;EAAK,WAAWI,EAAGR,EAAO,MAAMS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC1E,CAAC,GAGKuB,IAAaxB,EAA2D,SAC5E,EAAE,cAAW,GAAGC,KAChB,GACA;CACA,OAAO,kBAAC,OAAD;EAAU;EAAK,WAAWI,EAAGR,EAAO,QAAQS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC5E,CAAC,GAGKwB,IAAWzB,EAA2D,SAC1E,EAAE,cAAW,GAAGC,KAChB,GACA;CACA,OAAO,kBAAC,OAAD;EAAU;EAAK,WAAWI,EAAGR,EAAO,MAAMS,CAAS;EAAG,GAAIL;CAAO,CAAA;AAC1E,CAAC,GAEYyB,IAAO,OAAO,OAAO3B,GAAU;CAC1C,OAAOe;CACP,QAAQG;CACR,OAAOC;CACP,aAAaI;CACb,MAAMC;CACN,QAAQC;CACR,MAAMC;AACR,CAAC"}
|
|
@@ -18,6 +18,15 @@ export interface CodeBlockProps extends Omit<HTMLAttributes<HTMLElement>, 'title
|
|
|
18
18
|
copyLabel?: string;
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
+
* A block of source with an optional filename bar, line numbers, highlighted lines and a copy
|
|
22
|
+
* button.
|
|
23
|
+
*
|
|
24
|
+
* @remarks
|
|
25
|
+
* `CodeBlock` shows code: a snippet in an article, a command, a config example. It's a plain
|
|
26
|
+
* surface with an optional title bar, never fake window chrome, and it has no syntax highlighting:
|
|
27
|
+
* the code is the design. Every code sample in these docs is a `CodeBlock`.
|
|
28
|
+
*
|
|
29
|
+
* @privateRemarks
|
|
21
30
|
* A block of source: a snippet in a blog post, a command in a README, a config example.
|
|
22
31
|
* A plain surface with an optional filename bar — no fake window chrome.
|
|
23
32
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CodeBlock.js","names":["styles","CodeBlock_module_default","CodeBlock","forwardRef","rest","copy","setCopy","useState","timer","useRef","captionId","useId","source","code","lines","highlighted","highlightLines","hasHeader","title","language","copyable","onCopy","cx","className","wrap","showLineNumbers","Button","jsx","CheckIcon","CopyIcon","copyLabel","i","line"],"sources":["../../../../src/components/display/CodeBlock/CodeBlock.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { CheckIcon, CopyIcon } from '#icons'\nimport { Button } from '#components/actions/Button'\nimport { cx } from '#utils/cx'\nimport styles from './CodeBlock.module.css'\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n /** The source, verbatim. Trailing newlines are trimmed. */\n code: string\n /** Shown as a label (\"TypeScript\", \"bash\"). No syntax highlighting — the code is the design. */\n language?: string\n /** Filename or caption shown in the header: `src/queries/keys.ts`. */\n title?: ReactNode\n /** Number the lines in a gutter (not copied with a selection). */\n showLineNumbers?: boolean\n /** 1-based line numbers to mark with the theme's highlight colour. */\n highlightLines?: number[]\n /** Show a copy button. Default `true`. */\n copyable?: boolean\n /** Wrap long lines instead of scrolling sideways. */\n wrap?: boolean\n /** Accessible name of the copy button. Default \"Copy code\". */\n copyLabel?: string\n}\n\ntype CopyState = 'idle' | 'copied' | 'failed'\n\n/**\n * A block of source: a snippet in a blog post, a command in a README, a config example.\n * A plain surface with an optional filename bar — no fake window chrome.\n *\n * <CodeBlock title=\"queries/keys.ts\" language=\"TypeScript\" code={source} highlightLines={[3, 4]} />\n */\nexport const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language,\n title,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n wrap = false,\n copyLabel = 'Copy code',\n className,\n ...rest\n },\n ref,\n) {\n const [copy, setCopy] = useState<CopyState>('idle')\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const captionId = useId()\n const source = code.replace(/\\n+$/, '')\n const lines = source.split('\\n')\n const highlighted = new Set(highlightLines)\n const hasHeader = Boolean(title) || Boolean(language) || copyable\n\n useEffect(\n () => () => {\n clearTimeout(timer.current)\n },\n [],\n )\n\n const onCopy = async () => {\n clearTimeout(timer.current)\n try {\n await navigator.clipboard.writeText(source)\n setCopy('copied')\n } catch {\n setCopy('failed')\n }\n timer.current = setTimeout(() => {\n setCopy('idle')\n }, 2000)\n }\n\n return (\n <figure\n ref={ref}\n className={cx(styles.codeBlock, className)}\n data-kiln-component=\"\"\n data-wrap={wrap || undefined}\n data-line-numbers={showLineNumbers || undefined}\n aria-labelledby={title ? captionId : undefined}\n {...rest}\n >\n {hasHeader ? (\n <figcaption className={styles.header}>\n {title ? (\n <span id={captionId} className={styles.title}>\n {title}\n </span>\n ) : null}\n {language ? <span className={styles.language}>{language}</span> : null}\n {copyable ? (\n <Button\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n className={styles.copy}\n leadingIcon={copy === 'copied' ? <CheckIcon /> : <CopyIcon />}\n aria-label={copyLabel}\n onClick={() => {\n void onCopy()\n }}\n data-state={copy}\n >\n {copy === 'copied' ? 'Copied' : copy === 'failed' ? 'Copy failed' : 'Copy'}\n </Button>\n ) : null}\n </figcaption>\n ) : null}\n {/* A scrollable region must be reachable by keyboard. */}\n <pre className={styles.pre} tabIndex={0}>\n <code className={styles.code}>\n {lines.map((line, i) => (\n <span\n key={i}\n className={styles.line}\n data-line={showLineNumbers ? i + 1 : undefined}\n data-highlighted={highlighted.has(i + 1) || undefined}\n >\n {line}\n {i < lines.length - 1 ? '\\n' : null}\n </span>\n ))}\n </code>\n </pre>\n {copyable ? (\n <span className={styles.visuallyHidden} role=\"status\" aria-live=\"polite\">\n {copy === 'copied' ? 'Copied to clipboard' : copy === 'failed' ? 'Copy failed' : ''}\n </span>\n ) : null}\n </figure>\n )\n})\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"CodeBlock.js","names":["styles","CodeBlock_module_default","CodeBlock","forwardRef","rest","copy","setCopy","useState","timer","useRef","captionId","useId","source","code","lines","highlighted","highlightLines","hasHeader","title","language","copyable","onCopy","cx","className","wrap","showLineNumbers","Button","jsx","CheckIcon","CopyIcon","copyLabel","i","line"],"sources":["../../../../src/components/display/CodeBlock/CodeBlock.tsx"],"sourcesContent":["import {\n forwardRef,\n useEffect,\n useId,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from 'react'\nimport { CheckIcon, CopyIcon } from '#icons'\nimport { Button } from '#components/actions/Button'\nimport { cx } from '#utils/cx'\nimport styles from './CodeBlock.module.css'\n\nexport interface CodeBlockProps extends Omit<HTMLAttributes<HTMLElement>, 'title'> {\n /** The source, verbatim. Trailing newlines are trimmed. */\n code: string\n /** Shown as a label (\"TypeScript\", \"bash\"). No syntax highlighting — the code is the design. */\n language?: string\n /** Filename or caption shown in the header: `src/queries/keys.ts`. */\n title?: ReactNode\n /** Number the lines in a gutter (not copied with a selection). */\n showLineNumbers?: boolean\n /** 1-based line numbers to mark with the theme's highlight colour. */\n highlightLines?: number[]\n /** Show a copy button. Default `true`. */\n copyable?: boolean\n /** Wrap long lines instead of scrolling sideways. */\n wrap?: boolean\n /** Accessible name of the copy button. Default \"Copy code\". */\n copyLabel?: string\n}\n\ntype CopyState = 'idle' | 'copied' | 'failed'\n\n/**\n * A block of source with an optional filename bar, line numbers, highlighted lines and a copy\n * button.\n *\n * @remarks\n * `CodeBlock` shows code: a snippet in an article, a command, a config example. It's a plain\n * surface with an optional title bar, never fake window chrome, and it has no syntax highlighting:\n * the code is the design. Every code sample in these docs is a `CodeBlock`.\n *\n * @privateRemarks\n * A block of source: a snippet in a blog post, a command in a README, a config example.\n * A plain surface with an optional filename bar — no fake window chrome.\n *\n * <CodeBlock title=\"queries/keys.ts\" language=\"TypeScript\" code={source} highlightLines={[3, 4]} />\n */\nexport const CodeBlock = forwardRef<HTMLElement, CodeBlockProps>(function CodeBlock(\n {\n code,\n language,\n title,\n showLineNumbers = false,\n highlightLines,\n copyable = true,\n wrap = false,\n copyLabel = 'Copy code',\n className,\n ...rest\n },\n ref,\n) {\n const [copy, setCopy] = useState<CopyState>('idle')\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)\n const captionId = useId()\n const source = code.replace(/\\n+$/, '')\n const lines = source.split('\\n')\n const highlighted = new Set(highlightLines)\n const hasHeader = Boolean(title) || Boolean(language) || copyable\n\n useEffect(\n () => () => {\n clearTimeout(timer.current)\n },\n [],\n )\n\n const onCopy = async () => {\n clearTimeout(timer.current)\n try {\n await navigator.clipboard.writeText(source)\n setCopy('copied')\n } catch {\n setCopy('failed')\n }\n timer.current = setTimeout(() => {\n setCopy('idle')\n }, 2000)\n }\n\n return (\n <figure\n ref={ref}\n className={cx(styles.codeBlock, className)}\n data-kiln-component=\"\"\n data-wrap={wrap || undefined}\n data-line-numbers={showLineNumbers || undefined}\n aria-labelledby={title ? captionId : undefined}\n {...rest}\n >\n {hasHeader ? (\n <figcaption className={styles.header}>\n {title ? (\n <span id={captionId} className={styles.title}>\n {title}\n </span>\n ) : null}\n {language ? <span className={styles.language}>{language}</span> : null}\n {copyable ? (\n <Button\n variant=\"ghost\"\n tone=\"neutral\"\n size=\"sm\"\n className={styles.copy}\n leadingIcon={copy === 'copied' ? <CheckIcon /> : <CopyIcon />}\n aria-label={copyLabel}\n onClick={() => {\n void onCopy()\n }}\n data-state={copy}\n >\n {copy === 'copied' ? 'Copied' : copy === 'failed' ? 'Copy failed' : 'Copy'}\n </Button>\n ) : null}\n </figcaption>\n ) : null}\n {/* A scrollable region must be reachable by keyboard. */}\n <pre className={styles.pre} tabIndex={0}>\n <code className={styles.code}>\n {lines.map((line, i) => (\n <span\n key={i}\n className={styles.line}\n data-line={showLineNumbers ? i + 1 : undefined}\n data-highlighted={highlighted.has(i + 1) || undefined}\n >\n {line}\n {i < lines.length - 1 ? '\\n' : null}\n </span>\n ))}\n </code>\n </pre>\n {copyable ? (\n <span className={styles.visuallyHidden} role=\"status\" aria-live=\"polite\">\n {copy === 'copied' ? 'Copied to clipboard' : copy === 'failed' ? 'Copy failed' : ''}\n </span>\n ) : null}\n </figure>\n )\n})\n"],"mappings":";;;;;;;AAkDA,IAAaE,IAAYC,EAAwC,SAC/D,EACE,SACA,aACA,UACA,qBAAkB,IAClB,mBACA,cAAW,IACX,UAAO,IACP,eAAY,aACZ,cACA,GAAGC,KAEL,GACA;CACA,IAAM,CAACC,GAAMC,KAAWC,EAAoB,MAAM,GAC5CC,IAAQC,EAAkD,KAAA,CAAS,GACnEC,IAAYC,EAAM,GAClBC,IAASC,EAAK,QAAQ,QAAQ,EAAE,GAChCC,IAAQF,EAAO,MAAM,IAAI,GACzBG,IAAc,IAAI,IAAIC,CAAc,GACpCC,IAAY,EAAQC,KAAU,EAAQC,KAAaC;CAEzD,cACc;EACV,aAAaZ,EAAM,OAAO;CAC5B,GACA,CAAC,CACH;CAEA,IAAMa,IAAS,YAAY;EACzB,aAAab,EAAM,OAAO;EAC1B,IAAI;GAEF,AADA,MAAM,UAAU,UAAU,UAAUI,CAAM,GAC1CN,EAAQ,QAAQ;EAClB,QAAQ;GACN,EAAQ,QAAQ;EAClB;EACA,EAAM,UAAU,iBAAiB;GAC/B,EAAQ,MAAM;EAChB,GAAG,GAAI;CACT;CAEA,OACE,kBAAC,UAAD;EACO;EACL,WAAWgB,EAAGtB,EAAO,WAAWuB,CAAS;EACzC,uBAAoB;EACpB,aAAWC,KAAQ,KAAA;EACnB,qBAAmBC,KAAmB,KAAA;EACtC,mBAAiBP,IAAQR,IAAY,KAAA;EACrC,GAAIN;EAPN,UAAA;GASGa,IACC,kBAAC,cAAD;IAAY,WAAWjB,EAAO;IAA9B,UAAA;KACGkB,IACC,kBAAC,QAAD;MAAM,IAAIR;MAAW,WAAWV,EAAO;MACpC,UAAAkB;KACG,CAAA,IACJ;KACHC,IAAW,kBAAC,QAAD;MAAM,WAAWnB,EAAO;MAAW,UAAAmB;KAAe,CAAA,IAAI;KACjEC,IACC,kBAACM,GAAD;MACE,SAAQ;MACR,MAAK;MACL,MAAK;MACL,WAAW1B,EAAO;MAClB,aAAiC2B,EAApBtB,MAAS,WAAYuB,IAAgBC,GAAjB,CAAY,CAAe;MAC5D,cAAYC;MACZ,eAAe;OACb,EAAY;MACd;MACA,cAAYzB;MAEX,UAAAA,MAAS,WAAW,WAAWA,MAAS,WAAW,gBAAgB;KAC9D,CAAA,IACN;IACM;GACV,CAAA,IAAA;GAEJ,kBAAC,OAAD;IAAK,WAAWL,EAAO;IAAK,UAAU;IACpC,UAAA,kBAAC,QAAD;KAAM,WAAWA,EAAO;KACrB,UAAAc,EAAM,KAAK,GAAM,MAChB,kBAAC,QAAD;MAEE,WAAWd,EAAO;MAClB,aAAWyB,IAAkBM,IAAI,IAAI,KAAA;MACrC,oBAAkBhB,EAAY,IAAIgB,IAAI,CAAC,KAAK,KAAA;MAJ9C,UAAA,CAMGC,GACAD,IAAIjB,EAAM,SAAS,IAAI,OAAO,IAC3B;KAPC,GAAAiB,CAOD,CACP;IACG,CAAA;GACH,CAAA;GACJX,IACC,kBAAC,QAAD;IAAM,WAAWpB,EAAO;IAAgB,MAAK;IAAS,aAAU;IAC7D,UAAAK,MAAS,WAAW,wBAAwBA,MAAS,WAAW,gBAAgB;GAC7E,CAAA,IACJ;EACE;;AAEZ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DataList.js","names":["styles","DataList_module_default","DataListRoot","forwardRef","rest","cx","className","orientation","divided","DataListItem","label","DataList"],"sources":["../../../../src/components/display/DataList/DataList.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport styles from './DataList.module.css'\n\nexport type DataListOrientation = 'horizontal' | 'vertical'\n\nexport interface DataListProps extends HTMLAttributes<HTMLDListElement> {\n /**\n * `horizontal` (default): labels in a hang column, values aligned beside them.\n * `vertical`: each label sits above its value — for narrow columns and inline meta strips.\n */\n orientation?: DataListOrientation\n /** Hairline rules between items. */\n divided?: boolean\n}\n\n/**\n * Label/value pairs as a real `<dl>`: a project's facts, a release's region and start time,\n * an account's details.\n *\n * <DataList><DataList.Item label=\"Region\">Sydney</DataList.Item></DataList>\n */\nconst DataListRoot = forwardRef<HTMLDListElement, DataListProps>(function DataList(\n { orientation = 'horizontal', divided = false, className, ...rest },\n ref,\n) {\n return (\n <dl\n ref={ref}\n className={cx(styles.list, className)}\n data-orientation={orientation}\n data-divided={divided || undefined}\n {...rest}\n />\n )\n})\n\nexport interface DataListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n label: ReactNode\n /** The value. Numbers are tabular. */\n children?: ReactNode\n}\n\nconst DataListItem = forwardRef<HTMLDivElement, DataListItemProps>(function DataListItem(\n { label, children, className, ...rest },\n ref,\n) {\n return (\n <div ref={ref} className={cx(styles.item, className)} {...rest}>\n <dt className={styles.label}>{label}</dt>\n <dd className={styles.value}>{children}</dd>\n </div>\n )\n})\n\nexport const DataList = Object.assign(DataListRoot, { Item: DataListItem })\n"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"DataList.js","names":["styles","DataList_module_default","DataListRoot","forwardRef","rest","cx","className","orientation","divided","DataListItem","label","DataList"],"sources":["../../../../src/components/display/DataList/DataList.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport styles from './DataList.module.css'\n\nexport type DataListOrientation = 'horizontal' | 'vertical'\n\nexport interface DataListProps extends HTMLAttributes<HTMLDListElement> {\n /**\n * `horizontal` (default): labels in a hang column, values aligned beside them.\n * `vertical`: each label sits above its value — for narrow columns and inline meta strips.\n */\n orientation?: DataListOrientation\n /** Hairline rules between items. */\n divided?: boolean\n}\n\n/**\n * Label and value pairs as a real description list. A ticket's details, an account's facts.\n *\n * @remarks\n * `DataList` renders label and value pairs as a `<dl>`, so assistive technology reads each label\n * with its value. Labels sit in a column beside their values (`orientation=\"horizontal\"`, the\n * default), or above them.\n *\n * @privateRemarks\n * Label/value pairs as a real `<dl>`: a project's facts, a release's region and start time,\n * an account's details.\n *\n * <DataList><DataList.Item label=\"Region\">Sydney</DataList.Item></DataList>\n */\nconst DataListRoot = forwardRef<HTMLDListElement, DataListProps>(function DataList(\n { orientation = 'horizontal', divided = false, className, ...rest },\n ref,\n) {\n return (\n <dl\n ref={ref}\n className={cx(styles.list, className)}\n data-orientation={orientation}\n data-divided={divided || undefined}\n {...rest}\n />\n )\n})\n\nexport interface DataListItemProps extends Omit<HTMLAttributes<HTMLDivElement>, 'children'> {\n label: ReactNode\n /** The value. Numbers are tabular. */\n children?: ReactNode\n}\n\nconst DataListItem = forwardRef<HTMLDivElement, DataListItemProps>(function DataListItem(\n { label, children, className, ...rest },\n ref,\n) {\n return (\n <div ref={ref} className={cx(styles.item, className)} {...rest}>\n <dt className={styles.label}>{label}</dt>\n <dd className={styles.value}>{children}</dd>\n </div>\n )\n})\n\nexport const DataList = Object.assign(DataListRoot, { Item: DataListItem })\n"],"mappings":";;;;;AA8BA,IAAME,IAAeC,EAA4C,SAC/D,EAAE,iBAAc,cAAc,aAAU,IAAO,cAAW,GAAGC,KAC7D,GACA;CACA,OACE,kBAAC,MAAD;EACO;EACL,WAAWC,EAAGL,EAAO,MAAMM,CAAS;EACpC,oBAAkBC;EAClB,gBAAcC,KAAW,KAAA;EACzB,GAAIJ;CACL,CAAA;AAEL,CAAC,GAQKK,IAAeN,EAA8C,SACjE,EAAE,UAAO,aAAU,cAAW,GAAGC,KACjC,GACA;CACA,OACE,kBAAC,OAAD;EAAU;EAAK,WAAWC,EAAGL,EAAO,MAAMM,CAAS;EAAG,GAAIF;EAA1D,UAAA,CACE,kBAAC,MAAD;GAAI,WAAWJ,EAAO;GAAQ,UAAAU;EAAU,CAAA,GACxC,kBAAC,MAAD;GAAI,WAAWV,EAAO;GAAQ;EAAa,CAAA,CACxC;;AAET,CAAC,GAEYW,IAAW,OAAO,OAAOT,GAAc,EAAE,MAAMO,EAAa,CAAC"}
|
|
@@ -14,6 +14,15 @@ export interface DeltaProps extends HTMLAttributes<HTMLSpanElement> {
|
|
|
14
14
|
children?: ReactNode;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
+
* A change marker, coloured by whether the change is good, and spoken in words so it never depends
|
|
18
|
+
* on the glyph.
|
|
19
|
+
*
|
|
20
|
+
* @remarks
|
|
21
|
+
* `Delta` shows a change: a small triangle (or a dot for no change) and the amount. Screen readers
|
|
22
|
+
* hear the direction in words ("Up 12%"), so it never relies on colour or the glyph alone. `Stat`
|
|
23
|
+
* uses it for its change.
|
|
24
|
+
*
|
|
25
|
+
* @privateRemarks
|
|
17
26
|
* A change marker: a small triangle (or a dot for no change) and the amount, coloured by
|
|
18
27
|
* whether the change is good. The direction is spoken in words, so it never relies on
|
|
19
28
|
* colour or the glyph: screen readers hear "Up 1".
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Delta.js","names":["styles","Delta_module_default","DIRECTION_LABEL","DEFAULT_TONE","Delta","forwardRef","rest","cx","className","direction","tone","VisuallyHidden","children"],"sources":["../../../../src/components/display/Delta/Delta.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { VisuallyHidden } from '#components/layout/VisuallyHidden'\nimport styles from './Delta.module.css'\n\nexport type DeltaDirection = 'up' | 'down' | 'flat'\nexport type DeltaTone = 'positive' | 'critical' | 'neutral'\n\nexport interface DeltaProps extends HTMLAttributes<HTMLSpanElement> {\n /** Which way the figure moved. Drives the glyph and the spoken word (\"Up\", \"Down\"). */\n direction: DeltaDirection\n /**\n * Whether the change is good. Defaults from direction (up → positive, down → critical,\n * flat → neutral); override it when down is good — costs that fell are\n * `direction=\"down\" tone=\"positive\"`.\n */\n tone?: DeltaTone\n /** The change, already formatted: \"1\", \"3 places\", \"$212.40\". Omit for a bare glyph. */\n children?: ReactNode\n}\n\nconst DIRECTION_LABEL: Record<DeltaDirection, string> = {\n up: 'Up',\n down: 'Down',\n flat: 'No change',\n}\nconst DEFAULT_TONE: Record<DeltaDirection, DeltaTone> = {\n up: 'positive',\n down: 'critical',\n flat: 'neutral',\n}\n\n/**\n * A change marker: a small triangle (or a dot for no change) and the amount, coloured by\n * whether the change is good. The direction is spoken in words, so it never relies on\n * colour or the glyph: screen readers hear \"Up 1\".\n *\n * <Delta direction=\"up\">1</Delta> · <Delta direction=\"down\" tone=\"positive\">$84.20</Delta>\n */\nexport const Delta = forwardRef<HTMLSpanElement, DeltaProps>(function Delta(\n { direction, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cx(styles.delta, className)}\n data-direction={direction}\n data-tone={tone ?? DEFAULT_TONE[direction]}\n {...rest}\n >\n <svg className={styles.glyph} viewBox=\"0 0 10 10\" aria-hidden=\"true\" focusable=\"false\">\n {direction === 'flat' ? <circle cx=\"5\" cy=\"5\" r=\"2.5\" /> : <path d=\"M5 1.5 9 8.5H1z\" />}\n </svg>\n <VisuallyHidden>\n {DIRECTION_LABEL[direction]}\n {children != null ? ' ' : ''}\n </VisuallyHidden>\n {children}\n </span>\n )\n})\n"],"mappings":";;;;;;AAqBA,IAAME,IAAkD;CACtD,IAAI;CACJ,MAAM;CACN,MAAM;AACR,GACMC,IAAkD;CACtD,IAAI;CACJ,MAAM;CACN,MAAM;AACR,
|
|
1
|
+
{"version":3,"file":"Delta.js","names":["styles","Delta_module_default","DIRECTION_LABEL","DEFAULT_TONE","Delta","forwardRef","rest","cx","className","direction","tone","VisuallyHidden","children"],"sources":["../../../../src/components/display/Delta/Delta.tsx"],"sourcesContent":["import { forwardRef, type HTMLAttributes, type ReactNode } from 'react'\nimport { cx } from '#utils/cx'\nimport { VisuallyHidden } from '#components/layout/VisuallyHidden'\nimport styles from './Delta.module.css'\n\nexport type DeltaDirection = 'up' | 'down' | 'flat'\nexport type DeltaTone = 'positive' | 'critical' | 'neutral'\n\nexport interface DeltaProps extends HTMLAttributes<HTMLSpanElement> {\n /** Which way the figure moved. Drives the glyph and the spoken word (\"Up\", \"Down\"). */\n direction: DeltaDirection\n /**\n * Whether the change is good. Defaults from direction (up → positive, down → critical,\n * flat → neutral); override it when down is good — costs that fell are\n * `direction=\"down\" tone=\"positive\"`.\n */\n tone?: DeltaTone\n /** The change, already formatted: \"1\", \"3 places\", \"$212.40\". Omit for a bare glyph. */\n children?: ReactNode\n}\n\nconst DIRECTION_LABEL: Record<DeltaDirection, string> = {\n up: 'Up',\n down: 'Down',\n flat: 'No change',\n}\nconst DEFAULT_TONE: Record<DeltaDirection, DeltaTone> = {\n up: 'positive',\n down: 'critical',\n flat: 'neutral',\n}\n\n/**\n * A change marker, coloured by whether the change is good, and spoken in words so it never depends\n * on the glyph.\n *\n * @remarks\n * `Delta` shows a change: a small triangle (or a dot for no change) and the amount. Screen readers\n * hear the direction in words (\"Up 12%\"), so it never relies on colour or the glyph alone. `Stat`\n * uses it for its change.\n *\n * @privateRemarks\n * A change marker: a small triangle (or a dot for no change) and the amount, coloured by\n * whether the change is good. The direction is spoken in words, so it never relies on\n * colour or the glyph: screen readers hear \"Up 1\".\n *\n * <Delta direction=\"up\">1</Delta> · <Delta direction=\"down\" tone=\"positive\">$84.20</Delta>\n */\nexport const Delta = forwardRef<HTMLSpanElement, DeltaProps>(function Delta(\n { direction, tone, className, children, ...rest },\n ref,\n) {\n return (\n <span\n ref={ref}\n className={cx(styles.delta, className)}\n data-direction={direction}\n data-tone={tone ?? DEFAULT_TONE[direction]}\n {...rest}\n >\n <svg className={styles.glyph} viewBox=\"0 0 10 10\" aria-hidden=\"true\" focusable=\"false\">\n {direction === 'flat' ? <circle cx=\"5\" cy=\"5\" r=\"2.5\" /> : <path d=\"M5 1.5 9 8.5H1z\" />}\n </svg>\n <VisuallyHidden>\n {DIRECTION_LABEL[direction]}\n {children != null ? ' ' : ''}\n </VisuallyHidden>\n {children}\n </span>\n )\n})\n"],"mappings":";;;;;;AAqBA,IAAME,IAAkD;CACtD,IAAI;CACJ,MAAM;CACN,MAAM;AACR,GACMC,IAAkD;CACtD,IAAI;CACJ,MAAM;CACN,MAAM;AACR,GAkBaC,IAAQC,EAAwC,SAC3D,EAAE,cAAW,SAAM,cAAW,aAAU,GAAGC,KAC3C,GACA;CACA,OACE,kBAAC,QAAD;EACO;EACL,WAAWC,EAAGP,EAAO,OAAOQ,CAAS;EACrC,kBAAgBC;EAChB,aAAWC,KAAQP,EAAaM;EAChC,GAAIH;EALN,UAAA;GAOE,kBAAC,OAAD;IAAK,WAAWN,EAAO;IAAO,SAAQ;IAAY,eAAY;IAAO,WAAU;IAC5E,UAAAS,MAAc,SAAS,kBAAC,UAAD;KAAQ,IAAG;KAAI,IAAG;KAAI,GAAE;IAAO,CAAA,IAAI,kBAAC,QAAD,EAAM,GAAE,kBAAmB,CAAA;GACnF,CAAA;GACL,kBAACE,GAAD,EAAA,UAAA,CACGT,EAAgBO,IAChBG,KAAY,OAAa,KAAN,GACN,EAAA,CAAA;GACfA;EACG;;AAEV,CAAC"}
|