@seekalfred/primitives 0.5.1 → 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.js +10 -4
- 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 +15 -1
- package/dist/alert/alert.js +116 -20
- package/dist/alert/alert.js.map +1 -1
- package/dist/avatar/avatar.d.ts +6 -0
- package/dist/avatar/avatar.js +47 -16
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +11 -4
- package/dist/badge/badge.js +35 -5
- package/dist/badge/badge.js.map +1 -1
- package/dist/breadcrumb/breadcrumb.d.ts +0 -27
- package/dist/breadcrumb/breadcrumb.js +20 -6
- package/dist/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/button/button.d.ts +35 -22
- package/dist/button/button.js +177 -34
- 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 +1 -1
- package/dist/calendar/calendar.js +257 -30
- package/dist/calendar/calendar.js.map +1 -1
- package/dist/card/card.d.ts +9 -2
- package/dist/card/card.js +15 -4
- package/dist/card/card.js.map +1 -1
- package/dist/checkbox/checkbox.d.ts +0 -7
- package/dist/checkbox/checkbox.js +24 -27
- 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 +10 -14
- package/dist/dialog/dialog.js +39 -11
- 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 +22 -0
- package/dist/input/input.js +36 -12
- package/dist/input/input.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 +3 -3
- package/dist/lib/merge-refs.js.map +1 -1
- 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/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 +8 -0
- package/dist/popover/popover.js +3 -3
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/progress.js +46 -5
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/radio-group.js +7 -6
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.d.ts +10 -0
- package/dist/scroll-area/scroll-area.js +25 -3
- 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.js +79 -21
- package/dist/select/select.js.map +1 -1
- package/dist/sheet/sheet.js +2 -1
- 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 +6 -2
- package/dist/skeleton/skeleton.js.map +1 -1
- package/dist/slider/slider.js +61 -7
- package/dist/slider/slider.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.js +9 -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 +36 -5
- package/dist/table/table.js +112 -11
- package/dist/table/table.js.map +1 -1
- package/dist/tabs/tabs.js +142 -6
- package/dist/tabs/tabs.js.map +1 -1
- package/dist/textarea/textarea.d.ts +23 -0
- package/dist/textarea/textarea.js +24 -2
- 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.js +58 -38
- package/dist/toast/toast.js.map +1 -1
- package/dist/tooltip/tooltip.d.ts +1 -1
- package/dist/tooltip/tooltip.js +63 -14
- package/dist/tooltip/tooltip.js.map +1 -1
- package/package.json +83 -3
package/dist/avatar/avatar.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
|
+
import { SwapText } from "../lib/swap-text.js";
|
|
4
5
|
import { cva } from "class-variance-authority";
|
|
5
6
|
import { forwardRef, useCallback, useMemo, useState } from "react";
|
|
6
7
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -21,7 +22,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
21
22
|
* class assembled at runtime is invisible to it and would never be emitted.
|
|
22
23
|
*/
|
|
23
24
|
const avatarVariants = cva([
|
|
24
|
-
"relative inline-flex shrink-0 items-center justify-center
|
|
25
|
+
"relative inline-flex shrink-0 items-center justify-center",
|
|
25
26
|
"rounded-full",
|
|
26
27
|
"box-border",
|
|
27
28
|
"font-sans font-medium",
|
|
@@ -36,7 +37,15 @@ const avatarVariants = cva([
|
|
|
36
37
|
} },
|
|
37
38
|
defaultVariants: { size: "md" }
|
|
38
39
|
});
|
|
39
|
-
|
|
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) {
|
|
40
49
|
const [status, setStatus] = useState(src === void 0 ? "idle" : "loading");
|
|
41
50
|
/**
|
|
42
51
|
* Reset when src changes, during RENDER rather than in an effect.
|
|
@@ -63,6 +72,7 @@ const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback,
|
|
|
63
72
|
*/
|
|
64
73
|
const measure = useCallback((node) => {
|
|
65
74
|
if (node?.complete) setStatus(node.naturalWidth > 0 ? "loaded" : "error");
|
|
75
|
+
else if (node) node.dataset.loading = "";
|
|
66
76
|
}, []);
|
|
67
77
|
const imageRef = useMemo(() => mergeRefs(ref, measure), [ref, measure]);
|
|
68
78
|
const showImage = src !== void 0 && status !== "error";
|
|
@@ -71,20 +81,41 @@ const Avatar = forwardRef(function Avatar({ className, size, src, alt, fallback,
|
|
|
71
81
|
"data-slot": "avatar",
|
|
72
82
|
"data-status": status,
|
|
73
83
|
className: cn(avatarVariants({ size }), className),
|
|
74
|
-
children: [
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
+
]
|
|
88
119
|
});
|
|
89
120
|
});
|
|
90
121
|
//#endregion
|
|
@@ -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 {\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 { 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
|
|
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.
|
|
@@ -36,12 +36,19 @@ import { ComponentPropsWithoutRef } from "react";
|
|
|
36
36
|
* across every shadcn control.
|
|
37
37
|
*/
|
|
38
38
|
export declare const badgeVariants: (props?: ({
|
|
39
|
-
tone?: "neutral" | "success" | "warning" | "danger" | "info" |
|
|
40
|
-
appearance?: "
|
|
39
|
+
tone?: "primary" | "neutral" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
40
|
+
appearance?: "outline" | "solid" | "soft" | null | undefined;
|
|
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> {
|
|
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
|
+
}
|
|
45
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,8 +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 { forwardRef } from "react";
|
|
5
|
-
import { jsx } from "react/jsx-runtime";
|
|
6
|
+
import { forwardRef, isValidElement, useRef } from "react";
|
|
7
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
6
8
|
//#region src/badge/badge.tsx
|
|
7
9
|
/**
|
|
8
10
|
* Badge -- the status pill, in every shape the product asks for.
|
|
@@ -44,7 +46,8 @@ const badgeVariants = cva([
|
|
|
44
46
|
"font-sans font-medium",
|
|
45
47
|
"whitespace-nowrap",
|
|
46
48
|
"box-border",
|
|
47
|
-
"[
|
|
49
|
+
"[&_svg]:pointer-events-none [&_svg]:size-3",
|
|
50
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-standard) motion-safe:ease-settle"
|
|
48
51
|
], {
|
|
49
52
|
variants: {
|
|
50
53
|
/** What the badge MEANS. Colour follows from it; it is never picked directly. */
|
|
@@ -171,7 +174,17 @@ const badgeVariants = cva([
|
|
|
171
174
|
shape: "rounded"
|
|
172
175
|
}
|
|
173
176
|
});
|
|
174
|
-
|
|
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}`);
|
|
175
188
|
return /* @__PURE__ */ jsx("span", {
|
|
176
189
|
ref,
|
|
177
190
|
...props,
|
|
@@ -182,7 +195,24 @@ const Badge = forwardRef(function Badge({ className, tone, appearance, size, sha
|
|
|
182
195
|
appearance,
|
|
183
196
|
size,
|
|
184
197
|
shape
|
|
185
|
-
}), 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
|
+
})
|
|
186
216
|
});
|
|
187
217
|
});
|
|
188
218
|
//#endregion
|
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 { forwardRef, 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 const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, tone, appearance, size, shape, ...props },\n ref,\n) {\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 );\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,MAAa,QAAQ,WAAwC,SAAS,MACpE,EAAE,WAAW,MAAM,YAAY,MAAM,OAAO,GAAG,SAC/C,KACA;CACA,OACE,oBAAC,QAAD;EACO;EACL,GAAI;EACJ,aAAU;EACV,aAAW,QAAQ;EACnB,WAAW,GAAG,cAAc;GAAE;GAAM;GAAY;GAAM;EAAM,CAAC,GAAG,SAAS;CAC1E,CAAA;AAEL,CAAC"}
|
|
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
1
|
import { ReactNode } from "react";
|
|
2
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;
|
|
@@ -1,7 +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";
|
|
4
|
-
import { forwardRef } from "react";
|
|
6
|
+
import { Children, createContext, forwardRef, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
5
7
|
import { Breadcrumb, Breadcrumbs, Link } from "react-aria-components";
|
|
6
8
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
9
|
//#region src/breadcrumb/breadcrumb.tsx
|
|
@@ -32,6 +34,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
32
34
|
* simply never be emitted -- the component would render unstyled with no
|
|
33
35
|
* error anywhere.
|
|
34
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);
|
|
35
39
|
const breadcrumbsVariants = cva([
|
|
36
40
|
"flex flex-wrap items-center gap-2",
|
|
37
41
|
"m-0 list-none p-0",
|
|
@@ -43,7 +47,7 @@ const breadcrumbLinkVariants = cva([
|
|
|
43
47
|
"inline-flex items-center gap-1.5",
|
|
44
48
|
"font-sans text-muted-foreground no-underline",
|
|
45
49
|
"rounded-sm cursor-pointer",
|
|
46
|
-
"motion-safe:transition-colors motion-safe:duration-(--duration-
|
|
50
|
+
"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle",
|
|
47
51
|
"data-[hovered]:text-foreground data-[hovered]:underline",
|
|
48
52
|
"outline-none",
|
|
49
53
|
"data-[focus-visible]:outline-solid",
|
|
@@ -55,24 +59,34 @@ const breadcrumbLinkVariants = cva([
|
|
|
55
59
|
"[&_svg:not([class*='size-'])]:size-(--icon-sm)"
|
|
56
60
|
]);
|
|
57
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("/"));
|
|
58
67
|
return /* @__PURE__ */ jsx("nav", {
|
|
59
68
|
"aria-label": ariaLabel,
|
|
60
69
|
"data-slot": "breadcrumb",
|
|
61
70
|
children: /* @__PURE__ */ jsx(Breadcrumbs, {
|
|
62
|
-
ref,
|
|
71
|
+
ref: refs,
|
|
63
72
|
...props,
|
|
64
73
|
"aria-label": ariaLabel,
|
|
65
74
|
"data-slot": "breadcrumb-list",
|
|
66
75
|
className: cn(breadcrumbsVariants(), className),
|
|
67
|
-
children
|
|
76
|
+
children: /* @__PURE__ */ jsx(TrailReady.Provider, {
|
|
77
|
+
value: ready,
|
|
78
|
+
children
|
|
79
|
+
})
|
|
68
80
|
})
|
|
69
81
|
});
|
|
70
82
|
});
|
|
71
83
|
const BreadcrumbItem = forwardRef(function BreadcrumbItem({ className, children, href, target, rel, "aria-label": ariaLabel, ...props }, ref) {
|
|
84
|
+
const [arrivedLater] = useState(useContext(TrailReady));
|
|
72
85
|
return /* @__PURE__ */ jsxs(Breadcrumb, {
|
|
73
86
|
...props,
|
|
74
87
|
"data-slot": "breadcrumb-item",
|
|
75
|
-
|
|
88
|
+
"data-flip": "",
|
|
89
|
+
className: cnAll(breadcrumbItemVariants(), arrivedLater && "motion-safe:animate-crumb-in", className),
|
|
76
90
|
children: [/* @__PURE__ */ jsx(Link, {
|
|
77
91
|
ref,
|
|
78
92
|
href,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","names":["Breadcrumbs","AriaBreadcrumbs","AriaBreadcrumb","AriaLink"],"sources":["../../src/breadcrumb/breadcrumb.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport {\n Breadcrumb as AriaBreadcrumb,\n Breadcrumbs as AriaBreadcrumbs,\n Link as AriaLink,\n type BreadcrumbProps as AriaBreadcrumbProps,\n type BreadcrumbsProps as AriaBreadcrumbsProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\n\n/**\n * Breadcrumbs, ported from the legacy shadcn/Radix component onto React\n * Aria's Breadcrumbs collection.\n *\n * SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's\n * `Breadcrumbs` is a real collection, so it knows which item is last:\n *\n * | legacy | uses | fate |\n * | ------------------- | ---- | ------------------------------------------- |\n * | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |\n * | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |\n * | BreadcrumbItem | 6 | -> `BreadcrumbItem` |\n * | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |\n * | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |\n * | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |\n * | BreadcrumbEllipsis | 0 | dropped -- zero call sites |\n *\n * The collection is also why the separator CANNOT stay a component: RAC\n * builds its item list from the children, so a `<BreadcrumbSeparator/>`\n * sibling would be a non-item node inside a collection and there is nowhere\n * legal to put it.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted -- the component would render unstyled with no\n * error anywhere.\n */\nexport const breadcrumbsVariants = cva([\n \"flex flex-wrap items-center gap-2\",\n\n // The user-agent `<ol>`: decimal markers, a 40px inline padding and a 1em\n // block margin. The reset in @seekalfred/styles is OPT-IN, so a consumer\n // may load none -- unreset, the trail renders as a numbered, indented\n // list. The Accordion shipped its panel in the browser's serif for exactly\n // this family of reason.\n \"m-0 list-none p-0\",\n\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"font-sans text-sm leading-(--leading-normal) text-muted-foreground\",\n\n // box-border: content-box would add any padding a consumer sets on top of\n // a constrained width. The Alert shipped overflowing by 46px that way.\n \"box-border break-words\",\n]);\n\nexport const breadcrumbItemVariants = cva([\n // `group` is load-bearing: the separator hides itself from the current\n // crumb via group-data-[current], and RAC puts data-current on the <li>,\n // which CSS cannot look upward to from the span.\n \"group inline-flex items-center gap-2\",\n]);\n\nexport const breadcrumbLinkVariants = cva([\n \"inline-flex items-center gap-1.5\",\n\n // The user-agent `<a>`: -webkit-link blue and a permanent underline. Left\n // alone, every crumb is blue, and the hover underline below stops being a\n // state cue at all because it is already on.\n \"font-sans text-muted-foreground no-underline\",\n \"rounded-sm cursor-pointer\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-fast) motion-safe:ease-standard\",\n\n // Hover is NOT colour-only. The legacy component moved muted-foreground to\n // foreground and nothing else; under forced-colors the user's palette\n // overrides both and the cue vanishes entirely (WCAG 1.4.1, and\n // ARCHITECTURE section 5 rule 6). The underline survives any colour mode.\n \"data-[hovered]:text-foreground data-[hovered]:underline\",\n\n // outline-solid is load-bearing, not decoration. `outline-none` sets BOTH\n // outline-style:none and --tw-outline-style:none, and the width utility\n // resolves its style from that same variable -- so a focus variant can\n // apply a width and a colour while faithfully re-applying style:none and\n // painting nothing. Button shipped exactly that: keyboard-focusable with\n // no visible ring, and every gate stayed green because axe has no\n // focus-indicator rule.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n\n // The current crumb. RAC forces isDisabled on it, so React Aria renders a\n // <span role=\"link\" aria-disabled=\"true\" aria-current=\"page\"> with no\n // tabIndex -- byte for byte the legacy BreadcrumbPage, minus the chance of\n // hand-writing it onto the wrong item.\n \"data-[current]:text-foreground data-[current]:font-normal data-[current]:cursor-default\",\n\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\n/**\n * A navigation trail.\n *\n * The last child is the current page: give it no `href`, and React Aria\n * marks it `aria-current=\"page\"` and takes it out of the tab order.\n *\n * @example\n * <Breadcrumbs>\n * <BreadcrumbItem href=\"/\">Home</BreadcrumbItem>\n * <BreadcrumbItem href=\"/design-thinking\">Design Thinking</BreadcrumbItem>\n * <BreadcrumbItem>User Personas</BreadcrumbItem>\n * </Breadcrumbs>\n */\nexport interface BreadcrumbsProps\n extends Omit<\n AriaBreadcrumbsProps<object>,\n \"className\" | \"style\" | \"children\" | \"items\" | \"dependencies\"\n > {\n /**\n * `BreadcrumbItem` elements. Static children only: `items` and the render\n * function are not exposed, because no call site builds a trail from data\n * and the generic they require leaks into every consumer's types.\n */\n children?: ReactNode;\n /**\n * Extra classes, merged last so they win conflicts with ours.\n *\n * They land on the `<ol>`, where our own classes are, so `tailwind-merge`\n * can actually resolve a conflict. The `<nav>` is an unstyled landmark\n * wrapper.\n */\n className?: string;\n /**\n * Accessible name for the navigation landmark. Only worth changing when a\n * page has more than one trail.\n *\n * @default \"Breadcrumb\"\n */\n \"aria-label\"?: string;\n}\n\nexport const Breadcrumbs = forwardRef<HTMLOListElement, BreadcrumbsProps>(function Breadcrumbs(\n { className, children, \"aria-label\": ariaLabel = \"Breadcrumb\", ...props },\n ref,\n) {\n return (\n // RAC renders the <ol> and puts its aria-label there; it does NOT render\n // a landmark. Dropping the <nav> would silently remove the thing screen\n // reader users actually navigate by, so it is added back here. Both\n // carry the same name deliberately -- RAC falls back to a localized\n // \"Breadcrumbs\" when aria-label is absent, so the <ol> is named either\n // way and letting the two names diverge would be the worse outcome.\n <nav aria-label={ariaLabel} data-slot=\"breadcrumb\">\n <AriaBreadcrumbs\n // The ref goes on the <ol>, not the <nav>: the nav is this\n // component's OWN added landmark wrapper, not something React Aria\n // renders, so the real list element is one level in.\n ref={ref}\n {...props}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-list\"\n className={cn(breadcrumbsVariants(), className)}\n >\n {children}\n </AriaBreadcrumbs>\n </nav>\n );\n});\n\n/**\n * One crumb.\n *\n * @example\n * <BreadcrumbItem href=\"/reports\">Reports</BreadcrumbItem>\n */\nexport interface BreadcrumbItemProps\n extends Omit<AriaBreadcrumbProps, \"className\" | \"style\" | \"children\"> {\n /**\n * The crumb's label.\n *\n * An icon-only crumb has no accessible name, which is an axe `link-name`\n * violation -- both legacy call sites ship one. Pass `aria-label` when the\n * only child is a glyph.\n */\n children?: ReactNode;\n /** Extra classes for the `<li>`, merged last. */\n className?: string;\n /** Destination. Omit it on the last crumb: that one is the current page. */\n href?: string;\n /** Anchor target, forwarded when `href` is set. */\n target?: string;\n /** Anchor rel, forwarded when `href` is set. */\n rel?: string;\n}\n\nexport const BreadcrumbItem = forwardRef<HTMLAnchorElement, BreadcrumbItemProps>(\n function BreadcrumbItem(\n { className, children, href, target, rel, \"aria-label\": ariaLabel, ...props },\n ref,\n ) {\n return (\n <AriaBreadcrumb\n {...props}\n data-slot=\"breadcrumb-item\"\n className={cn(breadcrumbItemVariants(), className)}\n >\n <AriaLink\n // The ref goes on the anchor, not the <li> RAC wraps it in: the list\n // item is the collection's own item container, not the interactive\n // element -- the link is what a call site actually needs to focus.\n ref={ref}\n href={href}\n target={target}\n rel={rel}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-link\"\n className={breadcrumbLinkVariants()}\n >\n {children}\n </AriaLink>\n {/*\n A real element, not a ::after. Generated content is announced by some\n screen readers, and it cannot be read back from the DOM at all -- a\n separator that stopped rendering would be invisible to the suite.\n\n \"/\" rather than the legacy lucide ChevronRight: this library ships no\n icon dependency, all four legacy separators are the default, and a\n chevron glyph does not mirror in RTL while a slash needs no mirroring\n at all.\n */}\n <span\n aria-hidden=\"true\"\n data-slot=\"breadcrumb-separator\"\n className=\"shrink-0 select-none group-data-[current]:hidden\"\n >\n /\n </span>\n </AriaBreadcrumb>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,MAAa,sBAAsB,IAAI;CACrC;CAOA;CAIA;CAIA;AACF,CAAC;AAED,MAAa,yBAAyB,IAAI,CAIxC,sCACF,CAAC;AAED,MAAa,yBAAyB,IAAI;CACxC;CAKA;CACA;CACA;CAMA;CASA;CACA;CACA;CACA;CACA;CAMA;CAEA;CACA;AACF,CAAC;AA2CD,MAAaA,gBAAc,WAA+C,SAASA,cACjF,EAAE,WAAW,UAAU,cAAc,YAAY,cAAc,GAAG,SAClE,KACA;CACA,OAOE,oBAAC,OAAD;EAAK,cAAY;EAAW,aAAU;EACpC,UAAA,oBAACC,aAAD;GAIO;GACL,GAAI;GACJ,cAAY;GACZ,aAAU;GACV,WAAW,GAAG,oBAAoB,GAAG,SAAS;GAE7C;EACc,CAAA;CACd,CAAA;AAET,CAAC;AA4BD,MAAa,iBAAiB,WAC5B,SAAS,eACP,EAAE,WAAW,UAAU,MAAM,QAAQ,KAAK,cAAc,WAAW,GAAG,SACtE,KACA;CACA,OACE,qBAACC,YAAD;EACE,GAAI;EACJ,aAAU;EACV,WAAW,GAAG,uBAAuB,GAAG,SAAS;EAHnD,UAAA,CAKE,oBAACC,MAAD;GAIO;GACC;GACE;GACH;GACL,cAAY;GACZ,aAAU;GACV,WAAW,uBAAuB;GAEjC;EACO,CAAA,GAWV,oBAAC,QAAD;GACE,eAAY;GACZ,aAAU;GACV,WAAU;GACX,UAAA;EAEK,CAAA,CACQ;;AAEpB,CACF"}
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","names":["Breadcrumbs","AriaBreadcrumbs","AriaBreadcrumb","AriaLink"],"sources":["../../src/breadcrumb/breadcrumb.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport {\n Children,\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport {\n Breadcrumb as AriaBreadcrumb,\n Breadcrumbs as AriaBreadcrumbs,\n Link as AriaLink,\n type BreadcrumbProps as AriaBreadcrumbProps,\n type BreadcrumbsProps as AriaBreadcrumbsProps,\n} from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { useFlip } from \"../lib/flip.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\n\n/**\n * Breadcrumbs, ported from the legacy shadcn/Radix component onto React\n * Aria's Breadcrumbs collection.\n *\n * SIX LEGACY PARTS COLLAPSE TO TWO, and not for tidiness -- React Aria's\n * `Breadcrumbs` is a real collection, so it knows which item is last:\n *\n * | legacy | uses | fate |\n * | ------------------- | ---- | ------------------------------------------- |\n * | Breadcrumb | 2 | -> `Breadcrumbs` (nav + ol) |\n * | BreadcrumbList | 2 | dropped -- `Breadcrumbs` IS the `<ol>` |\n * | BreadcrumbItem | 6 | -> `BreadcrumbItem` |\n * | BreadcrumbLink | 4 | merged into `BreadcrumbItem` as `href` |\n * | BreadcrumbPage | 2 | dropped -- the last crumb is auto-current |\n * | BreadcrumbSeparator | 4 | dropped -- every non-last crumb draws one |\n * | BreadcrumbEllipsis | 0 | dropped -- zero call sites |\n *\n * The collection is also why the separator CANNOT stay a component: RAC\n * builds its item list from the children, so a `<BreadcrumbSeparator/>`\n * sibling would be a non-item node inside a collection and there is nowhere\n * legal to put it.\n *\n * Every string in the cva blocks below is a STATIC literal. Tailwind scans\n * source text, so a class assembled at runtime is invisible to it and would\n * simply never be emitted -- the component would render unstyled with no\n * error anywhere.\n */\n/** True once the first render is done: a crumb that mounts after that slides in; the ones that were there at first are still. */\nconst TrailReady = createContext(false);\n\nexport const breadcrumbsVariants = cva([\n \"flex flex-wrap items-center gap-2\",\n\n // The user-agent `<ol>`: decimal markers, a 40px inline padding and a 1em\n // block margin. The reset in @seekalfred/styles is OPT-IN, so a consumer\n // may load none -- unreset, the trail renders as a numbered, indented\n // list. The Accordion shipped its panel in the browser's serif for exactly\n // this family of reason.\n \"m-0 list-none p-0\",\n\n // leading-* AFTER text-*: tailwind-merge treats a later text-* as owning\n // line-height and silently strips a leading-* that precedes it.\n \"font-sans text-sm leading-(--leading-normal) text-muted-foreground\",\n\n // box-border: content-box would add any padding a consumer sets on top of\n // a constrained width. The Alert shipped overflowing by 46px that way.\n \"box-border break-words\",\n]);\n\nexport const breadcrumbItemVariants = cva([\n // `group` is load-bearing: the separator hides itself from the current\n // crumb via group-data-[current], and RAC puts data-current on the <li>,\n // which CSS cannot look upward to from the span.\n \"group inline-flex items-center gap-2\",\n]);\n\nexport const breadcrumbLinkVariants = cva([\n \"inline-flex items-center gap-1.5\",\n\n // The user-agent `<a>`: -webkit-link blue and a permanent underline. Left\n // alone, every crumb is blue, and the hover underline below stops being a\n // state cue at all because it is already on.\n \"font-sans text-muted-foreground no-underline\",\n \"rounded-sm cursor-pointer\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n\n // Hover is NOT colour-only. The legacy component moved muted-foreground to\n // foreground and nothing else; under forced-colors the user's palette\n // overrides both and the cue vanishes entirely (WCAG 1.4.1, and\n // ARCHITECTURE section 5 rule 6). The underline survives any colour mode.\n \"data-[hovered]:text-foreground data-[hovered]:underline\",\n\n // outline-solid is load-bearing, not decoration. `outline-none` sets BOTH\n // outline-style:none and --tw-outline-style:none, and the width utility\n // resolves its style from that same variable -- so a focus variant can\n // apply a width and a colour while faithfully re-applying style:none and\n // painting nothing. Button shipped exactly that: keyboard-focusable with\n // no visible ring, and every gate stayed green because axe has no\n // focus-indicator rule.\n \"outline-none\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n\n // The current crumb. RAC forces isDisabled on it, so React Aria renders a\n // <span role=\"link\" aria-disabled=\"true\" aria-current=\"page\"> with no\n // tabIndex -- byte for byte the legacy BreadcrumbPage, minus the chance of\n // hand-writing it onto the wrong item.\n \"data-[current]:text-foreground data-[current]:font-normal data-[current]:cursor-default\",\n\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n]);\n\n/**\n * A navigation trail.\n *\n * The last child is the current page: give it no `href`, and React Aria\n * marks it `aria-current=\"page\"` and takes it out of the tab order.\n *\n * @example\n * <Breadcrumbs>\n * <BreadcrumbItem href=\"/\">Home</BreadcrumbItem>\n * <BreadcrumbItem href=\"/design-thinking\">Design Thinking</BreadcrumbItem>\n * <BreadcrumbItem>User Personas</BreadcrumbItem>\n * </Breadcrumbs>\n */\nexport interface BreadcrumbsProps\n extends Omit<\n AriaBreadcrumbsProps<object>,\n \"className\" | \"style\" | \"children\" | \"items\" | \"dependencies\"\n > {\n /**\n * `BreadcrumbItem` elements. Static children only: `items` and the render\n * function are not exposed, because no call site builds a trail from data\n * and the generic they require leaks into every consumer's types.\n */\n children?: ReactNode;\n /**\n * Extra classes, merged last so they win conflicts with ours.\n *\n * They land on the `<ol>`, where our own classes are, so `tailwind-merge`\n * can actually resolve a conflict. The `<nav>` is an unstyled landmark\n * wrapper.\n */\n className?: string;\n /**\n * Accessible name for the navigation landmark. Only worth changing when a\n * page has more than one trail.\n *\n * @default \"Breadcrumb\"\n */\n \"aria-label\"?: string;\n}\n\nexport const Breadcrumbs = forwardRef<HTMLOListElement, BreadcrumbsProps>(function Breadcrumbs(\n { className, children, \"aria-label\": ariaLabel = \"Breadcrumb\", ...props },\n ref,\n) {\n const list = useRef<HTMLOListElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, list), [ref]);\n const [ready, setReady] = useState(false);\n useEffect(() => setReady(true), []);\n // When the trail changes, the crumbs that stay slide to their new place.\n useFlip(list, Children.toArray(children).map((child) => (child as { key?: string }).key).join(\"/\"));\n return (\n // RAC renders the <ol> and puts its aria-label there; it does NOT render\n // a landmark. Dropping the <nav> would silently remove the thing screen\n // reader users actually navigate by, so it is added back here. Both\n // carry the same name deliberately -- RAC falls back to a localized\n // \"Breadcrumbs\" when aria-label is absent, so the <ol> is named either\n // way and letting the two names diverge would be the worse outcome.\n <nav aria-label={ariaLabel} data-slot=\"breadcrumb\">\n <AriaBreadcrumbs\n // The ref goes on the <ol>, not the <nav>: the nav is this\n // component's OWN added landmark wrapper, not something React Aria\n // renders, so the real list element is one level in.\n ref={refs}\n {...props}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-list\"\n className={cn(breadcrumbsVariants(), className)}\n >\n <TrailReady.Provider value={ready}>{children}</TrailReady.Provider>\n </AriaBreadcrumbs>\n </nav>\n );\n});\n\n/**\n * One crumb.\n *\n * @example\n * <BreadcrumbItem href=\"/reports\">Reports</BreadcrumbItem>\n */\nexport interface BreadcrumbItemProps\n extends Omit<AriaBreadcrumbProps, \"className\" | \"style\" | \"children\"> {\n /**\n * The crumb's label.\n *\n * An icon-only crumb has no accessible name, which is an axe `link-name`\n * violation -- both legacy call sites ship one. Pass `aria-label` when the\n * only child is a glyph.\n */\n children?: ReactNode;\n /** Extra classes for the `<li>`, merged last. */\n className?: string;\n /** Destination. Omit it on the last crumb: that one is the current page. */\n href?: string;\n /** Anchor target, forwarded when `href` is set. */\n target?: string;\n /** Anchor rel, forwarded when `href` is set. */\n rel?: string;\n}\n\nexport const BreadcrumbItem = forwardRef<HTMLAnchorElement, BreadcrumbItemProps>(\n function BreadcrumbItem(\n { className, children, href, target, rel, \"aria-label\": ariaLabel, ...props },\n ref,\n ) {\n // Decided once, at this crumb's own mount.\n const [arrivedLater] = useState(useContext(TrailReady));\n return (\n <AriaBreadcrumb\n {...props}\n data-slot=\"breadcrumb-item\"\n data-flip=\"\"\n className={cnAll(\n breadcrumbItemVariants(),\n arrivedLater && \"motion-safe:animate-crumb-in\",\n className,\n )}\n >\n <AriaLink\n // The ref goes on the anchor, not the <li> RAC wraps it in: the list\n // item is the collection's own item container, not the interactive\n // element -- the link is what a call site actually needs to focus.\n ref={ref}\n href={href}\n target={target}\n rel={rel}\n aria-label={ariaLabel}\n data-slot=\"breadcrumb-link\"\n className={breadcrumbLinkVariants()}\n >\n {children}\n </AriaLink>\n {/*\n A real element, not a ::after. Generated content is announced by some\n screen readers, and it cannot be read back from the DOM at all -- a\n separator that stopped rendering would be invisible to the suite.\n\n \"/\" rather than the legacy lucide ChevronRight: this library ships no\n icon dependency, all four legacy separators are the default, and a\n chevron glyph does not mirror in RTL while a slash needs no mirroring\n at all.\n */}\n <span\n aria-hidden=\"true\"\n data-slot=\"breadcrumb-separator\"\n className=\"shrink-0 select-none group-data-[current]:hidden\"\n >\n /\n </span>\n </AriaBreadcrumb>\n );\n },\n);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsDA,MAAM,aAAa,cAAc,KAAK;AAEtC,MAAa,sBAAsB,IAAI;CACrC;CAOA;CAIA;CAIA;AACF,CAAC;AAED,MAAa,yBAAyB,IAAI,CAIxC,sCACF,CAAC;AAED,MAAa,yBAAyB,IAAI;CACxC;CAKA;CACA;CACA;CAMA;CASA;CACA;CACA;CACA;CACA;CAMA;CAEA;CACA;AACF,CAAC;AA2CD,MAAaA,gBAAc,WAA+C,SAASA,cACjF,EAAE,WAAW,UAAU,cAAc,YAAY,cAAc,GAAG,SAClE,KACA;CACA,MAAM,OAAO,OAAgC,IAAI;CACjD,MAAM,OAAO,cAAc,UAAU,KAAK,IAAI,GAAG,CAAC,GAAG,CAAC;CACtD,MAAM,CAAC,OAAO,YAAY,SAAS,KAAK;CACxC,gBAAgB,SAAS,IAAI,GAAG,CAAC,CAAC;CAElC,QAAQ,MAAM,SAAS,QAAQ,QAAQ,CAAC,CAAC,KAAK,UAAW,MAA2B,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC;CAClG,OAOE,oBAAC,OAAD;EAAK,cAAY;EAAW,aAAU;EACpC,UAAA,oBAACC,aAAD;GAIE,KAAK;GACL,GAAI;GACJ,cAAY;GACZ,aAAU;GACV,WAAW,GAAG,oBAAoB,GAAG,SAAS;GAE9C,UAAA,oBAAC,WAAW,UAAZ;IAAqB,OAAO;IAAQ;GAA8B,CAAA;EACnD,CAAA;CACd,CAAA;AAET,CAAC;AA4BD,MAAa,iBAAiB,WAC5B,SAAS,eACP,EAAE,WAAW,UAAU,MAAM,QAAQ,KAAK,cAAc,WAAW,GAAG,SACtE,KACA;CAEA,MAAM,CAAC,gBAAgB,SAAS,WAAW,UAAU,CAAC;CACtD,OACE,qBAACC,YAAD;EACE,GAAI;EACJ,aAAU;EACV,aAAU;EACV,WAAW,MACT,uBAAuB,GACvB,gBAAgB,gCAChB,SACF;EARF,UAAA,CAUE,oBAACC,MAAD;GAIO;GACC;GACE;GACH;GACL,cAAY;GACZ,aAAU;GACV,WAAW,uBAAuB;GAEjC;EACO,CAAA,GAWV,oBAAC,QAAD;GACE,eAAY;GACZ,aAAU;GACV,WAAU;GACX,UAAA;EAEK,CAAA,CACQ;;AAEpB,CACF"}
|
package/dist/button/button.d.ts
CHANGED
|
@@ -1,27 +1,9 @@
|
|
|
1
1
|
import { VariantProps } from "class-variance-authority";
|
|
2
2
|
import { ReactNode } from "react";
|
|
3
|
-
import { ButtonProps } from "react-aria-components";
|
|
3
|
+
import { ButtonProps, LinkProps } from "react-aria-components";
|
|
4
4
|
//#region src/button/button.d.ts
|
|
5
|
-
/**
|
|
6
|
-
* Variant classes, exported so a consumer can reuse the exact classes we ship
|
|
7
|
-
* rather than guessing at them. This is also the supported answer for the
|
|
8
|
-
* link-shaped cases React Aria's Button cannot cover: it never renders an
|
|
9
|
-
* anchor, so `<a className={buttonVariants({ variant: "outline" })}>` is how a
|
|
10
|
-
* real navigation target gets button styling until the Link primitive lands.
|
|
11
|
-
*
|
|
12
|
-
* Ported from the legacy shadcn Button. The variant and size vocabulary is
|
|
13
|
-
* deliberately identical so existing call sites migrate by rename, with six
|
|
14
|
-
* corrections that the Alfred contracts require -- each marked below.
|
|
15
|
-
*
|
|
16
|
-
* Every string here is a STATIC literal. Tailwind scans source text, so a class
|
|
17
|
-
* assembled at runtime is invisible to it and would simply never be emitted.
|
|
18
|
-
*
|
|
19
|
-
* State comes from React Aria data attributes rather than pseudo-classes:
|
|
20
|
-
* data-[focus-visible] is keyboard focus only, and data-[pressed] covers mouse,
|
|
21
|
-
* touch and keyboard uniformly. The original had no pressed state at all.
|
|
22
|
-
*/
|
|
23
5
|
export declare const buttonVariants: (props?: ({
|
|
24
|
-
variant?: "
|
|
6
|
+
variant?: "link" | "destructive" | "primary" | "secondary" | "outline" | "outlineNeutral" | "ghost" | null | undefined;
|
|
25
7
|
size?: "sm" | "md" | "lg" | "icon" | null | undefined;
|
|
26
8
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
27
9
|
/**
|
|
@@ -30,8 +12,8 @@ export declare const buttonVariants: (props?: ({
|
|
|
30
12
|
* CORRECTION 6 -- the legacy Button was a plain `<button>`, so it took
|
|
31
13
|
* `onClick`, `disabled` and Radix's `asChild`. React Aria's Button has none of
|
|
32
14
|
* those: `ButtonProps` explicitly omits `onClick` and `href`. The replacements
|
|
33
|
-
* are `onPress`, `isDisabled`, and `
|
|
34
|
-
*
|
|
15
|
+
* are `onPress`, `isDisabled`, and `href` -- which renders React Aria's Link in
|
|
16
|
+
* place of its Button. See the migration table in the package README.
|
|
35
17
|
*
|
|
36
18
|
* Deliberately NOT intersected with ComponentPropsWithoutRef<"button">: that
|
|
37
19
|
* would advertise disabled, tabIndex and role, which React Aria does not
|
|
@@ -59,10 +41,40 @@ interface ButtonProps$1 extends Omit<ButtonProps, "className" | "style">, Varian
|
|
|
59
41
|
startIcon?: ReactNode;
|
|
60
42
|
/** Icon rendered after the label. Same rules as {@link ButtonProps.startIcon}. */
|
|
61
43
|
endIcon?: ReactNode;
|
|
44
|
+
/**
|
|
45
|
+
* Makes the button a link. It renders React Aria's `Link` -- a real `<a>`,
|
|
46
|
+
* announced as a link -- with exactly the classes a button of the same
|
|
47
|
+
* `variant` and `size` gets, and the same hover, press and focus-ring states.
|
|
48
|
+
*
|
|
49
|
+
* Use it whenever activating the control navigates. A button that navigates
|
|
50
|
+
* cannot be opened in a new tab, is announced as the wrong role, and is
|
|
51
|
+
* skipped by a screen reader's list of links.
|
|
52
|
+
*
|
|
53
|
+
* Client-side routing needs React Aria's `RouterProvider` mounted once above
|
|
54
|
+
* it; without one, an `href` is an ordinary page load, which is also what an
|
|
55
|
+
* external or download link wants.
|
|
56
|
+
*
|
|
57
|
+
* Button-only props have nothing to act on here: `type`, `form*` and
|
|
58
|
+
* `isPending` are ignored, and the `ref` receives the `<a>`.
|
|
59
|
+
*/
|
|
60
|
+
href?: LinkProps["href"];
|
|
61
|
+
/** With `href` only: where to open it, e.g. `"_blank"`. */
|
|
62
|
+
target?: LinkProps["target"];
|
|
63
|
+
/** With `href` only. Give `"noreferrer"` alongside `target="_blank"` to an external site. */
|
|
64
|
+
rel?: LinkProps["rel"];
|
|
65
|
+
/** With `href` only: download the target instead of opening it, optionally under this file name. */
|
|
66
|
+
download?: LinkProps["download"];
|
|
62
67
|
}
|
|
63
68
|
/**
|
|
64
69
|
* A button.
|
|
65
70
|
*
|
|
71
|
+
* A label that changes (a different text, or a different icon) crossfades, and
|
|
72
|
+
* the button's width springs to fit it. While that runs, the OUTGOING label is
|
|
73
|
+
* still in the DOM for about 200ms, `aria-hidden` and out of flow, so read a
|
|
74
|
+
* button's text after the change settles, and do not put `id`s, inputs or
|
|
75
|
+
* anything stateful inside a label that changes. Text that changes constantly,
|
|
76
|
+
* such as a live counter, crossfades every time it does.
|
|
77
|
+
*
|
|
66
78
|
* `size="icon"` renders a square button with no visible text, so it **must**
|
|
67
79
|
* carry an `aria-label` -- otherwise it has no accessible name and fails axe's
|
|
68
80
|
* `button-name` rule. The IconOnly story is the guard on that.
|
|
@@ -72,6 +84,7 @@ interface ButtonProps$1 extends Omit<ButtonProps, "className" | "style">, Varian
|
|
|
72
84
|
* <Button startIcon={<SaveIcon aria-hidden />}>Save changes</Button>
|
|
73
85
|
* <Button endIcon={<ChevronIcon aria-hidden />}>Continue</Button>
|
|
74
86
|
* <Button variant="outline" size="icon" aria-label="Close"><XIcon /></Button>
|
|
87
|
+
* <Button href="/reports" variant="outlineNeutral">View reports</Button>
|
|
75
88
|
*/
|
|
76
89
|
declare const Button$1: import("react").ForwardRefExoticComponent<ButtonProps$1 & import("react").RefAttributes<HTMLButtonElement>>;
|
|
77
90
|
//#endregion
|