@seekalfred/primitives 0.5.0 → 0.6.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 +664 -9
- package/dist/accordion/accordion.d.ts +5 -5
- package/dist/accordion/accordion.js +22 -11
- package/dist/accordion/accordion.js.map +1 -1
- package/dist/action-button/action-button.d.ts +50 -0
- package/dist/action-button/action-button.js +159 -0
- package/dist/action-button/action-button.js.map +1 -0
- package/dist/action-button/index.d.ts +2 -0
- package/dist/action-button/index.js +2 -0
- package/dist/alert/alert.d.ts +16 -2
- package/dist/alert/alert.js +118 -20
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert-dialog/alert-dialog.d.ts +1 -1
- package/dist/alert-dialog/alert-dialog.js +4 -2
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -1
- package/dist/avatar/avatar.js +51 -18
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +10 -3
- package/dist/badge/badge.js +37 -5
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +3 -30
- package/dist/breadcrumb/breadcrumb.js +25 -8
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +36 -23
- package/dist/button/button.js +178 -33
- package/dist/button/button.js.map +1 -1
- package/dist/button-group/button-group.d.ts +76 -0
- package/dist/button-group/button-group.js +403 -0
- package/dist/button-group/button-group.js.map +1 -0
- package/dist/button-group/index.d.ts +2 -0
- package/dist/button-group/index.js +2 -0
- package/dist/calendar/calendar.d.ts +3 -3
- package/dist/calendar/calendar.js +263 -34
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +14 -7
- package/dist/card/card.js +32 -14
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +2 -9
- package/dist/checkbox/checkbox.js +28 -29
- package/dist/checkbox/checkbox.js.map +1 -1
- package/dist/confirm-morph/confirm-morph.d.ts +85 -0
- package/dist/confirm-morph/confirm-morph.js +652 -0
- package/dist/confirm-morph/confirm-morph.js.map +1 -0
- package/dist/confirm-morph/index.d.ts +2 -0
- package/dist/confirm-morph/index.js +2 -0
- package/dist/copy-button/copy-button.d.ts +32 -0
- package/dist/copy-button/copy-button.js +134 -0
- package/dist/copy-button/copy-button.js.map +1 -0
- package/dist/copy-button/index.d.ts +2 -0
- package/dist/copy-button/index.js +2 -0
- package/dist/date-picker/date-picker.d.ts +51 -0
- package/dist/date-picker/date-picker.js +241 -0
- package/dist/date-picker/date-picker.js.map +1 -0
- package/dist/date-picker/date-range-picker.d.ts +78 -0
- package/dist/date-picker/date-range-picker.js +1015 -0
- package/dist/date-picker/date-range-picker.js.map +1 -0
- package/dist/date-picker/index.d.ts +4 -0
- package/dist/date-picker/index.js +4 -0
- package/dist/date-picker/picker-parts.d.ts +46 -0
- package/dist/date-picker/picker-parts.js +135 -0
- package/dist/date-picker/picker-parts.js.map +1 -0
- package/dist/dialog/dialog.d.ts +12 -16
- package/dist/dialog/dialog.js +45 -14
- package/dist/dialog/dialog.js.map +1 -1
- package/dist/empty-state/empty-state.d.ts +44 -0
- package/dist/empty-state/empty-state.js +83 -0
- package/dist/empty-state/empty-state.js.map +1 -0
- package/dist/empty-state/index.d.ts +2 -0
- package/dist/empty-state/index.js +2 -0
- package/dist/expanding-button-group/expanding-button-group.d.ts +54 -0
- package/dist/expanding-button-group/expanding-button-group.js +350 -0
- package/dist/expanding-button-group/expanding-button-group.js.map +1 -0
- package/dist/expanding-button-group/index.d.ts +2 -0
- package/dist/expanding-button-group/index.js +2 -0
- package/dist/file-drop-zone/file-drop-zone.d.ts +71 -0
- package/dist/file-drop-zone/file-drop-zone.js +116 -0
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -0
- package/dist/file-drop-zone/index.d.ts +2 -0
- package/dist/file-drop-zone/index.js +2 -0
- package/dist/filter-bar/filter-bar.d.ts +121 -0
- package/dist/filter-bar/filter-bar.js +300 -0
- package/dist/filter-bar/filter-bar.js.map +1 -0
- package/dist/filter-bar/index.d.ts +2 -0
- package/dist/filter-bar/index.js +2 -0
- package/dist/floating-button-group/floating-button-group.d.ts +61 -0
- package/dist/floating-button-group/floating-button-group.js +340 -0
- package/dist/floating-button-group/floating-button-group.js.map +1 -0
- package/dist/floating-button-group/index.d.ts +2 -0
- package/dist/floating-button-group/index.js +2 -0
- package/dist/icon-morph/icon-morph.d.ts +55 -0
- package/dist/icon-morph/icon-morph.js +331 -0
- package/dist/icon-morph/icon-morph.js.map +1 -0
- package/dist/icon-morph/index.d.ts +2 -0
- package/dist/icon-morph/index.js +2 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +29 -7
- package/dist/input/input.d.ts +23 -1
- package/dist/input/input.js +37 -13
- package/dist/input/input.js.map +1 -1
- package/dist/label/label.d.ts +1 -1
- package/dist/label/label.js +4 -2
- package/dist/label/label.js.map +1 -1
- package/dist/lib/count-to.js +113 -0
- package/dist/lib/count-to.js.map +1 -0
- package/dist/lib/eye-morph.js +62 -0
- package/dist/lib/eye-morph.js.map +1 -0
- package/dist/lib/field-message.js +336 -0
- package/dist/lib/field-message.js.map +1 -0
- package/dist/lib/field-shell.js +81 -0
- package/dist/lib/field-shell.js.map +1 -0
- package/dist/lib/field-surface.js +4 -2
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/flip.js +80 -0
- package/dist/lib/flip.js.map +1 -0
- package/dist/lib/height-follow.js +59 -0
- package/dist/lib/height-follow.js.map +1 -0
- package/dist/lib/icon-swap.js +47 -0
- package/dist/lib/icon-swap.js.map +1 -0
- package/dist/lib/leaving-list.js +57 -0
- package/dist/lib/leaving-list.js.map +1 -0
- package/dist/lib/merge-refs.js +22 -0
- package/dist/lib/merge-refs.js.map +1 -0
- package/dist/lib/morph-text.js +128 -0
- package/dist/lib/morph-text.js.map +1 -0
- package/dist/lib/motion.js +162 -0
- package/dist/lib/motion.js.map +1 -0
- package/dist/lib/pausable-timeout.js +67 -0
- package/dist/lib/pausable-timeout.js.map +1 -0
- package/dist/lib/rolling-value.js +31 -0
- package/dist/lib/rolling-value.js.map +1 -0
- package/dist/lib/spring-progress.js +114 -0
- package/dist/lib/spring-progress.js.map +1 -0
- package/dist/lib/svg-id.js +10 -0
- package/dist/lib/svg-id.js.map +1 -0
- package/dist/lib/swap-text.js +77 -0
- package/dist/lib/swap-text.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +36 -0
- package/dist/lib/use-copy-feedback.js.map +1 -0
- package/dist/menu/menu.d.ts +5 -5
- package/dist/menu/menu.js +10 -6
- package/dist/menu/menu.js.map +1 -1
- package/dist/page-header/index.d.ts +2 -0
- package/dist/page-header/index.js +2 -0
- package/dist/page-header/page-header.d.ts +75 -0
- package/dist/page-header/page-header.js +83 -0
- package/dist/page-header/page-header.js.map +1 -0
- package/dist/password-field/index.d.ts +2 -0
- package/dist/password-field/index.js +2 -0
- package/dist/password-field/password-field.d.ts +37 -0
- package/dist/password-field/password-field.js +101 -0
- package/dist/password-field/password-field.js.map +1 -0
- package/dist/password-strength/index.d.ts +2 -0
- package/dist/password-strength/index.js +2 -0
- package/dist/password-strength/password-strength.d.ts +127 -0
- package/dist/password-strength/password-strength.js +455 -0
- package/dist/password-strength/password-strength.js.map +1 -0
- package/dist/popover/popover.d.ts +10 -2
- package/dist/popover/popover.js +6 -4
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.d.ts +2 -2
- package/dist/progress/progress.js +50 -7
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.d.ts +3 -3
- package/dist/radio-group/radio-group.js +14 -10
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +17 -6
- package/dist/scroll-area/scroll-area.js +30 -6
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/search-field/index.d.ts +2 -0
- package/dist/search-field/index.js +2 -0
- package/dist/search-field/search-field.d.ts +26 -0
- package/dist/search-field/search-field.js +155 -0
- package/dist/search-field/search-field.js.map +1 -0
- package/dist/select/select.d.ts +3 -3
- package/dist/select/select.js +85 -24
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +1 -1
- package/dist/separator/separator.js +4 -2
- package/dist/separator/separator.js.map +1 -1
- package/dist/sheet/sheet.d.ts +1 -1
- package/dist/sheet/sheet.js +6 -3
- package/dist/sheet/sheet.js.map +1 -1
- package/dist/side-nav/index.d.ts +2 -0
- package/dist/side-nav/index.js +2 -0
- package/dist/side-nav/side-nav.d.ts +72 -0
- package/dist/side-nav/side-nav.js +142 -0
- package/dist/side-nav/side-nav.js.map +1 -0
- package/dist/skeleton/skeleton.d.ts +8 -2
- package/dist/skeleton/skeleton.js +9 -3
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.d.ts +2 -2
- package/dist/slider/slider.js +64 -8
- package/dist/slider/slider.js.map +1 -1
- package/dist/spinner/spinner.d.ts +1 -1
- package/dist/spinner/spinner.js +4 -2
- package/dist/spinner/spinner.js.map +1 -1
- package/dist/split-button/index.d.ts +2 -0
- package/dist/split-button/index.js +2 -0
- package/dist/split-button/split-button.d.ts +46 -0
- package/dist/split-button/split-button.js +122 -0
- package/dist/split-button/split-button.js.map +1 -0
- package/dist/stat-card/index.d.ts +2 -0
- package/dist/stat-card/index.js +2 -0
- package/dist/stat-card/stat-card.d.ts +81 -0
- package/dist/stat-card/stat-card.js +152 -0
- package/dist/stat-card/stat-card.js.map +1 -0
- package/dist/styles.css +1 -1
- package/dist/switch/switch.d.ts +2 -2
- package/dist/switch/switch.js +11 -8
- package/dist/switch/switch.js.map +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +38 -7
- package/dist/table/table.js +114 -12
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.d.ts +4 -4
- package/dist/tabs/tabs.js +153 -12
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +24 -1
- package/dist/textarea/textarea.js +27 -3
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/theme-switch/index.d.ts +2 -0
- package/dist/theme-switch/index.js +2 -0
- package/dist/theme-switch/theme-switch.d.ts +37 -0
- package/dist/theme-switch/theme-switch.js +123 -0
- package/dist/theme-switch/theme-switch.js.map +1 -0
- package/dist/time-picker/index.d.ts +2 -0
- package/dist/time-picker/index.js +2 -0
- package/dist/time-picker/time-picker.d.ts +54 -0
- package/dist/time-picker/time-picker.js +201 -0
- package/dist/time-picker/time-picker.js.map +1 -0
- package/dist/toast/toast.d.ts +2 -2
- package/dist/toast/toast.js +61 -39
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +3 -3
- package/dist/tooltip/tooltip.js +66 -15
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +83 -3
package/dist/alert/alert.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert.js","names":[],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef, ReactNode } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-lg border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-background text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-background text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n}\n\nexport function Alert({\n className,\n variant,\n icon,\n title,\n headingLevel = 3,\n children,\n ...props\n}: AlertProps) {\n const Title = `h${headingLevel}` as const;\n\n return (\n <div\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n * The glyph is sized from the icon scale unless the caller sized it,\n * and never becomes a press target. shrink-0 keeps it square when the\n * message wraps.\n */}\n {icon === undefined ? null : (\n <span\n data-slot=\"alert-icon\"\n className=\"shrink-0 [&>svg]:pointer-events-none [&>svg:not([class*='size-'])]:size-(--icon-sm)\"\n >\n {icon}\n </span>\n )}\n <div data-slot=\"alert-body\" className=\"min-w-0 flex-1\">\n {title === undefined ? null : (\n // leading-* AFTER text-*, always: tailwind-merge treats a later\n // text-* as owning line-height and silently strips a leading-*\n // that precedes it.\n <Title\n data-slot=\"alert-title\"\n className=\"mb-1 text-sm leading-(--leading-tight) font-medium\"\n >\n {title}\n </Title>\n )}\n {children === undefined ? null : <div data-slot=\"alert-description\">{children}</div>}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAMT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;AA8CA,SAAgB,MAAM,EACpB,WACA,SACA,MACA,OACA,eAAe,GACf,UACA,GAAG,SACU;CACb,MAAM,QAAQ,IAAI;CAElB,OACE,qBAAC,OAAD;EACE,GAAI;EACJ,MAAK;EACL,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EAJrD,UAAA,CAWG,SAAS,KAAA,IAAY,OACpB,oBAAC,QAAD;GACE,aAAU;GACV,WAAU;GAET,UAAA;EACG,CAAA,GAER,qBAAC,OAAD;GAAK,aAAU;GAAa,WAAU;GAAtC,UAAA,CACG,UAAU,KAAA,IAAY,OAIrB,oBAAC,OAAD;IACE,aAAU;IACV,WAAU;IAET,UAAA;GACI,CAAA,GAER,aAAa,KAAA,IAAY,OAAO,oBAAC,OAAD;IAAK,aAAU;IAAqB;GAAc,CAAA,CAChF;EACF,CAAA,CAAA;;AAET"}
|
|
1
|
+
{"version":3,"file":"alert.js","names":["AriaButton"],"sources":["../../src/alert/alert.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useHeightFollow } from \"../lib/height-follow.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Alert, ported from the legacy shadcn component.\n *\n * There is no React Aria base: RAC ships no static inline alert (its Toast\n * API is transient and queue-driven), so this is a styled div with\n * role=\"alert\". Because it is not RAC-backed, its props follow\n * ARCHITECTURE section 7 rule 1 -- VariantProps & ComponentPropsWithoutRef --\n * rather than the Button's Omit-from-React-Aria departure, which exists only\n * to stop RAC-ignored props being advertised.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n *\n * THE LAYOUT IS THE BIGGEST CHANGE. The legacy component positioned its icon\n * with `[&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4`, pushed the text over\n * with `[&>svg~*]:pl-7`, and nudged it back with `[&>svg+div]:translate-y-[-3px]`.\n * All three are physical properties or magic numbers: `left` and `pl` do not\n * mirror in RTL, so the icon stayed left while the text indented the wrong\n * side, and `translate-y-[-3px]` is a bracket value that `check:hardcoding`\n * rejects outright. Flex with an explicit `icon` prop deletes all three\n * problems rather than porting them.\n */\nexport const alertVariants = cva(\n [\n // box-border is not boilerplate. Tailwind Preflight sets box-sizing\n // globally and the legacy app had it; this library's reset is OPT-IN, so\n // a consumer may load none and the browser default content-box applies.\n // w-full is width:100%, so the padding and border are then ADDED to it\n // and the alert overflows its container by exactly 46px at comfortable\n // density. Measured, not theorised: 346px inside a 300px parent.\n \"box-border flex w-full items-start gap-(--density-control-gap)\",\n \"rounded-lg border-(length:--border-width) p-(--density-control-pad-md)\",\n // font-sans is load-bearing. The library does not style the consumer's\n // page, so anything that does not set its own face inherits the host's\n // default -- which is a serif in an unstyled document. The accordion\n // panel shipped exactly that way and it was caught by eye, not by a test.\n \"font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n variant: {\n default: \"bg-background text-foreground border-border\",\n // destructive-STRONG, not destructive. The fill token is 4.04:1 on\n // the dark page; this role exists so the text and border clear the\n // 4.5 floor in both themes. The legacy `border-destructive/50` was\n // also an opacity modifier, which cannot be contrast-checked at all\n // because the result depends on whatever sits behind it.\n destructive: \"bg-background text-destructive-strong border-destructive-strong\",\n },\n },\n defaultVariants: { variant: \"default\" },\n },\n);\n\n/**\n * An inline status message.\n *\n * Announced by assistive technology the moment it renders, via `role=\"alert\"`.\n * That makes it the wrong component for content that is present on load and\n * merely styled as a notice -- use a plain region for those.\n *\n * @example\n * <Alert variant=\"destructive\" icon={<WarningIcon aria-hidden />} title=\"Save failed\">\n * Your changes were not saved.\n * </Alert>\n */\nexport interface AlertProps\n // The native `title` attribute is omitted so this component can use the\n // name for its heading. That attribute renders a browser tooltip, which is\n // not keyboard reachable and is not announced reliably -- on a role=\"alert\"\n // it is worth nothing, and silently shadowing it with a ReactNode would be\n // a type error waiting for the first consumer who tried both.\n extends Omit<ComponentPropsWithoutRef<\"div\">, \"title\">, VariantProps<typeof alertVariants> {\n /**\n * Leading glyph. Mark it `aria-hidden` -- the message text is the\n * announcement, and an unhidden graphic makes the alert announce twice.\n *\n * A prop rather than a positioned child: the legacy `[&>svg]` selectors\n * only worked when the icon was the first element, and broke in RTL.\n */\n icon?: ReactNode;\n /**\n * Optional heading. Replaces the legacy `AlertTitle` part.\n *\n * Omit it for a single-line alert; most call sites do.\n */\n title?: ReactNode;\n /**\n * Heading level for {@link AlertProps.title}.\n *\n * The legacy `AlertTitle` was always an `<h5>`, which breaks the document\n * outline anywhere the alert is not nested five deep.\n *\n * @default 3\n */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n /**\n * Whether the alert is shown. Controlled: hiding it collapses its height and\n * fades it out, and showing it again grows it back. An alert that is open on\n * first render does not animate. Leave it out for an alert that is always there.\n */\n open?: boolean;\n /**\n * Adds a Dismiss button. Pressed, a controlled alert (`open` given) only calls\n * this and waits to be told `open={false}`; an uncontrolled alert (no `open`)\n * collapses first and then calls it.\n */\n onDismiss?: () => void;\n /** The Dismiss button's accessible name. @default \"Dismiss\" */\n dismissLabel?: string;\n}\n\n/** A string title or description swaps as a whole when its text changes. */\nfunction Swappable({ value, children }: { value: ReactNode; children: ReactNode }) {\n if (typeof value !== \"string\" && typeof value !== \"number\") return <>{children}</>;\n return (\n <SwapText swapKey={String(value)} className=\"block\">\n {children}\n </SwapText>\n );\n}\n\n/** The name an icon swaps under: a different icon component crossfades; the same one re-rendering stays still. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nfunction DismissGlyph() {\n return (\n <svg\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M4 4l8 8M12 4l-8 8\" />\n </svg>\n );\n}\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n {\n className,\n variant,\n icon,\n title,\n headingLevel = 3,\n children,\n open,\n onDismiss,\n dismissLabel = \"Dismiss\",\n ...props\n },\n ref,\n) {\n const Title = `h${headingLevel}` as const;\n const frame = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : \"\";\n useHeightFollow(\n frame,\n content,\n `${typeof title === \"string\" ? title : \"\"}\\n${children === undefined ? \"\" : \"details\"}\\n${text}`,\n );\n\n // Presence. Without `open` or `onDismiss` the alert is simply there.\n const managed = open !== undefined || onDismiss !== undefined;\n const [dismissed, setDismissed] = useState(false);\n const wanted = open ?? !dismissed;\n const [rendered, setRendered] = useState(wanted);\n const [shown, setShown] = useState(wanted);\n if (wanted && !rendered) setRendered(true);\n if (!wanted && shown) setShown(false);\n useEffect(() => {\n // A newly shown alert mounts collapsed and opens a frame later, so it grows rather than appears.\n if (wanted && rendered && !shown) {\n const frameId = requestAnimationFrame(() => setShown(true));\n return () => cancelAnimationFrame(frameId);\n }\n }, [wanted, rendered, shown]);\n const leaving = rendered && !wanted;\n const finish = useCallback(() => {\n setRendered(false);\n if (open === undefined) onDismiss?.();\n }, [onDismiss, open]);\n useExitBackstop(leaving, finish, 900);\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) finish();\n }, [leaving, finish]);\n if (managed && !rendered) return null;\n\n const alert = (\n <div\n ref={ref}\n {...props}\n role=\"alert\"\n data-slot=\"alert\"\n className={cn(alertVariants({ variant }), className)}\n >\n {/*\n The icon is decorative or labelled by the consumer; this wrapper adds\n no name. It keeps the icon from shrinking and sizes a bare svg to the\n icon scale, unless the consumer sized it themselves.\n */}\n {icon === undefined ? null : (\n <span\n data-slot=\"alert-icon\"\n className=\"grid shrink-0 [&>svg]:pointer-events-none [&>svg:not([class*='size-'])]:size-(--icon-sm)\"\n >\n <SwapText swapKey={iconKey(icon)} motion=\"glyph\" className=\"grid place-items-center\">\n {icon}\n </SwapText>\n </span>\n )}\n {/* The copy column follows its content's height, so longer or shorter text never snaps the alert. */}\n <div ref={frame} data-slot=\"alert-body\" className=\"height-follow min-w-0 flex-1\">\n <div ref={content}>\n {title === undefined ? null : (\n <Title\n data-slot=\"alert-title\"\n className=\"mb-1 text-sm leading-(--leading-tight) font-medium\"\n >\n <Swappable value={title}>{title}</Swappable>\n </Title>\n )}\n {children === undefined ? null : (\n <div data-slot=\"alert-description\">\n <Swappable value={children}>{children}</Swappable>\n </div>\n )}\n </div>\n </div>\n {onDismiss ? (\n <AriaButton\n data-slot=\"alert-dismiss\"\n aria-label={typeof title === \"string\" ? `${dismissLabel}: ${title}` : dismissLabel}\n onPress={() => (open === undefined ? setDismissed(true) : onDismiss())}\n className=\"calendar-nav-motion -my-1 -me-1.5 grid size-7 shrink-0 cursor-pointer place-items-center rounded-full border-0 bg-transparent text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring)\"\n >\n <DismissGlyph />\n </AriaButton>\n ) : null}\n </div>\n );\n if (!managed) return alert;\n return (\n <div\n data-slot=\"alert-presence\"\n data-shown={shown || undefined}\n onTransitionEnd={(event) => {\n if (leaving && event.target === event.currentTarget && event.propertyName === \"grid-template-rows\")\n finish();\n }}\n className=\"alert-presence\"\n >\n <div>{alert}</div>\n </div>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,gBAAgB,IAC3B;CAOE;CACA;CAKA;AACF,GACA;CACE,UAAU,EACR,SAAS;EACP,SAAS;EAMT,aAAa;CACf,EACF;CACA,iBAAiB,EAAE,SAAS,UAAU;AACxC,CACF;;AA6DA,SAAS,UAAU,EAAE,OAAO,YAAuD;CACjF,IAAI,OAAO,UAAU,YAAY,OAAO,UAAU,UAAU,OAAO,oBAAA,YAAA,EAAG,SAAW,CAAA;CACjF,OACE,oBAAC,UAAD;EAAU,SAAS,OAAO,KAAK;EAAG,WAAU;EACzC;CACO,CAAA;AAEd;;AAGA,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,SAAS,eAAe;CACtB,OACE,oBAAC,OAAD;EACE,SAAQ;EACR,MAAK;EACL,QAAO;EACP,aAAY;EACZ,eAAc;EACd,eAAY;EACZ,WAAU;EAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,qBAAsB,CAAA;CAC3B,CAAA;AAET;AAEA,MAAa,QAAQ,WAAuC,SAAS,MACnE,EACE,WACA,SACA,MACA,OACA,eAAe,GACf,UACA,MACA,WACA,eAAe,WACf,GAAG,SAEL,KACA;CACA,MAAM,QAAQ,IAAI;CAClB,MAAM,QAAQ,OAAuB,IAAI;CACzC,MAAM,UAAU,OAAuB,IAAI;CAE3C,gBACE,OACA,SACA,GAAG,OAAO,UAAU,WAAW,QAAQ,GAAG,IAAI,aAAa,KAAA,IAAY,KAAK,UAAU,IAJ3E,OAAO,aAAa,YAAY,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI,IAK/F;CAGA,MAAM,UAAU,SAAS,KAAA,KAAa,cAAc,KAAA;CACpD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAChD,MAAM,SAAS,QAAQ,CAAC;CACxB,MAAM,CAAC,UAAU,eAAe,SAAS,MAAM;CAC/C,MAAM,CAAC,OAAO,YAAY,SAAS,MAAM;CACzC,IAAI,UAAU,CAAC,UAAU,YAAY,IAAI;CACzC,IAAI,CAAC,UAAU,OAAO,SAAS,KAAK;CACpC,gBAAgB;EAEd,IAAI,UAAU,YAAY,CAAC,OAAO;GAChC,MAAM,UAAU,4BAA4B,SAAS,IAAI,CAAC;GAC1D,aAAa,qBAAqB,OAAO;EAC3C;CACF,GAAG;EAAC;EAAQ;EAAU;CAAK,CAAC;CAC5B,MAAM,UAAU,YAAY,CAAC;CAC7B,MAAM,SAAS,kBAAkB;EAC/B,YAAY,KAAK;EACjB,IAAI,SAAS,KAAA,GAAW,YAAY;CACtC,GAAG,CAAC,WAAW,IAAI,CAAC;CACpB,gBAAgB,SAAS,QAAQ,GAAG;CACpC,gCAAgC;EAC9B,IAAI,WAAW,qBAAqB,GAAG,OAAO;CAChD,GAAG,CAAC,SAAS,MAAM,CAAC;CACpB,IAAI,WAAW,CAAC,UAAU,OAAO;CAEjC,MAAM,QACJ,qBAAC,OAAD;EACO;EACL,GAAI;EACJ,MAAK;EACL,aAAU;EACV,WAAW,GAAG,cAAc,EAAE,QAAQ,CAAC,GAAG,SAAS;EALrD,UAAA;GAYG,SAAS,KAAA,IAAY,OACpB,oBAAC,QAAD;IACE,aAAU;IACV,WAAU;IAEV,UAAA,oBAAC,UAAD;KAAU,SAAS,QAAQ,IAAI;KAAG,QAAO;KAAQ,WAAU;KACxD,UAAA;IACO,CAAA;GACN,CAAA;GAGR,oBAAC,OAAD;IAAK,KAAK;IAAO,aAAU;IAAa,WAAU;IAChD,UAAA,qBAAC,OAAD;KAAK,KAAK;KAAV,UAAA,CACG,UAAU,KAAA,IAAY,OACrB,oBAAC,OAAD;MACE,aAAU;MACV,WAAU;MAEV,UAAA,oBAAC,WAAD;OAAW,OAAO;OAAQ,UAAA;MAAiB,CAAA;KACtC,CAAA,GAER,aAAa,KAAA,IAAY,OACxB,oBAAC,OAAD;MAAK,aAAU;MACb,UAAA,oBAAC,WAAD;OAAW,OAAO;OAAW;MAAoB,CAAA;KAC9C,CAAA,CAEJ;;GACF,CAAA;GACJ,YACC,oBAACA,QAAD;IACE,aAAU;IACV,cAAY,OAAO,UAAU,WAAW,GAAG,aAAa,IAAI,UAAU;IACtE,eAAgB,SAAS,KAAA,IAAY,aAAa,IAAI,IAAI,UAAU;IACpE,WAAU;IAEV,UAAA,oBAAC,cAAD,CAAe,CAAA;GACL,CAAA,IACV;EACD;;CAEP,IAAI,CAAC,SAAS,OAAO;CACrB,OACE,oBAAC,OAAD;EACE,aAAU;EACV,cAAY,SAAS,KAAA;EACrB,kBAAkB,UAAU;GAC1B,IAAI,WAAW,MAAM,WAAW,MAAM,iBAAiB,MAAM,iBAAiB,sBAC5E,OAAO;EACX;EACA,WAAU;EAEV,UAAA,oBAAC,OAAD,EAAA,UAAM,MAAW,CAAA;CACd,CAAA;AAET,CAAC"}
|
|
@@ -48,6 +48,6 @@ export interface AlertDialogProps extends Omit<DialogProps, "role" | "closeButto
|
|
|
48
48
|
/** The actions. Give the cancel button `slot="close"`. */
|
|
49
49
|
children?: ReactNode;
|
|
50
50
|
}
|
|
51
|
-
export declare
|
|
51
|
+
export declare const AlertDialog: import("react").ForwardRefExoticComponent<AlertDialogProps & import("react").RefAttributes<HTMLElement>>;
|
|
52
52
|
//#endregion
|
|
53
53
|
//# sourceMappingURL=alert-dialog.d.ts.map
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { Dialog } from "../dialog/dialog.js";
|
|
3
|
+
import { forwardRef } from "react";
|
|
3
4
|
import { jsx } from "react/jsx-runtime";
|
|
4
5
|
//#region src/alert-dialog/alert-dialog.tsx
|
|
5
|
-
function AlertDialog(props) {
|
|
6
|
+
const AlertDialog = forwardRef(function AlertDialog(props, ref) {
|
|
6
7
|
return /* @__PURE__ */ jsx(Dialog, {
|
|
7
8
|
...props,
|
|
9
|
+
ref,
|
|
8
10
|
role: "alertdialog",
|
|
9
11
|
closeButton: false,
|
|
10
12
|
isDismissable: false
|
|
11
13
|
});
|
|
12
|
-
}
|
|
14
|
+
});
|
|
13
15
|
//#endregion
|
|
14
16
|
export { AlertDialog };
|
|
15
17
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alert-dialog.js","names":[],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport type
|
|
1
|
+
{"version":3,"file":"alert-dialog.js","names":[],"sources":["../../src/alert-dialog/alert-dialog.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, type ReactNode } from \"react\";\n\nimport { Dialog, type DialogProps } from \"../dialog/dialog.js\";\n\n/**\n * AlertDialog: a confirmation that must be answered.\n *\n * A PRESET over `Dialog`, not a second implementation. The legacy\n * `alert-dialog.tsx` is a 157-line clone of `dialog.tsx` with three\n * differences, and keeping two copies is how they drift -- the legacy pair\n * already disagree about `leading-none` on their titles for no stated\n * reason.\n *\n * Census: 2 files, all eight parts used twice each, `AlertDialogPortal`,\n * `AlertDialogOverlay` and `AlertDialogTrigger` never rendered externally.\n *\n * What the preset forces, and why each one is a defect if forgotten:\n *\n * - `role=\"alertdialog\"`. React Aria associates the description with\n * `aria-describedby` ONLY for this role (useDialog.mjs:29), so a\n * confirmation built from a plain Dialog loses the sentence explaining\n * what is about to happen.\n * - `closeButton={false}`. An X in the corner is a third answer to a\n * two-answer question, and it is ambiguous: it reads as \"cancel\" but\n * looks like \"dismiss\".\n * - `isDismissable={false}`. Clicking the page must not resolve a\n * destructive confirmation. Escape still cancels, which is the APG\n * behaviour and what Radix does.\n * - `description` is REQUIRED. A confirmation with no explanation is the\n * thing this component exists to prevent, and the role only associates it\n * when it is there.\n *\n * NO `AlertDialogAction` OR `AlertDialogCancel`. The legacy shipped both,\n * with `AlertDialogAction` defaulting to the gradient primary button -- and\n * BOTH call sites override it back to destructive by hand\n * (`data-table.tsx:146`, `calendar-view.tsx:638`). A default that every\n * caller fights is not a default. Use a `Button` and say which one it is:\n * `slot=\"close\"` cancels, and the confirm button picks its own variant.\n */\nexport interface AlertDialogProps\n extends Omit<DialogProps, \"role\" | \"closeButton\" | \"closeLabel\" | \"isDismissable\"> {\n /**\n * What is about to happen, and whether it can be undone.\n *\n * **Required**, unlike on `Dialog`. `role=\"alertdialog\"` is what makes\n * React Aria wire this to `aria-describedby`, so it is both the sentence\n * the user needs and the one the screen reader announces with the name.\n */\n description: ReactNode;\n /** The actions. Give the cancel button `slot=\"close\"`. */\n children?: ReactNode;\n}\n\nexport const AlertDialog = forwardRef<HTMLElement, AlertDialogProps>(function AlertDialog(\n props,\n ref,\n) {\n return (\n <Dialog {...props} ref={ref} role=\"alertdialog\" closeButton={false} isDismissable={false} />\n );\n});\n"],"mappings":";;;;;AAuDA,MAAa,cAAc,WAA0C,SAAS,YAC5E,OACA,KACA;CACA,OACE,oBAAC,QAAD;EAAQ,GAAI;EAAY;EAAK,MAAK;EAAc,aAAa;EAAO,eAAe;CAAQ,CAAA;AAE/F,CAAC"}
|
package/dist/avatar/avatar.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export declare const avatarVariants: (props?: ({
|
|
|
29
29
|
export interface AvatarProps extends Omit<ComponentPropsWithoutRef<"span">, "children">, VariantProps<typeof avatarVariants> {
|
|
30
30
|
/** Image URL. Omit it to render the fallback alone. */
|
|
31
31
|
src?: string;
|
|
32
|
+
/**
|
|
33
|
+
* A small dot at the corner: `online` or `offline`. It grows in when it appears,
|
|
34
|
+
* shrinks away when it goes, and swaps when it changes. Its word is read to
|
|
35
|
+
* assistive technology beside the name.
|
|
36
|
+
*/
|
|
37
|
+
presence?: "online" | "offline";
|
|
32
38
|
/**
|
|
33
39
|
* Alternative text. **Required**, and `""` is the right value when the
|
|
34
40
|
* person's name already appears next to the avatar.
|
|
@@ -45,6 +51,6 @@ export interface AvatarProps extends Omit<ComponentPropsWithoutRef<"span">, "chi
|
|
|
45
51
|
*/
|
|
46
52
|
fallback?: ReactNode;
|
|
47
53
|
}
|
|
48
|
-
export declare
|
|
54
|
+
export declare const Avatar: import("react").ForwardRefExoticComponent<AvatarProps & import("react").RefAttributes<HTMLImageElement>>;
|
|
49
55
|
//#endregion
|
|
50
56
|
//# sourceMappingURL=avatar.d.ts.map
|
package/dist/avatar/avatar.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
3
5
|
import { cva } from "class-variance-authority";
|
|
6
|
+
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
4
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
|
-
import { useCallback, useState } from "react";
|
|
6
8
|
//#region src/avatar/avatar.tsx
|
|
7
9
|
/**
|
|
8
10
|
* Avatar, ported from the legacy shadcn/Radix component.
|
|
@@ -20,7 +22,7 @@ import { useCallback, useState } from "react";
|
|
|
20
22
|
* class assembled at runtime is invisible to it and would never be emitted.
|
|
21
23
|
*/
|
|
22
24
|
const avatarVariants = cva([
|
|
23
|
-
"relative inline-flex shrink-0 items-center justify-center
|
|
25
|
+
"relative inline-flex shrink-0 items-center justify-center",
|
|
24
26
|
"rounded-full",
|
|
25
27
|
"box-border",
|
|
26
28
|
"font-sans font-medium",
|
|
@@ -35,7 +37,15 @@ const avatarVariants = cva([
|
|
|
35
37
|
} },
|
|
36
38
|
defaultVariants: { size: "md" }
|
|
37
39
|
});
|
|
38
|
-
|
|
40
|
+
/** Where the presence dot sits and how big it is, per size. Static strings: Tailwind scans source text. */
|
|
41
|
+
const dotPlace = {
|
|
42
|
+
xs: "bottom-0 end-0 size-1.5",
|
|
43
|
+
sm: "bottom-0 end-0 size-2",
|
|
44
|
+
md: "bottom-0 end-0 size-2.5",
|
|
45
|
+
lg: "bottom-0 end-0 size-3",
|
|
46
|
+
xl: "bottom-1 end-1 size-3.5"
|
|
47
|
+
};
|
|
48
|
+
const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback, presence, ...props }, ref) {
|
|
39
49
|
const [status, setStatus] = useState(src === void 0 ? "idle" : "loading");
|
|
40
50
|
/**
|
|
41
51
|
* Reset when src changes, during RENDER rather than in an effect.
|
|
@@ -62,29 +72,52 @@ function Avatar({ className, size, src, alt, fallback, ...props }) {
|
|
|
62
72
|
*/
|
|
63
73
|
const measure = useCallback((node) => {
|
|
64
74
|
if (node?.complete) setStatus(node.naturalWidth > 0 ? "loaded" : "error");
|
|
75
|
+
else if (node) node.dataset.loading = "";
|
|
65
76
|
}, []);
|
|
77
|
+
const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);
|
|
66
78
|
const showImage = src !== void 0 && status !== "error";
|
|
67
79
|
return /* @__PURE__ */ jsxs("span", {
|
|
68
80
|
...props,
|
|
69
81
|
"data-slot": "avatar",
|
|
70
82
|
"data-status": status,
|
|
71
83
|
className: cn(avatarVariants({ size }), className),
|
|
72
|
-
children: [
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
children: [
|
|
85
|
+
showImage ? /* @__PURE__ */ jsx("img", {
|
|
86
|
+
ref: imageRef,
|
|
87
|
+
"data-slot": "avatar-image",
|
|
88
|
+
className: "avatar-photo size-full rounded-[inherit] object-cover",
|
|
89
|
+
src,
|
|
90
|
+
alt,
|
|
91
|
+
onLoad: (event) => {
|
|
92
|
+
delete event.currentTarget.dataset.loading;
|
|
93
|
+
setStatus("loaded");
|
|
94
|
+
},
|
|
95
|
+
onError: () => setStatus("error")
|
|
96
|
+
}, src) : null,
|
|
97
|
+
status === "loaded" ? null : /* @__PURE__ */ jsx("span", {
|
|
98
|
+
"data-slot": "avatar-fallback",
|
|
99
|
+
"aria-hidden": "true",
|
|
100
|
+
className: cn("select-none", src !== void 0 && "motion-safe:animate-avatar-fallback-in"),
|
|
101
|
+
children: fallback
|
|
102
|
+
}),
|
|
103
|
+
presence === void 0 ? null : /* @__PURE__ */ jsx("span", {
|
|
104
|
+
className: "sr-only",
|
|
105
|
+
children: presence
|
|
106
|
+
}),
|
|
107
|
+
/* @__PURE__ */ jsx(SwapText, {
|
|
108
|
+
swapKey: presence ?? "",
|
|
109
|
+
motion: "dot",
|
|
110
|
+
className: cn("absolute z-10", dotPlace[size ?? "md"]),
|
|
111
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
112
|
+
"data-slot": "avatar-presence",
|
|
113
|
+
"data-presence": presence,
|
|
114
|
+
"aria-hidden": "true",
|
|
115
|
+
className: cn("block size-full rounded-full border-(length:--border-width) border-background box-border", presence === "online" ? "bg-success-strong" : "bg-muted-foreground")
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
]
|
|
86
119
|
});
|
|
87
|
-
}
|
|
120
|
+
});
|
|
88
121
|
//#endregion
|
|
89
122
|
export { Avatar, avatarVariants };
|
|
90
123
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {
|
|
1
|
+
{"version":3,"file":"avatar.js","names":[],"sources":["../../src/avatar/avatar.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n useCallback,\n useMemo,\n useState,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * Avatar, ported from the legacy shadcn/Radix component.\n *\n * React Aria ships no avatar, so the image load/error state machine is\n * re-implemented here -- see the status handling below, which is the part\n * Radix was actually providing.\n *\n * The three legacy parts (Avatar / AvatarImage / AvatarFallback) collapse\n * into one props-driven component. The census is unambiguous: 36 / 35 / 35\n * uses, always as the same triple, so the compound API bought nothing but\n * three imports and the chance to forget one.\n *\n * Every string below is a STATIC literal. Tailwind scans source text, so a\n * class assembled at runtime is invisible to it and would never be emitted.\n */\nexport const avatarVariants = cva(\n [\n \"relative inline-flex shrink-0 items-center justify-center\",\n \"rounded-full\",\n // box-border: this library's reset is OPT-IN, so a consumer may load\n // none and the browser default content-box applies. The Alert shipped\n // overflowing its container by 46px for exactly this reason.\n \"box-border\",\n // font-sans: initials are text, and an unstyled document renders text in\n // a serif. The accordion panel shipped that way.\n \"font-sans font-medium\",\n \"bg-muted text-foreground\",\n ],\n {\n variants: {\n // Sizes come from the census, not from taste: call sites reach for\n // h-5 / h-6 / h-8 / h-10 / h-16 through className because the legacy\n // component has no size prop. size-7 (2 uses) has no variant; those\n // sites pick sm or md.\n //\n // Deliberately NOT on the density scale. An avatar is content, not a\n // control -- it does not shrink when a table is compacted, and putting\n // it on --density-control-* would couple a portrait to a button's\n // rhythm. If that turns out wrong it becomes an --avatar-* scale.\n size: {\n xs: \"size-5 text-2xs leading-(--leading-none)\",\n sm: \"size-6 text-2xs leading-(--leading-none)\",\n md: \"size-8 text-sm leading-(--leading-none)\",\n lg: \"size-10 text-sm leading-(--leading-none)\",\n xl: \"size-16 text-base leading-(--leading-none)\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/** Where the presence dot sits and how big it is, per size. Static strings: Tailwind scans source text. */\nconst dotPlace = {\n xs: \"bottom-0 end-0 size-1.5\",\n sm: \"bottom-0 end-0 size-2\",\n md: \"bottom-0 end-0 size-2.5\",\n lg: \"bottom-0 end-0 size-3\",\n xl: \"bottom-1 end-1 size-3.5\",\n} as const;\n\ntype Status = \"idle\" | \"loading\" | \"loaded\" | \"error\";\n\n/**\n * A user avatar.\n *\n * @example\n * <Avatar src={user.photoUrl} alt={user.name} fallback={initials(user.name)} />\n * <Avatar alt=\"\" fallback=\"JD\" />\n */\nexport interface AvatarProps\n extends Omit<ComponentPropsWithoutRef<\"span\">, \"children\">, VariantProps<typeof avatarVariants> {\n /** Image URL. Omit it to render the fallback alone. */\n src?: string;\n /**\n * A small dot at the corner: `online` or `offline`. It grows in when it appears,\n * shrinks away when it goes, and swaps when it changes. Its word is read to\n * assistive technology beside the name.\n */\n presence?: \"online\" | \"offline\";\n /**\n * Alternative text. **Required**, and `\"\"` is the right value when the\n * person's name already appears next to the avatar.\n *\n * Required on purpose: the census found 15 of 38 legacy `AvatarImage` uses\n * with no `alt` at all, which renders an `<img>` that fails axe's\n * `image-alt` rule. Making it required turns that into a decision the\n * caller makes rather than a default they fall into.\n */\n alt: string;\n /**\n * Shown until the image loads, and permanently if it fails. Usually\n * initials.\n */\n fallback?: ReactNode;\n}\n\nexport const Avatar = forwardRef<HTMLImageElement, AvatarProps>(function Avatar(\n { className, size, src, alt, fallback, presence, ...props },\n ref,\n) {\n const [status, setStatus] = useState<Status>(src === undefined ? \"idle\" : \"loading\");\n\n /**\n * Reset when src changes, during RENDER rather than in an effect.\n *\n * React's documented \"adjusting state when a prop changes\" pattern. An\n * effect that called setState here would be flagged by\n * react-hooks/set-state-in-effect, and rightly: it renders once with the\n * stale status before correcting itself.\n */\n const [renderedSrc, setRenderedSrc] = useState(src);\n if (src !== renderedSrc) {\n setRenderedSrc(src);\n setStatus(src === undefined ? \"idle\" : \"loading\");\n }\n\n /**\n * Resolve the status from the DOM, not only from events.\n *\n * A cached or data-URL image is already decoded by the time React attaches\n * its handlers, so `onLoad` fires BEFORE any effect could run -- and an\n * effect that set \"loading\" would then clobber it and leave the avatar on\n * its fallback forever. A ref callback runs when the node attaches, which\n * is early enough to read `complete` and late enough to have a node. The\n * handlers below still cover the asynchronous case.\n */\n const measure = useCallback((node: HTMLImageElement | null) => {\n if (node?.complete) {\n setStatus(node.naturalWidth > 0 ? \"loaded\" : \"error\");\n } else if (node) {\n // Still downloading after mount: it fades in from a soft blur when it arrives. A photo that\n // is already decoded shows at once, so cached photos never fade.\n node.dataset.loading = \"\";\n }\n }, []);\n\n // Memoized, not called inline on the img below: mergeRefs returns a new\n // callback every call, and an inline call is a new callback every render --\n // React would detach and reattach both refs on every re-render instead of\n // only on mount, re-running measure's decode check each time.\n const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);\n\n const showImage = src !== undefined && status !== \"error\";\n\n return (\n <span\n {...props}\n data-slot=\"avatar\"\n data-status={status}\n className={cn(avatarVariants({ size }), className)}\n >\n {showImage ? (\n <img\n // The forwarded ref is null while the fallback shows -- the img is\n // not rendered then -- matching measure's own scope, not a new\n // limitation.\n key={src}\n ref={imageRef}\n data-slot=\"avatar-image\"\n className=\"avatar-photo size-full rounded-[inherit] object-cover\"\n src={src}\n alt={alt}\n onLoad={(event) => {\n delete event.currentTarget.dataset.loading;\n setStatus(\"loaded\");\n }}\n onError={() => setStatus(\"error\")}\n />\n ) : null}\n {status === \"loaded\" ? null : (\n // aria-hidden: when the image is present the img carries the\n // accessible name, and when it is not the initials are decorative\n // shorthand for a name the surrounding UI already states.\n <span\n data-slot=\"avatar-fallback\"\n aria-hidden=\"true\"\n className={cn(\"select-none\", src !== undefined && \"motion-safe:animate-avatar-fallback-in\")}\n >\n {fallback}\n </span>\n )}\n {presence === undefined ? null : <span className=\"sr-only\">{presence}</span>}\n <SwapText\n swapKey={presence ?? \"\"}\n motion=\"dot\"\n className={cn(\"absolute z-10\", dotPlace[size ?? \"md\"])}\n >\n <span\n data-slot=\"avatar-presence\"\n data-presence={presence}\n aria-hidden=\"true\"\n className={cn(\n \"block size-full rounded-full border-(length:--border-width) border-background box-border\",\n presence === \"online\" ? \"bg-success-strong\" : \"bg-muted-foreground\",\n )}\n />\n </SwapText>\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,iBAAiB,IAC5B;CACE;CACA;CAIA;CAGA;CACA;AACF,GACA;CACE,UAAU,EAUR,MAAM;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;EACJ,IAAI;CACN,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;;AAGA,MAAM,WAAW;CACf,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAsCA,MAAa,SAAS,WAA0C,SAAS,OACvE,EAAE,WAAW,MAAM,KAAK,KAAK,UAAU,UAAU,GAAG,SACpD,KACA;CACA,MAAM,CAAC,QAAQ,aAAa,SAAiB,QAAQ,KAAA,IAAY,SAAS,SAAS;;;;;;;;;CAUnF,MAAM,CAAC,aAAa,kBAAkB,SAAS,GAAG;CAClD,IAAI,QAAQ,aAAa;EACvB,eAAe,GAAG;EAClB,UAAU,QAAQ,KAAA,IAAY,SAAS,SAAS;CAClD;;;;;;;;;;;CAYA,MAAM,UAAU,aAAa,SAAkC;EAC7D,IAAI,MAAM,UACR,UAAU,KAAK,eAAe,IAAI,WAAW,OAAO;OAC/C,IAAI,MAGT,KAAK,QAAQ,UAAU;CAE3B,GAAG,CAAC,CAAC;CAML,MAAM,WAAW,cAAc,UAAU,KAAK,OAAO,GAAG,CAAC,KAAK,OAAO,CAAC;CAEtE,MAAM,YAAY,QAAQ,KAAA,KAAa,WAAW;CAElD,OACE,qBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,eAAa;EACb,WAAW,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,SAAS;EAJnD,UAAA;GAMG,YACC,oBAAC,OAAD;IAKE,KAAK;IACL,aAAU;IACV,WAAU;IACL;IACA;IACL,SAAS,UAAU;KACjB,OAAO,MAAM,cAAc,QAAQ;KACnC,UAAU,QAAQ;IACpB;IACA,eAAe,UAAU,OAAO;GACjC,GAXM,GAWN,IACC;GACH,WAAW,WAAW,OAIrB,oBAAC,QAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAW,GAAG,eAAe,QAAQ,KAAA,KAAa,wCAAwC;IAEzF,UAAA;GACG,CAAA;GAEP,aAAa,KAAA,IAAY,OAAO,oBAAC,QAAD;IAAM,WAAU;IAAW,UAAA;GAAe,CAAA;GAC3E,oBAAC,UAAD;IACE,SAAS,YAAY;IACrB,QAAO;IACP,WAAW,GAAG,iBAAiB,SAAS,QAAQ,KAAK;IAErD,UAAA,oBAAC,QAAD;KACE,aAAU;KACV,iBAAe;KACf,eAAY;KACZ,WAAW,GACT,4FACA,aAAa,WAAW,sBAAsB,qBAChD;IACD,CAAA;GACO,CAAA;EACN;;AAEV,CAAC"}
|
package/dist/badge/badge.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
|
-
import { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
3
3
|
//#region src/badge/badge.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Badge -- the status pill, in every shape the product asks for.
|
|
@@ -41,7 +41,14 @@ export declare const badgeVariants: (props?: ({
|
|
|
41
41
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
42
42
|
shape?: "rounded" | "pill" | null | undefined;
|
|
43
43
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
44
|
-
export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantProps<typeof badgeVariants> {
|
|
45
|
-
|
|
44
|
+
export interface BadgeProps extends ComponentPropsWithoutRef<"span">, VariantProps<typeof badgeVariants> {
|
|
45
|
+
/**
|
|
46
|
+
* A leading icon. A different icon component swaps in place (the new one grows in
|
|
47
|
+
* from small and blurred); the same one re-rendering stays still. Mark it
|
|
48
|
+
* `aria-hidden` when the label already says what it means.
|
|
49
|
+
*/
|
|
50
|
+
icon?: ReactNode;
|
|
51
|
+
}
|
|
52
|
+
export declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
46
53
|
//#endregion
|
|
47
54
|
//# sourceMappingURL=badge.d.ts.map
|
package/dist/badge/badge.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
|
+
import { useMorphWidth } from "../lib/motion.js";
|
|
4
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
3
5
|
import { cva } from "class-variance-authority";
|
|
4
|
-
import {
|
|
6
|
+
import { forwardRef, isValidElement, useRef } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
5
8
|
//#region src/badge/badge.tsx
|
|
6
9
|
/**
|
|
7
10
|
* Badge -- the status pill, in every shape the product asks for.
|
|
@@ -43,7 +46,8 @@ const badgeVariants = cva([
|
|
|
43
46
|
"font-sans font-medium",
|
|
44
47
|
"whitespace-nowrap",
|
|
45
48
|
"box-border",
|
|
46
|
-
"[
|
|
49
|
+
"[&_svg]:pointer-events-none [&_svg]:size-3",
|
|
50
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle"
|
|
47
51
|
], {
|
|
48
52
|
variants: {
|
|
49
53
|
/** What the badge MEANS. Colour follows from it; it is never picked directly. */
|
|
@@ -170,8 +174,19 @@ const badgeVariants = cva([
|
|
|
170
174
|
shape: "rounded"
|
|
171
175
|
}
|
|
172
176
|
});
|
|
173
|
-
|
|
177
|
+
/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */
|
|
178
|
+
function iconKey(icon) {
|
|
179
|
+
if (!isValidElement(icon)) return "icon";
|
|
180
|
+
const type = icon.type;
|
|
181
|
+
return typeof type === "string" ? type : type.displayName ?? type.name ?? "icon";
|
|
182
|
+
}
|
|
183
|
+
const Badge = forwardRef(function Badge({ className, tone, appearance, size, shape, icon, children, ...props }, ref) {
|
|
184
|
+
const content = useRef(null);
|
|
185
|
+
const text = typeof children === "string" || typeof children === "number" ? String(children) : null;
|
|
186
|
+
const glyph = icon === void 0 || icon === null ? "" : iconKey(icon);
|
|
187
|
+
useMorphWidth(content, `${text ?? ""}|${glyph}`);
|
|
174
188
|
return /* @__PURE__ */ jsx("span", {
|
|
189
|
+
ref,
|
|
175
190
|
...props,
|
|
176
191
|
"data-slot": "badge",
|
|
177
192
|
"data-tone": tone ?? "neutral",
|
|
@@ -180,9 +195,26 @@ function Badge({ className, tone, appearance, size, shape, ...props }) {
|
|
|
180
195
|
appearance,
|
|
181
196
|
size,
|
|
182
197
|
shape
|
|
183
|
-
}), className)
|
|
198
|
+
}), className),
|
|
199
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
200
|
+
className: "button-slot",
|
|
201
|
+
children: /* @__PURE__ */ jsxs("span", {
|
|
202
|
+
ref: content,
|
|
203
|
+
className: "inline-flex flex-none items-center gap-1",
|
|
204
|
+
children: [glyph ? /* @__PURE__ */ jsx(SwapText, {
|
|
205
|
+
swapKey: glyph,
|
|
206
|
+
motion: "glyph",
|
|
207
|
+
className: "inline-flex items-center",
|
|
208
|
+
children: icon
|
|
209
|
+
}) : null, text === null ? children : /* @__PURE__ */ jsx(SwapText, {
|
|
210
|
+
swapKey: text,
|
|
211
|
+
className: "inline-block",
|
|
212
|
+
children: text
|
|
213
|
+
})]
|
|
214
|
+
})
|
|
215
|
+
})
|
|
184
216
|
});
|
|
185
|
-
}
|
|
217
|
+
});
|
|
186
218
|
//#endregion
|
|
187
219
|
export { Badge, badgeVariants };
|
|
188
220
|
|
package/dist/badge/badge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"badge.js","names":[],"sources":["../../src/badge/badge.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport type { ComponentPropsWithoutRef } from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Badge -- the status pill, in every shape the product asks for.\n *\n * Presentational, so there is no React Aria base. RAC's `Tag` looks similar\n * and is not equivalent: it is focusable, selectable and removable, and it is\n * illegal outside a `TagGroup` collection. A status pill is none of those.\n *\n * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one\n * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --\n * across 22 and 22 consumer files. They disagreed about everything: shape\n * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)\n * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for\n * the same colour. Converging them is why `variant` is gone.\n *\n * The axes are the two questions a call site actually answers -- what does\n * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than\n * one axis whose values are the cross product. Six tones by three appearances\n * is eighteen `variant` values otherwise, and shipping eighteen is how a\n * design system ends up with `softPrimary` next to `primaryOutline`.\n *\n * Every string below is a STATIC literal, including the compound ones.\n * Tailwind scans source text, so a class assembled at runtime is invisible to\n * it and would never be emitted.\n *\n * Three things in the legacy are deliberately not ported:\n *\n * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles\n * existed only to colour the anchor asChild produced.\n * - `focus-visible:ring-*`. A span is not focusable, so the ring could never\n * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is\n * the wrong one anyway -- box-shadows vanish under forced-colors.\n * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied\n * across every shadcn control.\n */\nexport const badgeVariants = cva(\n [\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1\",\n \"border-(length:--border-width) border-transparent\",\n // NO leading-* here. It lives on each `size` instead, AFTER that size's\n // text-* class: tailwind-merge treats a later text-* as owning\n // line-height, so a leading-* in this base string is silently stripped the\n // moment a size variant adds one. The NoUaLeak story caught exactly that.\n \"font-sans font-medium\",\n \"whitespace-nowrap\",\n // box-border and font-sans are not boilerplate. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none: an unstyled\n // span inherits the host's serif, and content-box would add the padding\n // to any width a consumer sets.\n \"box-border\",\n \"[&>svg]:pointer-events-none [&>svg]:size-3\",\n ],\n {\n variants: {\n /** What the badge MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"\",\n primary: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n /** How loud it is. */\n appearance: {\n solid: \"\",\n soft: \"\",\n outline: \"bg-transparent\",\n },\n size: {\n sm: \"px-2 py-0.5 text-xs leading-(--leading-snug)\",\n md: \"px-2.5 py-0.5 text-xs leading-(--leading-snug)\",\n lg: \"px-3 py-1 text-sm leading-(--leading-snug)\",\n },\n /*\n * The product's two components disagreed on this and nothing else:\n * `badge.tsx` was rounded-md, `pill.tsx` was rounded-full. Both survive\n * as one axis rather than one winning, because a count-bearing badge and\n * a verdict pill really do read differently.\n */\n shape: { rounded: \"rounded-md\", pill: \"rounded-full\" },\n },\n compoundVariants: [\n // SOLID -- the fill carries the meaning, so the text must clear 4.5:1 on it.\n /*\n * muted-foreground, NOT `secondary`. --color-secondary resolves to\n * periwinkle.400, so a \"neutral\" badge rendered lavender -- caught in a\n * browser, not by any assertion, because the story compared it against\n * the same wrong token it was painted with. A tone called neutral has to\n * be neutral; the periwinkle fill the product uses for its `secondary`\n * badge is the brand's SECOND colour, not an absence of one.\n */\n { tone: \"neutral\", appearance: \"solid\", class: \"bg-muted-foreground text-background\" },\n { tone: \"primary\", appearance: \"solid\", class: \"bg-primary text-primary-foreground\" },\n { tone: \"success\", appearance: \"solid\", class: \"bg-success-strong text-success-strong-foreground\" },\n { tone: \"warning\", appearance: \"solid\", class: \"bg-warning-strong text-warning-strong-foreground\" },\n { tone: \"danger\", appearance: \"solid\", class: \"bg-destructive text-destructive-foreground\" },\n { tone: \"info\", appearance: \"solid\", class: \"bg-info-strong text-info-strong-foreground\" },\n\n /*\n * SOFT -- a tint, and the default, because this is what a status pill is.\n * The ink is one step darker than the obvious pairing for success,\n * warning and info: the product measured 4.13, 3.48 and 3.34 on its own\n * tints and had to darken all three. Our `-foreground` tokens already\n * encode that correction, which is why they are used rather than\n * `-strong`.\n */\n { tone: \"neutral\", appearance: \"soft\", class: \"bg-muted text-muted-foreground\" },\n // No primary-subtle-foreground token: plain `foreground` on this tint\n // measures 18.70 light / 9.78 dark, already graded for the Table's\n // highlighted row.\n { tone: \"primary\", appearance: \"soft\", class: \"bg-primary-subtle text-foreground\" },\n { tone: \"success\", appearance: \"soft\", class: \"bg-success-subtle text-success-foreground\" },\n { tone: \"warning\", appearance: \"soft\", class: \"bg-warning-subtle text-warning-foreground\" },\n {\n tone: \"danger\",\n appearance: \"soft\",\n class: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n },\n { tone: \"info\", appearance: \"soft\", class: \"bg-info-subtle text-info-foreground\" },\n\n /*\n * OUTLINE -- no fill, so the border is the only thing separating the\n * badge from the page and is held to the 3:1 non-text floor of WCAG\n * 1.4.11. Text AND border are `-strong`, not `-foreground`: with no tint\n * behind it the label sits on `background`, and `-strong on background`\n * is the pair already graded at 4.5. `-foreground` is graded only\n * against its own `-subtle`, so using it here would have been a ratio\n * nobody had measured.\n */\n { tone: \"neutral\", appearance: \"outline\", class: \"border-border text-foreground\" },\n { tone: \"primary\", appearance: \"outline\", class: \"border-primary-strong text-foreground\" },\n {\n tone: \"success\",\n appearance: \"outline\",\n class: \"border-success-strong text-success-strong\",\n },\n {\n tone: \"warning\",\n appearance: \"outline\",\n class: \"border-warning-strong text-warning-strong\",\n },\n {\n tone: \"danger\",\n appearance: \"outline\",\n class: \"border-destructive-strong text-destructive-strong\",\n },\n { tone: \"info\", appearance: \"outline\", class: \"border-info-strong text-info-strong\" },\n ],\n defaultVariants: { tone: \"neutral\", appearance: \"soft\", size: \"md\", shape: \"rounded\" },\n },\n);\n\nexport interface BadgeProps\n extends ComponentPropsWithoutRef<\"span\">,\n VariantProps<typeof badgeVariants> {}\n\nexport function Badge({ className, tone, appearance, size, shape, ...props }: BadgeProps) {\n return (\n <span\n {...props}\n data-slot=\"badge\"\n data-tone={tone ?? \"neutral\"}\n className={cn(badgeVariants({ tone, appearance, size, shape }), className)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,gBAAgB,IAC3B;CACE;CACA;CAKA;CACA;CAKA;CACA;AACF,GACA;CACE,UAAU;;EAER,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,SAAS;GACT,QAAQ;GACR,MAAM;EACR;;EAEA,YAAY;GACV,OAAO;GACP,MAAM;GACN,SAAS;EACX;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAOA,OAAO;GAAE,SAAS;GAAc,MAAM;EAAe;CACvD;CACA,kBAAkB;EAUhB;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAsC;EACrF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAqC;EACpF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAU,YAAY;GAAS,OAAO;EAA6C;EAC3F;GAAE,MAAM;GAAQ,YAAY;GAAS,OAAO;EAA6C;EAUzF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAiC;EAI/E;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAoC;EAClF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAQ,OAAO;EAAsC;EAWjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAgC;EACjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAwC;EACzF;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAW,OAAO;EAAsC;CACtF;CACA,iBAAiB;EAAE,MAAM;EAAW,YAAY;EAAQ,MAAM;EAAM,OAAO;CAAU;AACvF,CACF;AAMA,SAAgB,MAAM,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,GAAG,SAAqB;CACxF,OACE,oBAAC,QAAD;EACE,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;CAC1E,CAAA;AAEL"}
|
|
1
|
+
{"version":3,"file":"badge.js","names":[],"sources":["../../src/badge/badge.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n isValidElement,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { useMorphWidth } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\n\n/**\n * Badge -- the status pill, in every shape the product asks for.\n *\n * Presentational, so there is no React Aria base. RAC's `Tag` looks similar\n * and is not equivalent: it is focusable, selectable and removable, and it is\n * illegal outside a `TagGroup` collection. A status pill is none of those.\n *\n * THIS ABSORBED THE PRODUCT'S `Pill`. The app shipped two components for one\n * job -- `badge.tsx` with a `variant` axis and `pill.tsx` with a `tone` axis --\n * across 22 and 22 consumer files. They disagreed about everything: shape\n * (`rounded-md` against `rounded-full`), size (one fixed, one `sm`/`md`/`lg`)\n * and naming, so `pill` accepted `blue` AND `info`, `green` AND `success`, for\n * the same colour. Converging them is why `variant` is gone.\n *\n * The axes are the two questions a call site actually answers -- what does\n * this MEAN (`tone`) and how loud should it be (`appearance`) -- rather than\n * one axis whose values are the cross product. Six tones by three appearances\n * is eighteen `variant` values otherwise, and shipping eighteen is how a\n * design system ends up with `softPrimary` next to `primaryOutline`.\n *\n * Every string below is a STATIC literal, including the compound ones.\n * Tailwind scans source text, so a class assembled at runtime is invisible to\n * it and would never be emitted.\n *\n * Three things in the legacy are deliberately not ported:\n *\n * - `asChild` and its `[a&]:hover:` rules. Zero call sites; the hover styles\n * existed only to colour the anchor asChild produced.\n * - `focus-visible:ring-*`. A span is not focusable, so the ring could never\n * appear. A consumer who adds `tabIndex` owns the indicator, and a ring is\n * the wrong one anyway -- box-shadows vanish under forced-colors.\n * - `aria-invalid:*`. A badge is never invalid; that was boilerplate copied\n * across every shadcn control.\n */\nexport const badgeVariants = cva(\n [\n \"inline-flex w-fit shrink-0 items-center justify-center gap-1\",\n \"border-(length:--border-width) border-transparent\",\n // NO leading-* here. It lives on each `size` instead, AFTER that size's\n // text-* class: tailwind-merge treats a later text-* as owning\n // line-height, so a leading-* in this base string is silently stripped the\n // moment a size variant adds one. The NoUaLeak story caught exactly that.\n \"font-sans font-medium\",\n \"whitespace-nowrap\",\n // box-border and font-sans are not boilerplate. The reset in\n // @seekalfred/styles is OPT-IN, so a consumer may load none: an unstyled\n // span inherits the host's serif, and content-box would add the padding\n // to any width a consumer sets.\n \"box-border\",\n \"[&_svg]:pointer-events-none [&_svg]:size-3\",\n // Colour, border and fill ease over 240ms as the tone changes.\n \"motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle\",\n ],\n {\n variants: {\n /** What the badge MEANS. Colour follows from it; it is never picked directly. */\n tone: {\n neutral: \"\",\n primary: \"\",\n success: \"\",\n warning: \"\",\n danger: \"\",\n info: \"\",\n },\n /** How loud it is. */\n appearance: {\n solid: \"\",\n soft: \"\",\n outline: \"bg-transparent\",\n },\n size: {\n sm: \"px-2 py-0.5 text-xs leading-(--leading-snug)\",\n md: \"px-2.5 py-0.5 text-xs leading-(--leading-snug)\",\n lg: \"px-3 py-1 text-sm leading-(--leading-snug)\",\n },\n /*\n * The product's two components disagreed on this and nothing else:\n * `badge.tsx` was rounded-md, `pill.tsx` was rounded-full. Both survive\n * as one axis rather than one winning, because a count-bearing badge and\n * a verdict pill really do read differently.\n */\n shape: { rounded: \"rounded-md\", pill: \"rounded-full\" },\n },\n compoundVariants: [\n // SOLID -- the fill carries the meaning, so the text must clear 4.5:1 on it.\n /*\n * muted-foreground, NOT `secondary`. --color-secondary resolves to\n * periwinkle.400, so a \"neutral\" badge rendered lavender -- caught in a\n * browser, not by any assertion, because the story compared it against\n * the same wrong token it was painted with. A tone called neutral has to\n * be neutral; the periwinkle fill the product uses for its `secondary`\n * badge is the brand's SECOND colour, not an absence of one.\n */\n { tone: \"neutral\", appearance: \"solid\", class: \"bg-muted-foreground text-background\" },\n { tone: \"primary\", appearance: \"solid\", class: \"bg-primary text-primary-foreground\" },\n { tone: \"success\", appearance: \"solid\", class: \"bg-success-strong text-success-strong-foreground\" },\n { tone: \"warning\", appearance: \"solid\", class: \"bg-warning-strong text-warning-strong-foreground\" },\n { tone: \"danger\", appearance: \"solid\", class: \"bg-destructive text-destructive-foreground\" },\n { tone: \"info\", appearance: \"solid\", class: \"bg-info-strong text-info-strong-foreground\" },\n\n /*\n * SOFT -- a tint, and the default, because this is what a status pill is.\n * The ink is one step darker than the obvious pairing for success,\n * warning and info: the product measured 4.13, 3.48 and 3.34 on its own\n * tints and had to darken all three. Our `-foreground` tokens already\n * encode that correction, which is why they are used rather than\n * `-strong`.\n */\n { tone: \"neutral\", appearance: \"soft\", class: \"bg-muted text-muted-foreground\" },\n // No primary-subtle-foreground token: plain `foreground` on this tint\n // measures 18.70 light / 9.78 dark, already graded for the Table's\n // highlighted row.\n { tone: \"primary\", appearance: \"soft\", class: \"bg-primary-subtle text-foreground\" },\n { tone: \"success\", appearance: \"soft\", class: \"bg-success-subtle text-success-foreground\" },\n { tone: \"warning\", appearance: \"soft\", class: \"bg-warning-subtle text-warning-foreground\" },\n {\n tone: \"danger\",\n appearance: \"soft\",\n class: \"bg-destructive-subtle text-destructive-subtle-foreground\",\n },\n { tone: \"info\", appearance: \"soft\", class: \"bg-info-subtle text-info-foreground\" },\n\n /*\n * OUTLINE -- no fill, so the border is the only thing separating the\n * badge from the page and is held to the 3:1 non-text floor of WCAG\n * 1.4.11. Text AND border are `-strong`, not `-foreground`: with no tint\n * behind it the label sits on `background`, and `-strong on background`\n * is the pair already graded at 4.5. `-foreground` is graded only\n * against its own `-subtle`, so using it here would have been a ratio\n * nobody had measured.\n */\n { tone: \"neutral\", appearance: \"outline\", class: \"border-border text-foreground\" },\n { tone: \"primary\", appearance: \"outline\", class: \"border-primary-strong text-foreground\" },\n {\n tone: \"success\",\n appearance: \"outline\",\n class: \"border-success-strong text-success-strong\",\n },\n {\n tone: \"warning\",\n appearance: \"outline\",\n class: \"border-warning-strong text-warning-strong\",\n },\n {\n tone: \"danger\",\n appearance: \"outline\",\n class: \"border-destructive-strong text-destructive-strong\",\n },\n { tone: \"info\", appearance: \"outline\", class: \"border-info-strong text-info-strong\" },\n ],\n defaultVariants: { tone: \"neutral\", appearance: \"soft\", size: \"md\", shape: \"rounded\" },\n },\n);\n\nexport interface BadgeProps\n extends ComponentPropsWithoutRef<\"span\">, VariantProps<typeof badgeVariants> {\n /**\n * A leading icon. A different icon component swaps in place (the new one grows in\n * from small and blurred); the same one re-rendering stays still. Mark it\n * `aria-hidden` when the label already says what it means.\n */\n icon?: ReactNode;\n}\n\n/** The name an icon swaps under: a different component swaps; the same one re-rendering does not. */\nfunction iconKey(icon: ReactNode) {\n if (!isValidElement(icon)) return \"icon\";\n const type = icon.type as string | { displayName?: string; name?: string };\n return typeof type === \"string\" ? type : (type.displayName ?? type.name ?? \"icon\");\n}\n\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, tone, appearance, size, shape, icon, children, ...props },\n ref,\n) {\n const content = useRef<HTMLSpanElement>(null);\n const text = typeof children === \"string\" || typeof children === \"number\" ? String(children) : null;\n const glyph = icon === undefined || icon === null ? \"\" : iconKey(icon);\n // The pill follows its content: a new label or icon swaps in place while the width springs to the new size.\n useMorphWidth(content, `${text ?? \"\"}|${glyph}`);\n return (\n <span\n ref={ref}\n {...props}\n data-slot=\"badge\"\n data-tone={tone ?? \"neutral\"}\n className={cn(badgeVariants({ tone, appearance, size, shape }), className)}\n >\n <span className=\"button-slot\">\n <span ref={content} className=\"inline-flex flex-none items-center gap-1\">\n {glyph ? (\n <SwapText swapKey={glyph} motion=\"glyph\" className=\"inline-flex items-center\">\n {icon}\n </SwapText>\n ) : null}\n {text === null ? (\n children\n ) : (\n <SwapText swapKey={text} className=\"inline-block\">\n {text}\n </SwapText>\n )}\n </span>\n </span>\n </span>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAa,gBAAgB,IAC3B;CACE;CACA;CAKA;CACA;CAKA;CACA;CAEA;AACF,GACA;CACE,UAAU;;EAER,MAAM;GACJ,SAAS;GACT,SAAS;GACT,SAAS;GACT,SAAS;GACT,QAAQ;GACR,MAAM;EACR;;EAEA,YAAY;GACV,OAAO;GACP,MAAM;GACN,SAAS;EACX;EACA,MAAM;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI;EACN;EAOA,OAAO;GAAE,SAAS;GAAc,MAAM;EAAe;CACvD;CACA,kBAAkB;EAUhB;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAsC;EACrF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAqC;EACpF;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAW,YAAY;GAAS,OAAO;EAAmD;EAClG;GAAE,MAAM;GAAU,YAAY;GAAS,OAAO;EAA6C;EAC3F;GAAE,MAAM;GAAQ,YAAY;GAAS,OAAO;EAA6C;EAUzF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAiC;EAI/E;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAAoC;EAClF;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GAAE,MAAM;GAAW,YAAY;GAAQ,OAAO;EAA4C;EAC1F;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAQ,OAAO;EAAsC;EAWjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAgC;EACjF;GAAE,MAAM;GAAW,YAAY;GAAW,OAAO;EAAwC;EACzF;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GACE,MAAM;GACN,YAAY;GACZ,OAAO;EACT;EACA;GAAE,MAAM;GAAQ,YAAY;GAAW,OAAO;EAAsC;CACtF;CACA,iBAAiB;EAAE,MAAM;EAAW,YAAY;EAAQ,MAAM;EAAM,OAAO;CAAU;AACvF,CACF;;AAaA,SAAS,QAAQ,MAAiB;CAChC,IAAI,CAAC,eAAe,IAAI,GAAG,OAAO;CAClC,MAAM,OAAO,KAAK;CAClB,OAAO,OAAO,SAAS,WAAW,OAAQ,KAAK,eAAe,KAAK,QAAQ;AAC7E;AAEA,MAAa,QAAQ,WAAwC,SAAS,MACpE,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,MAAM,UAAU,GAAG,SAC/D,KACA;CACA,MAAM,UAAU,OAAwB,IAAI;CAC5C,MAAM,OAAO,OAAO,aAAa,YAAY,OAAO,aAAa,WAAW,OAAO,QAAQ,IAAI;CAC/F,MAAM,QAAQ,SAAS,KAAA,KAAa,SAAS,OAAO,KAAK,QAAQ,IAAI;CAErE,cAAc,SAAS,GAAG,QAAQ,GAAG,GAAG,OAAO;CAC/C,OACE,oBAAC,QAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;EAEzE,UAAA,oBAAC,QAAD;GAAM,WAAU;GACd,UAAA,qBAAC,QAAD;IAAM,KAAK;IAAS,WAAU;IAA9B,UAAA,CACG,QACC,oBAAC,UAAD;KAAU,SAAS;KAAO,QAAO;KAAQ,WAAU;KAChD,UAAA;IACO,CAAA,IACR,MACH,SAAS,OACR,WAEA,oBAAC,UAAD;KAAU,SAAS;KAAM,WAAU;KAChC,UAAA;IACO,CAAA,CAER;;EACF,CAAA;CACF,CAAA;AAEV,CAAC"}
|
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
import { BreadcrumbProps, BreadcrumbsProps } from "react-aria-components";
|
|
2
1
|
import { ReactNode } from "react";
|
|
2
|
+
import { BreadcrumbProps, BreadcrumbsProps } from "react-aria-components";
|
|
3
3
|
//#region src/breadcrumb/breadcrumb.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Breadcrumbs, ported from the legacy shadcn/Radix component onto React
|
|
6
|
-
* Aria's Breadcrumbs collection.
|
|
7
|
-
*
|
|
8
|
-
* SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's
|
|
9
|
-
* `Breadcrumbs` is a real collection, so it knows which item is last:
|
|
10
|
-
*
|
|
11
|
-
* | legacy | uses | fate |
|
|
12
|
-
* | ------------------- | ---- | ------------------------------------------- |
|
|
13
|
-
* | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |
|
|
14
|
-
* | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |
|
|
15
|
-
* | BreadcrumbItem | 6 | -> `BreadcrumbItem` |
|
|
16
|
-
* | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |
|
|
17
|
-
* | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |
|
|
18
|
-
* | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |
|
|
19
|
-
* | BreadcrumbEllipsis | 0 | dropped -- zero call sites |
|
|
20
|
-
*
|
|
21
|
-
* The collection is also why the separator CANNOT stay a component: RAC
|
|
22
|
-
* builds its item list from the children, so a `<BreadcrumbSeparator/>`
|
|
23
|
-
* sibling would be a non-item node inside a collection and there is nowhere
|
|
24
|
-
* legal to put it.
|
|
25
|
-
*
|
|
26
|
-
* Every string in the cva blocks below is a STATIC literal. Tailwind scans
|
|
27
|
-
* source text, so a class assembled at runtime is invisible to it and would
|
|
28
|
-
* simply never be emitted -- the component would render unstyled with no
|
|
29
|
-
* error anywhere.
|
|
30
|
-
*/
|
|
31
4
|
export declare const breadcrumbsVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
32
5
|
export declare const breadcrumbItemVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
33
6
|
export declare const breadcrumbLinkVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
@@ -67,7 +40,7 @@ interface BreadcrumbsProps$1 extends Omit<BreadcrumbsProps<object>, "className"
|
|
|
67
40
|
*/
|
|
68
41
|
"aria-label"?: string;
|
|
69
42
|
}
|
|
70
|
-
declare
|
|
43
|
+
declare const Breadcrumbs$1: import("react").ForwardRefExoticComponent<BreadcrumbsProps$1 & import("react").RefAttributes<HTMLOListElement>>;
|
|
71
44
|
/**
|
|
72
45
|
* One crumb.
|
|
73
46
|
*
|
|
@@ -92,7 +65,7 @@ export interface BreadcrumbItemProps extends Omit<BreadcrumbProps, "className" |
|
|
|
92
65
|
/** Anchor rel, forwarded when `href` is set. */
|
|
93
66
|
rel?: string;
|
|
94
67
|
}
|
|
95
|
-
export declare
|
|
68
|
+
export declare const BreadcrumbItem: import("react").ForwardRefExoticComponent<BreadcrumbItemProps & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
96
69
|
//#endregion
|
|
97
70
|
export { Breadcrumbs$1 as Breadcrumbs, BreadcrumbsProps$1 as BreadcrumbsProps };
|
|
98
71
|
//# sourceMappingURL=breadcrumb.d.ts.map
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { cn } from "../lib/cn.js";
|
|
2
|
+
import { cn, cnAll } from "../lib/cn.js";
|
|
3
|
+
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
|
+
import { useFlip } from "../lib/flip.js";
|
|
3
5
|
import { cva } from "class-variance-authority";
|
|
6
|
+
import { Children, createContext, forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
7
|
import { Breadcrumb, Breadcrumbs, Link } from "react-aria-components";
|
|
5
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
9
|
//#region src/breadcrumb/breadcrumb.tsx
|
|
@@ -31,6 +34,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
31
34
|
* simply never be emitted -- the component would render unstyled with no
|
|
32
35
|
* error anywhere.
|
|
33
36
|
*/
|
|
37
|
+
/** True once the first render is done: a crumb that mounts after that slides in; the ones that were there at first are still. */
|
|
38
|
+
const TrailReady = createContext(false);
|
|
34
39
|
const breadcrumbsVariants = cva([
|
|
35
40
|
"flex flex-wrap items-center gap-2",
|
|
36
41
|
"m-0 list-none p-0",
|
|
@@ -42,7 +47,7 @@ const breadcrumbLinkVariants = cva([
|
|
|
42
47
|
"inline-flex items-center gap-1.5",
|
|
43
48
|
"font-sans text-muted-foreground no-underline",
|
|
44
49
|
"rounded-sm cursor-pointer",
|
|
45
|
-
"motion-safe:transition-colors motion-safe:duration-(--duration-
|
|
50
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle",
|
|
46
51
|
"data-[hovered]:text-foreground data-[hovered]:underline",
|
|
47
52
|
"outline-none",
|
|
48
53
|
"data-[focus-visible]:outline-solid",
|
|
@@ -53,25 +58,37 @@ const breadcrumbLinkVariants = cva([
|
|
|
53
58
|
"[&_svg]:pointer-events-none [&_svg]:shrink-0",
|
|
54
59
|
"[&_svg:not([class*='size-'])]:size-(--icon-sm)"
|
|
55
60
|
]);
|
|
56
|
-
|
|
61
|
+
const Breadcrumbs$1 = forwardRef(function Breadcrumbs$2({ className, children, "aria-label": ariaLabel = "Breadcrumb", ...props }, ref) {
|
|
62
|
+
const list = useRef(null);
|
|
63
|
+
const refs = useMemo(() => mergeRefs(ref, list), [ref]);
|
|
64
|
+
const [ready, setReady] = useState(false);
|
|
65
|
+
useEffect(() => setReady(true), []);
|
|
66
|
+
useFlip(list, Children.toArray(children).map((child) => child.key).join("/"));
|
|
57
67
|
return /* @__PURE__ */ jsx("nav", {
|
|
58
68
|
"aria-label": ariaLabel,
|
|
59
69
|
"data-slot": "breadcrumb",
|
|
60
70
|
children: /* @__PURE__ */ jsx(Breadcrumbs, {
|
|
71
|
+
ref: refs,
|
|
61
72
|
...props,
|
|
62
73
|
"aria-label": ariaLabel,
|
|
63
74
|
"data-slot": "breadcrumb-list",
|
|
64
75
|
className: cn(breadcrumbsVariants(), className),
|
|
65
|
-
children
|
|
76
|
+
children: /* @__PURE__ */ jsx(TrailReady.Provider, {
|
|
77
|
+
value: ready,
|
|
78
|
+
children
|
|
79
|
+
})
|
|
66
80
|
})
|
|
67
81
|
});
|
|
68
|
-
}
|
|
69
|
-
function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }) {
|
|
82
|
+
});
|
|
83
|
+
const BreadcrumbItem = forwardRef(function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }, ref) {
|
|
84
|
+
const [arrivedLater] = useState(useContext(TrailReady));
|
|
70
85
|
return /* @__PURE__ */ jsxs(Breadcrumb, {
|
|
71
86
|
...props,
|
|
72
87
|
"data-slot": "breadcrumb-item",
|
|
73
|
-
|
|
88
|
+
"data-flip": "",
|
|
89
|
+
className: cnAll(breadcrumbItemVariants(), arrivedLater && "motion-safe:animate-crumb-in", className),
|
|
74
90
|
children: [/* @__PURE__ */ jsx(Link, {
|
|
91
|
+
ref,
|
|
75
92
|
href,
|
|
76
93
|
target,
|
|
77
94
|
rel,
|
|
@@ -86,7 +103,7 @@ function BreadcrumbItem({ className, children, href, target, rel, "aria-label":
|
|
|
86
103
|
children: "/"
|
|
87
104
|
})]
|
|
88
105
|
});
|
|
89
|
-
}
|
|
106
|
+
});
|
|
90
107
|
//#endregion
|
|
91
108
|
export { BreadcrumbItem, Breadcrumbs$1 as Breadcrumbs, breadcrumbItemVariants, breadcrumbLinkVariants, breadcrumbsVariants };
|
|
92
109
|
|