@arcfusionz/arc-primitive-ui 0.1.0 → 0.2.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/dist/components/AILoader/AILoader.d.ts +32 -0
- package/dist/components/AILoader/AILoader.js +157 -0
- package/dist/components/AILoader/index.d.ts +2 -0
- package/dist/components/AILoader/index.js +2 -0
- package/dist/components/Accordion/Accordion.d.ts +96 -0
- package/dist/components/Accordion/Accordion.js +149 -0
- package/dist/components/Accordion/index.d.ts +2 -0
- package/dist/components/Accordion/index.js +2 -0
- package/dist/components/Avatar/Avatar.d.ts +152 -0
- package/dist/components/Avatar/Avatar.js +216 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Avatar/index.js +2 -0
- package/dist/components/Badge/Badge.d.ts +87 -0
- package/dist/components/Badge/Badge.js +118 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/Badge/index.js +2 -0
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +109 -0
- package/dist/components/Breadcrumb/Breadcrumb.js +182 -0
- package/dist/components/Breadcrumb/index.d.ts +2 -0
- package/dist/components/Breadcrumb/index.js +2 -0
- package/dist/components/Button/Button.d.ts +57 -0
- package/dist/components/Button/Button.js +177 -0
- package/dist/components/Button/index.d.ts +2 -0
- package/dist/components/Button/index.js +2 -0
- package/dist/components/Calendar/Calendar.d.ts +140 -0
- package/dist/components/Calendar/Calendar.js +468 -0
- package/dist/components/Calendar/index.d.ts +2 -0
- package/dist/components/Calendar/index.js +2 -0
- package/dist/components/Checkbox/Checkbox.d.ts +60 -0
- package/dist/components/Checkbox/Checkbox.js +129 -0
- package/dist/components/Checkbox/index.d.ts +2 -0
- package/dist/components/Checkbox/index.js +2 -0
- package/dist/components/Combobox/Combobox.d.ts +251 -0
- package/dist/components/Combobox/Combobox.js +354 -0
- package/dist/components/Combobox/index.d.ts +2 -0
- package/dist/components/Combobox/index.js +2 -0
- package/dist/components/ContextMenu/ContextMenu.d.ts +129 -0
- package/dist/components/ContextMenu/ContextMenu.js +93 -0
- package/dist/components/ContextMenu/index.d.ts +2 -0
- package/dist/components/ContextMenu/index.js +2 -0
- package/dist/components/DatePicker/DatePicker.d.ts +109 -0
- package/dist/components/DatePicker/DatePicker.js +145 -0
- package/dist/components/DatePicker/index.d.ts +2 -0
- package/dist/components/DatePicker/index.js +2 -0
- package/dist/components/DateRangePicker/DateRangePicker.d.ts +107 -0
- package/dist/components/DateRangePicker/DateRangePicker.js +161 -0
- package/dist/components/DateRangePicker/index.d.ts +2 -0
- package/dist/components/DateRangePicker/index.js +2 -0
- package/dist/components/Dialog/Dialog.d.ts +112 -0
- package/dist/components/Dialog/Dialog.js +141 -0
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/components/Dialog/index.js +2 -0
- package/dist/components/Drawer/Drawer.d.ts +188 -0
- package/dist/components/Drawer/Drawer.js +268 -0
- package/dist/components/Drawer/index.d.ts +2 -0
- package/dist/components/Drawer/index.js +2 -0
- package/dist/components/Field/Field.d.ts +123 -0
- package/dist/components/Field/Field.js +241 -0
- package/dist/components/Field/FieldContext.d.ts +43 -0
- package/dist/components/Field/FieldContext.js +28 -0
- package/dist/components/Field/index.d.ts +3 -0
- package/dist/components/Field/index.js +3 -0
- package/dist/components/Fieldset/Fieldset.d.ts +43 -0
- package/dist/components/Fieldset/Fieldset.js +48 -0
- package/dist/components/Fieldset/index.d.ts +2 -0
- package/dist/components/Fieldset/index.js +2 -0
- package/dist/components/Form/Form.d.ts +54 -0
- package/dist/components/Form/Form.js +57 -0
- package/dist/components/Form/index.d.ts +2 -0
- package/dist/components/Form/index.js +2 -0
- package/dist/components/Input/Input.d.ts +64 -0
- package/dist/components/Input/Input.js +111 -0
- package/dist/components/Input/index.d.ts +2 -0
- package/dist/components/Input/index.js +2 -0
- package/dist/components/Menu/Menu.d.ts +212 -0
- package/dist/components/Menu/Menu.js +255 -0
- package/dist/components/Menu/index.d.ts +2 -0
- package/dist/components/Menu/index.js +2 -0
- package/dist/components/Popover/Popover.d.ts +122 -0
- package/dist/components/Popover/Popover.js +116 -0
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Popover/index.js +2 -0
- package/dist/components/Progress/Progress.d.ts +87 -0
- package/dist/components/Progress/Progress.js +73 -0
- package/dist/components/Progress/index.d.ts +2 -0
- package/dist/components/Progress/index.js +2 -0
- package/dist/components/Radio/Radio.d.ts +65 -0
- package/dist/components/Radio/Radio.js +93 -0
- package/dist/components/Radio/index.d.ts +2 -0
- package/dist/components/Radio/index.js +2 -0
- package/dist/components/Select/Select.d.ts +136 -0
- package/dist/components/Select/Select.js +219 -0
- package/dist/components/Select/index.d.ts +2 -0
- package/dist/components/Select/index.js +2 -0
- package/dist/components/Skeleton/Skeleton.d.ts +39 -0
- package/dist/components/Skeleton/Skeleton.js +44 -0
- package/dist/components/Skeleton/index.d.ts +2 -0
- package/dist/components/Skeleton/index.js +2 -0
- package/dist/components/Stepper/Stepper.d.ts +161 -0
- package/dist/components/Stepper/Stepper.js +344 -0
- package/dist/components/Stepper/index.d.ts +2 -0
- package/dist/components/Stepper/index.js +2 -0
- package/dist/components/Switch/Switch.d.ts +56 -0
- package/dist/components/Switch/Switch.js +87 -0
- package/dist/components/Switch/index.d.ts +2 -0
- package/dist/components/Switch/index.js +2 -0
- package/dist/components/Table/Table.d.ts +116 -0
- package/dist/components/Table/Table.js +143 -0
- package/dist/components/Table/index.d.ts +2 -0
- package/dist/components/Table/index.js +2 -0
- package/dist/components/Tabs/Tabs.d.ts +116 -0
- package/dist/components/Tabs/Tabs.js +158 -0
- package/dist/components/Tabs/index.d.ts +2 -0
- package/dist/components/Tabs/index.js +2 -0
- package/dist/components/Timeline/Timeline.d.ts +137 -0
- package/dist/components/Timeline/Timeline.js +243 -0
- package/dist/components/Timeline/index.d.ts +2 -0
- package/dist/components/Timeline/index.js +2 -0
- package/dist/components/Toast/Toast.d.ts +135 -0
- package/dist/components/Toast/Toast.js +314 -0
- package/dist/components/Toast/index.d.ts +2 -0
- package/dist/components/Toast/index.js +2 -0
- package/dist/components/Tooltip/Tooltip.d.ts +104 -0
- package/dist/components/Tooltip/Tooltip.js +95 -0
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/components/Tooltip/index.js +2 -0
- package/dist/components/Typography/Typography.d.ts +38 -0
- package/dist/components/Typography/Typography.js +53 -0
- package/dist/components/Typography/index.d.ts +2 -0
- package/dist/components/Typography/index.js +2 -0
- package/dist/index.d.ts +66 -3398
- package/dist/index.js +34 -5343
- package/dist/lib/cn.d.ts +6 -0
- package/dist/lib/cn.js +9 -0
- package/package.json +6 -2
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { Children, createContext, forwardRef, useContext, useMemo } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { Avatar } from "@base-ui/react/avatar";
|
|
6
|
+
//#region src/components/Avatar/Avatar.tsx
|
|
7
|
+
/** Set by AvatarGroup so member avatars inherit its size/shape and gain the separator ring. */
|
|
8
|
+
const AvatarGroupContext = createContext(null);
|
|
9
|
+
/** Resolved size of the nearest Avatar, so AvatarBadge can scale with it. */
|
|
10
|
+
const AvatarSizeContext = createContext(null);
|
|
11
|
+
const baseClasses = "relative inline-flex shrink-0 select-none items-center justify-center align-middle bg-muted font-sans font-medium text-slate-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&_svg]:pointer-events-none [&_svg]:shrink-0";
|
|
12
|
+
const sizeClasses = {
|
|
13
|
+
xs: "size-6 text-xs [&_svg:not([class*='size-'])]:size-3.5",
|
|
14
|
+
sm: "size-8 text-xs [&_svg:not([class*='size-'])]:size-4",
|
|
15
|
+
md: "size-10 text-sm [&_svg:not([class*='size-'])]:size-5",
|
|
16
|
+
lg: "size-12 text-base [&_svg:not([class*='size-'])]:size-6",
|
|
17
|
+
xl: "size-16 text-xl [&_svg:not([class*='size-'])]:size-8"
|
|
18
|
+
};
|
|
19
|
+
const shapeClasses = {
|
|
20
|
+
circle: "rounded-full",
|
|
21
|
+
square: "rounded-md"
|
|
22
|
+
};
|
|
23
|
+
const imageClasses = "size-full rounded-[inherit] object-cover motion-safe:transition-opacity motion-safe:duration-200 data-starting-style:opacity-0 data-ending-style:opacity-0";
|
|
24
|
+
const fallbackClasses = "flex size-full items-center justify-center rounded-[inherit]";
|
|
25
|
+
const badgeSizeClasses = {
|
|
26
|
+
xs: "size-1.5",
|
|
27
|
+
sm: "size-2",
|
|
28
|
+
md: "size-2.5",
|
|
29
|
+
lg: "size-3",
|
|
30
|
+
xl: "size-4"
|
|
31
|
+
};
|
|
32
|
+
const groupSpacingClasses = {
|
|
33
|
+
xs: "-space-x-1.5",
|
|
34
|
+
sm: "-space-x-2",
|
|
35
|
+
md: "-space-x-2.5",
|
|
36
|
+
lg: "-space-x-3",
|
|
37
|
+
xl: "-space-x-4"
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Class list for an element styled as an Avatar box.
|
|
41
|
+
*
|
|
42
|
+
* Use it when the box must stay a foreign element — e.g. a plain anchor
|
|
43
|
+
* around a raw `<img>`, or a static placeholder — so it still matches
|
|
44
|
+
* real avatars in size, shape and fill:
|
|
45
|
+
*
|
|
46
|
+
* <a href="/profile" className={avatarVariants({ size: "sm" })}>
|
|
47
|
+
* <img src={photo} alt="Open profile" className="size-full rounded-[inherit] object-cover" />
|
|
48
|
+
* </a>
|
|
49
|
+
*/
|
|
50
|
+
function avatarVariants({ size = "md", shape = "circle", className } = {}) {
|
|
51
|
+
return cn(baseClasses, sizeClasses[size], shapeClasses[shape], className);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* First code point of the first and last words — "Punyarit Thanyaboon" → "PT",
|
|
55
|
+
* "สมชาย ใจดี" → "สใ". Array.from splits by code point so surrogate-pair
|
|
56
|
+
* characters stay whole; uppercasing is a no-op for caseless scripts.
|
|
57
|
+
*/
|
|
58
|
+
function initialsFromName(name) {
|
|
59
|
+
const words = name.trim().split(/\s+/);
|
|
60
|
+
const first = Array.from(words[0] ?? "").slice(0, 1);
|
|
61
|
+
const last = words.length > 1 ? Array.from(words[words.length - 1] ?? "").slice(0, 1) : [];
|
|
62
|
+
return first.concat(last).join("").toUpperCase();
|
|
63
|
+
}
|
|
64
|
+
/** Generic person silhouette shown when neither an image nor a name is available. */
|
|
65
|
+
function PersonIcon(props) {
|
|
66
|
+
return /* @__PURE__ */ jsxs("svg", {
|
|
67
|
+
viewBox: "0 0 24 24",
|
|
68
|
+
fill: "none",
|
|
69
|
+
stroke: "currentColor",
|
|
70
|
+
strokeWidth: 2,
|
|
71
|
+
strokeLinecap: "round",
|
|
72
|
+
strokeLinejoin: "round",
|
|
73
|
+
"aria-hidden": "true",
|
|
74
|
+
...props,
|
|
75
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
76
|
+
cx: "12",
|
|
77
|
+
cy: "8",
|
|
78
|
+
r: "4"
|
|
79
|
+
}), /* @__PURE__ */ jsx("path", { d: "M4 21a8 8 0 0 1 16 0" })]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Profile picture with automatic fallback, built on Base UI's Avatar.
|
|
84
|
+
*
|
|
85
|
+
* Shorthand props (`src`, `alt`, `name`, `fallback`, `fallbackDelay`) render
|
|
86
|
+
* the image and fallback parts internally; `children` render after them, so
|
|
87
|
+
* an `AvatarBadge` composes on top of the shorthand. When only `children`
|
|
88
|
+
* are passed the root renders them alone — full manual composition with
|
|
89
|
+
* `AvatarImage` / `AvatarFallback` (don't mix that with the shorthand props,
|
|
90
|
+
* or two fallbacks render).
|
|
91
|
+
*
|
|
92
|
+
* Decorative by default. When it stands alone, pass `aria-label` — the root
|
|
93
|
+
* then gets `role="img"` and its internals are hidden from assistive
|
|
94
|
+
* technology, whether the image or the fallback is showing.
|
|
95
|
+
*/
|
|
96
|
+
const Avatar$1 = forwardRef(({ size, shape, src, alt, name, fallback, fallbackDelay, role, className, children, ...rest }, ref) => {
|
|
97
|
+
const group = useContext(AvatarGroupContext);
|
|
98
|
+
const resolvedSize = size ?? group?.size ?? "md";
|
|
99
|
+
const resolvedShape = shape ?? group?.shape ?? "circle";
|
|
100
|
+
const labelled = rest["aria-label"] != null || rest["aria-labelledby"] != null;
|
|
101
|
+
const useShorthand = src != null || name != null || fallback != null || children == null;
|
|
102
|
+
const initials = name == null ? "" : initialsFromName(name);
|
|
103
|
+
return /* @__PURE__ */ jsx(AvatarSizeContext.Provider, {
|
|
104
|
+
value: resolvedSize,
|
|
105
|
+
children: /* @__PURE__ */ jsxs(Avatar.Root, {
|
|
106
|
+
ref,
|
|
107
|
+
role: role ?? (labelled ? "img" : void 0),
|
|
108
|
+
className: cn(avatarVariants({
|
|
109
|
+
size: resolvedSize,
|
|
110
|
+
shape: resolvedShape
|
|
111
|
+
}), group != null && "ring-2 ring-background", className),
|
|
112
|
+
...rest,
|
|
113
|
+
children: [useShorthand && /* @__PURE__ */ jsxs(Fragment, { children: [src != null && /* @__PURE__ */ jsx(AvatarImage, {
|
|
114
|
+
src,
|
|
115
|
+
alt: alt ?? ""
|
|
116
|
+
}), /* @__PURE__ */ jsx(AvatarFallback, {
|
|
117
|
+
delay: src != null ? fallbackDelay : void 0,
|
|
118
|
+
children: fallback ?? (initials || /* @__PURE__ */ jsx(PersonIcon, {}))
|
|
119
|
+
})] }), children]
|
|
120
|
+
})
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
Avatar$1.displayName = "Avatar";
|
|
124
|
+
/**
|
|
125
|
+
* The avatar photo, cropped to fill the box. Base UI keeps it unmounted
|
|
126
|
+
* until the file has actually loaded (the fallback shows meanwhile), then it
|
|
127
|
+
* fades in motion-safe. Defaults to `alt=""` — see `Avatar`'s `alt` docs.
|
|
128
|
+
* `onLoadingStatusChange` reports `idle → loading → loaded | error`.
|
|
129
|
+
*/
|
|
130
|
+
const AvatarImage = forwardRef(({ className, alt = "", ...rest }, ref) => /* @__PURE__ */ jsx(Avatar.Image, {
|
|
131
|
+
ref,
|
|
132
|
+
alt,
|
|
133
|
+
className: cn(imageClasses, className),
|
|
134
|
+
...rest
|
|
135
|
+
}));
|
|
136
|
+
AvatarImage.displayName = "AvatarImage";
|
|
137
|
+
/**
|
|
138
|
+
* Shown while there is no loaded image: initials, an icon, or any content.
|
|
139
|
+
* Fills the box and inherits the root's muted fill and text styling — set
|
|
140
|
+
* colors via `className` on the root (or here) as a pair, e.g.
|
|
141
|
+
* `bg-primary-100 text-primary-800`. Base UI's `delay` prop postpones it to
|
|
142
|
+
* avoid a flash on fast image loads; only set a delay when an `AvatarImage`
|
|
143
|
+
* sibling exists, otherwise the box just sits empty for that long.
|
|
144
|
+
*/
|
|
145
|
+
const AvatarFallback = forwardRef(({ className, ...rest }, ref) => /* @__PURE__ */ jsx(Avatar.Fallback, {
|
|
146
|
+
ref,
|
|
147
|
+
className: cn(fallbackClasses, className),
|
|
148
|
+
...rest
|
|
149
|
+
}));
|
|
150
|
+
AvatarFallback.displayName = "AvatarFallback";
|
|
151
|
+
/**
|
|
152
|
+
* Corner indicator anchored to the avatar's bottom-right rim, scaled to the
|
|
153
|
+
* avatar's size, with a background-colored ring separating it from the
|
|
154
|
+
* photo. Defaults to the success fill (presence "online"); recolor via
|
|
155
|
+
* `className` — `bg-warning` away, `bg-destructive` busy, `bg-slate-400`
|
|
156
|
+
* offline — and match the ring to the page surface with `ring-*` when the
|
|
157
|
+
* avatar sits on a non-background fill. Compose inside `<Avatar>`, after the
|
|
158
|
+
* shorthand props or parts.
|
|
159
|
+
*/
|
|
160
|
+
const AvatarBadge = forwardRef(({ label, render, className, children, ...rest }, ref) => {
|
|
161
|
+
const size = useContext(AvatarSizeContext) ?? "md";
|
|
162
|
+
return useRender({
|
|
163
|
+
defaultTagName: "span",
|
|
164
|
+
render,
|
|
165
|
+
ref,
|
|
166
|
+
props: {
|
|
167
|
+
className: cn("absolute end-0 bottom-0 block rounded-full bg-success ring-2 ring-background", badgeSizeClasses[size], className),
|
|
168
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [label != null && /* @__PURE__ */ jsx("span", {
|
|
169
|
+
className: "sr-only",
|
|
170
|
+
children: label
|
|
171
|
+
}), children] }),
|
|
172
|
+
...rest
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
AvatarBadge.displayName = "AvatarBadge";
|
|
177
|
+
/**
|
|
178
|
+
* Overlapping stack of avatars ("facepile") with an optional "+N" overflow
|
|
179
|
+
* chip. Members inherit the group's `size`/`shape` and get a
|
|
180
|
+
* background-colored separator ring; later members overlap earlier ones
|
|
181
|
+
* (DOM order). For a custom overflow control — different text, or a chip
|
|
182
|
+
* that opens the full member list — omit `max` and append your own child,
|
|
183
|
+
* e.g. `<Avatar render={<button aria-label="Show all 12 members" />}>+9</Avatar>`.
|
|
184
|
+
* The ring assumes the group sits on `background` — on another surface,
|
|
185
|
+
* override per member (`className="ring-surface"`). Purely visual grouping:
|
|
186
|
+
* give the group an `aria-label` (e.g. "Contributors") when the stack is
|
|
187
|
+
* meaningful, and name each member if avatars are individually meaningful.
|
|
188
|
+
*/
|
|
189
|
+
const AvatarGroup = forwardRef(({ size = "md", shape = "circle", max, total, render, className, children, ...rest }, ref) => {
|
|
190
|
+
const contextValue = useMemo(() => ({
|
|
191
|
+
size,
|
|
192
|
+
shape
|
|
193
|
+
}), [size, shape]);
|
|
194
|
+
const items = Children.toArray(children);
|
|
195
|
+
const count = total ?? items.length;
|
|
196
|
+
const clampedMax = max === void 0 ? void 0 : Math.max(2, max);
|
|
197
|
+
const visible = clampedMax !== void 0 && count > clampedMax ? items.slice(0, clampedMax - 1) : items;
|
|
198
|
+
const surplus = count - visible.length;
|
|
199
|
+
const element = useRender({
|
|
200
|
+
defaultTagName: "div",
|
|
201
|
+
render,
|
|
202
|
+
ref,
|
|
203
|
+
props: {
|
|
204
|
+
className: cn("flex items-center", groupSpacingClasses[size], className),
|
|
205
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [visible, surplus > 0 && /* @__PURE__ */ jsx(Avatar$1, { children: `+${surplus}` })] }),
|
|
206
|
+
...rest
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
return /* @__PURE__ */ jsx(AvatarGroupContext.Provider, {
|
|
210
|
+
value: contextValue,
|
|
211
|
+
children: element
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
AvatarGroup.displayName = "AvatarGroup";
|
|
215
|
+
//#endregion
|
|
216
|
+
export { Avatar$1 as Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarImage, avatarVariants };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { Avatar, AvatarBadge, AvatarBadgeProps, AvatarFallback, AvatarFallbackProps, AvatarGroup, AvatarGroupProps, AvatarImage, AvatarImageProps, AvatarProps, AvatarShape, AvatarSize, AvatarVariantsOptions, avatarVariants } from "./Avatar.js";
|
|
2
|
+
export { Avatar, AvatarBadge, type AvatarBadgeProps, AvatarFallback, type AvatarFallbackProps, AvatarGroup, type AvatarGroupProps, AvatarImage, type AvatarImageProps, type AvatarProps, type AvatarShape, type AvatarSize, type AvatarVariantsOptions, avatarVariants };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, ReactNode } from "react";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
import { Button } from "@base-ui/react/button";
|
|
4
|
+
//#region src/components/Badge/Badge.d.ts
|
|
5
|
+
type BadgeVariant = "neutral" | "primary" | "success" | "warning" | "destructive";
|
|
6
|
+
type BadgeAppearance = "solid" | "soft" | "outline";
|
|
7
|
+
type BadgeSize = "sm" | "md" | "lg";
|
|
8
|
+
interface BadgeVariantsOptions {
|
|
9
|
+
variant?: BadgeVariant;
|
|
10
|
+
appearance?: BadgeAppearance;
|
|
11
|
+
size?: BadgeSize;
|
|
12
|
+
pill?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Class list for an element styled as a Badge.
|
|
17
|
+
*
|
|
18
|
+
* Use it to style a plain anchor as a clickable badge while keeping link
|
|
19
|
+
* semantics (alternatively pass `render={<a href … />}` to `Badge` itself —
|
|
20
|
+
* badges carry no role, so both are sound):
|
|
21
|
+
*
|
|
22
|
+
* <a href="/releases" className={badgeVariants({ variant: "primary" })}>v2.0</a>
|
|
23
|
+
*/
|
|
24
|
+
declare function badgeVariants({ variant, appearance, size, pill, className }?: BadgeVariantsOptions): string;
|
|
25
|
+
interface BadgeBaseProps extends Omit<ComponentPropsWithoutRef<"span">, "className" | "children"> {
|
|
26
|
+
/**
|
|
27
|
+
* Semantic intent. Drives the hue in every appearance. For informational
|
|
28
|
+
* statuses use `primary` — the palette's `info` alias shares the primary
|
|
29
|
+
* ramp, so a separate variant would render identically.
|
|
30
|
+
*/
|
|
31
|
+
variant?: BadgeVariant;
|
|
32
|
+
/** Emphasis level: `solid` fill, `soft` tint (default), or `outline`. */
|
|
33
|
+
appearance?: BadgeAppearance;
|
|
34
|
+
/** Badge height: `sm` 20px · `md` 24px · `lg` 28px. The badge body is non-interactive; the delete button expands its hit area to the WCAG 2.5.8 24px floor. If the badge is made clickable via `render`, provide enough surrounding spacing to reach 24px. */
|
|
35
|
+
size?: BadgeSize;
|
|
36
|
+
/** Fully rounded shape for tags and counts. min-width equals the height, so single-digit counts stay compact and centered. */
|
|
37
|
+
pill?: boolean;
|
|
38
|
+
/** Badge label; icons ride along as children and inherit sizing. Standalone dots omit children. */
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
/**
|
|
41
|
+
* Leading status dot (color inherits the label). Per WCAG 1.4.1 the dot
|
|
42
|
+
* alone must not carry meaning — keep a visible label where state matters.
|
|
43
|
+
* Without `children` the badge collapses to a bare circular indicator.
|
|
44
|
+
*/
|
|
45
|
+
dot?: boolean;
|
|
46
|
+
/** Called when the trailing delete button is activated with pointer, Enter, or Space. Requires `deleteLabel`. */
|
|
47
|
+
onDelete?: NonNullable<ComponentPropsWithoutRef<typeof Button>["onClick"]>;
|
|
48
|
+
/** Accessible name for the delete button, e.g. `Remove React filter`. Required when `onDelete` is set. */
|
|
49
|
+
deleteLabel?: string;
|
|
50
|
+
/** Decorative replacement for the default close icon. Only rendered when `onDelete` is set. */
|
|
51
|
+
deleteIcon?: ReactNode;
|
|
52
|
+
/** Replace the rendered `span`, e.g. `render={<a href="…" />}` for a clickable badge. Unavailable when `onDelete` is set, so interactive elements are never nested. */
|
|
53
|
+
render?: useRender.RenderProp;
|
|
54
|
+
/** Additional classes merged after the badge's variant classes. */
|
|
55
|
+
className?: string;
|
|
56
|
+
}
|
|
57
|
+
/** A badge with no visible label is a bare dot, so an accessible name is required. */
|
|
58
|
+
type BadgeContentProps = {
|
|
59
|
+
children: ReactNode;
|
|
60
|
+
} | {
|
|
61
|
+
children?: never;
|
|
62
|
+
dot: true;
|
|
63
|
+
"aria-label": string;
|
|
64
|
+
} | {
|
|
65
|
+
children?: never;
|
|
66
|
+
dot: true;
|
|
67
|
+
"aria-labelledby": string;
|
|
68
|
+
};
|
|
69
|
+
/** A static badge may use `render` for semantic composition, including links. */
|
|
70
|
+
type BadgeStaticProps = BadgeContentProps & {
|
|
71
|
+
render?: useRender.RenderProp;
|
|
72
|
+
onDelete?: never;
|
|
73
|
+
deleteLabel?: never;
|
|
74
|
+
deleteIcon?: never;
|
|
75
|
+
};
|
|
76
|
+
/** Deletable badges need visible content and a unique name for their delete button. */
|
|
77
|
+
type BadgeDeletableProps = {
|
|
78
|
+
children: ReactNode;
|
|
79
|
+
onDelete: NonNullable<ComponentPropsWithoutRef<typeof Button>["onClick"]>;
|
|
80
|
+
deleteLabel: string;
|
|
81
|
+
deleteIcon?: ReactNode;
|
|
82
|
+
render?: never;
|
|
83
|
+
};
|
|
84
|
+
type BadgeProps = BadgeBaseProps & (BadgeStaticProps | BadgeDeletableProps);
|
|
85
|
+
declare const Badge: import("react").ForwardRefExoticComponent<BadgeProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
86
|
+
//#endregion
|
|
87
|
+
export { Badge, BadgeAppearance, BadgeProps, BadgeSize, BadgeVariant, BadgeVariantsOptions, badgeVariants };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { cn } from "../../lib/cn.js";
|
|
2
|
+
import { forwardRef } from "react";
|
|
3
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { Button } from "@base-ui/react/button";
|
|
6
|
+
//#region src/components/Badge/Badge.tsx
|
|
7
|
+
const baseClasses = "inline-flex w-fit shrink-0 items-center justify-center whitespace-nowrap rounded-md border border-transparent font-sans font-medium tabular-nums transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring [&_svg]:pointer-events-none [&_svg]:shrink-0";
|
|
8
|
+
const appearanceClasses = {
|
|
9
|
+
solid: {
|
|
10
|
+
neutral: "bg-foreground text-background [a&]:hover:bg-slate-700 [button&]:hover:bg-slate-700",
|
|
11
|
+
primary: "bg-primary text-primary-foreground [a&]:hover:bg-primary-700 [button&]:hover:bg-primary-700",
|
|
12
|
+
success: "bg-success-700 text-success-foreground [a&]:hover:bg-success-800 [button&]:hover:bg-success-800",
|
|
13
|
+
warning: "bg-warning text-warning-950 [a&]:hover:bg-warning-500 [button&]:hover:bg-warning-500",
|
|
14
|
+
destructive: "bg-destructive text-destructive-foreground [a&]:hover:bg-error-700 [button&]:hover:bg-error-700"
|
|
15
|
+
},
|
|
16
|
+
soft: {
|
|
17
|
+
neutral: "bg-muted text-slate-700 [a&]:hover:bg-slate-200 [button&]:hover:bg-slate-200",
|
|
18
|
+
primary: "bg-primary-50 text-primary-700 [a&]:hover:bg-primary-100 [button&]:hover:bg-primary-100",
|
|
19
|
+
success: "bg-success-50 text-success-700 [a&]:hover:bg-success-100 [button&]:hover:bg-success-100",
|
|
20
|
+
warning: "bg-warning-50 text-warning-800 [a&]:hover:bg-warning-100 [button&]:hover:bg-warning-100",
|
|
21
|
+
destructive: "bg-error-50 text-error-700 [a&]:hover:bg-error-100 [button&]:hover:bg-error-100"
|
|
22
|
+
},
|
|
23
|
+
outline: {
|
|
24
|
+
neutral: "border-slate-400 text-slate-700 [a&]:hover:bg-muted [button&]:hover:bg-muted",
|
|
25
|
+
primary: "border-primary-400 text-primary-700 [a&]:hover:bg-primary-50 [button&]:hover:bg-primary-50",
|
|
26
|
+
success: "border-success-400 text-success-700 [a&]:hover:bg-success-50 [button&]:hover:bg-success-50",
|
|
27
|
+
warning: "border-warning-400 text-warning-800 [a&]:hover:bg-warning-50 [button&]:hover:bg-warning-50",
|
|
28
|
+
destructive: "border-error-400 text-error-700 [a&]:hover:bg-error-50 [button&]:hover:bg-error-50"
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
const sizeClasses = {
|
|
32
|
+
sm: "h-5 min-w-5 gap-1 px-1.5 text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
33
|
+
md: "h-6 min-w-6 gap-1 px-2 text-xs [&_svg:not([class*='size-'])]:size-3",
|
|
34
|
+
lg: "h-7 min-w-7 gap-1.5 px-2.5 text-sm [&_svg:not([class*='size-'])]:size-4"
|
|
35
|
+
};
|
|
36
|
+
const dotOnlyBaseClasses = "inline-block shrink-0 rounded-full";
|
|
37
|
+
const dotOnlySizeClasses = {
|
|
38
|
+
sm: "size-2",
|
|
39
|
+
md: "size-2.5",
|
|
40
|
+
lg: "size-3"
|
|
41
|
+
};
|
|
42
|
+
const dotOnlyFillClasses = {
|
|
43
|
+
neutral: "bg-slate-400",
|
|
44
|
+
primary: "bg-primary",
|
|
45
|
+
success: "bg-success",
|
|
46
|
+
warning: "bg-warning",
|
|
47
|
+
destructive: "bg-destructive"
|
|
48
|
+
};
|
|
49
|
+
const deleteButtonClasses = "relative -mr-1 inline-flex size-4 shrink-0 cursor-pointer items-center justify-center rounded-full text-current opacity-70 transition-colors duration-150 before:absolute before:-inset-1 hover:bg-current/10 hover:opacity-100 active:bg-current/20 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 data-disabled:pointer-events-none data-disabled:opacity-50";
|
|
50
|
+
function DeleteIcon() {
|
|
51
|
+
return /* @__PURE__ */ jsx("svg", {
|
|
52
|
+
viewBox: "0 0 24 24",
|
|
53
|
+
fill: "none",
|
|
54
|
+
stroke: "currentColor",
|
|
55
|
+
strokeWidth: 2.5,
|
|
56
|
+
"aria-hidden": "true",
|
|
57
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
58
|
+
d: "m6 6 12 12M18 6 6 18",
|
|
59
|
+
strokeLinecap: "round"
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Class list for an element styled as a Badge.
|
|
65
|
+
*
|
|
66
|
+
* Use it to style a plain anchor as a clickable badge while keeping link
|
|
67
|
+
* semantics (alternatively pass `render={<a href … />}` to `Badge` itself —
|
|
68
|
+
* badges carry no role, so both are sound):
|
|
69
|
+
*
|
|
70
|
+
* <a href="/releases" className={badgeVariants({ variant: "primary" })}>v2.0</a>
|
|
71
|
+
*/
|
|
72
|
+
function badgeVariants({ variant = "neutral", appearance = "soft", size = "md", pill = false, className } = {}) {
|
|
73
|
+
return cn(baseClasses, appearanceClasses[appearance][variant], sizeClasses[size], pill && "rounded-full", className);
|
|
74
|
+
}
|
|
75
|
+
const Badge = forwardRef(({ variant = "neutral", appearance = "soft", size = "md", pill = false, dot = false, className, children, render, onDelete, deleteLabel, deleteIcon, ...rest }, ref) => {
|
|
76
|
+
return useRender({
|
|
77
|
+
defaultTagName: "span",
|
|
78
|
+
render,
|
|
79
|
+
ref,
|
|
80
|
+
props: dot && children == null ? {
|
|
81
|
+
role: "img",
|
|
82
|
+
className: cn(dotOnlyBaseClasses, dotOnlySizeClasses[size], dotOnlyFillClasses[variant], className),
|
|
83
|
+
...rest
|
|
84
|
+
} : {
|
|
85
|
+
className: cn(badgeVariants({
|
|
86
|
+
variant,
|
|
87
|
+
appearance,
|
|
88
|
+
size,
|
|
89
|
+
pill
|
|
90
|
+
}), className),
|
|
91
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
92
|
+
dot && /* @__PURE__ */ jsx("span", {
|
|
93
|
+
"aria-hidden": "true",
|
|
94
|
+
className: "size-1.5 shrink-0 rounded-full bg-current"
|
|
95
|
+
}),
|
|
96
|
+
children,
|
|
97
|
+
onDelete && /* @__PURE__ */ jsx(Button, {
|
|
98
|
+
type: "button",
|
|
99
|
+
"aria-label": deleteLabel,
|
|
100
|
+
className: deleteButtonClasses,
|
|
101
|
+
onClick: (event) => {
|
|
102
|
+
event.stopPropagation();
|
|
103
|
+
onDelete(event);
|
|
104
|
+
},
|
|
105
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
106
|
+
"aria-hidden": "true",
|
|
107
|
+
className: "contents",
|
|
108
|
+
children: deleteIcon ?? /* @__PURE__ */ jsx(DeleteIcon, {})
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
] }),
|
|
112
|
+
...rest
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
Badge.displayName = "Badge";
|
|
117
|
+
//#endregion
|
|
118
|
+
export { Badge, badgeVariants };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef } from "react";
|
|
2
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
3
|
+
//#region src/components/Breadcrumb/Breadcrumb.d.ts
|
|
4
|
+
interface BreadcrumbProps extends Omit<ComponentPropsWithoutRef<"nav">, "className"> {
|
|
5
|
+
/** Replace the rendered `nav`. Preserve the navigation landmark so screen-reader users can jump to the trail. */
|
|
6
|
+
render?: useRender.RenderProp;
|
|
7
|
+
/** Additional classes merged after the root recipe. */
|
|
8
|
+
className?: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Navigation landmark that holds the trail. It defaults to
|
|
12
|
+
* `aria-label="Breadcrumb"`; pass a localized `aria-label` (or
|
|
13
|
+
* `aria-labelledby`) when the page language isn't English.
|
|
14
|
+
*/
|
|
15
|
+
declare const Breadcrumb: import("react").ForwardRefExoticComponent<BreadcrumbProps & import("react").RefAttributes<HTMLElement>>;
|
|
16
|
+
interface BreadcrumbListProps extends Omit<ComponentPropsWithoutRef<"ol">, "className"> {
|
|
17
|
+
/** Replace the rendered `ol`. Preserve list semantics so assistive technology announces the trail length. */
|
|
18
|
+
render?: useRender.RenderProp;
|
|
19
|
+
/** Additional classes merged after the list recipe, e.g. `text-xs` for a denser trail. */
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Ordered list of crumbs. It wraps onto new lines on narrow viewports; cap
|
|
24
|
+
* individual crumbs with a truncation `className` instead of letting the
|
|
25
|
+
* whole trail overflow.
|
|
26
|
+
*/
|
|
27
|
+
declare const BreadcrumbList: import("react").ForwardRefExoticComponent<BreadcrumbListProps & import("react").RefAttributes<HTMLOListElement>>;
|
|
28
|
+
interface BreadcrumbItemProps extends Omit<ComponentPropsWithoutRef<"li">, "className"> {
|
|
29
|
+
/** Replace the rendered `li`. Preserve list-item semantics. */
|
|
30
|
+
render?: useRender.RenderProp;
|
|
31
|
+
/** Additional classes merged after the item recipe. */
|
|
32
|
+
className?: string;
|
|
33
|
+
}
|
|
34
|
+
/** One entry in the trail: wraps a link, the current page, or a composed control. */
|
|
35
|
+
declare const BreadcrumbItem: import("react").ForwardRefExoticComponent<BreadcrumbItemProps & import("react").RefAttributes<HTMLLIElement>>;
|
|
36
|
+
interface BreadcrumbLinkVariantsOptions {
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Class list for an element styled as a breadcrumb link.
|
|
41
|
+
*
|
|
42
|
+
* Use it to give non-anchor crumbs the same resting/hover/focus treatment —
|
|
43
|
+
* e.g. a Menu trigger button that reveals collapsed levels:
|
|
44
|
+
*
|
|
45
|
+
* <MenuTrigger
|
|
46
|
+
* render={<button aria-label="Show hidden levels" className={breadcrumbLinkVariants()} />}
|
|
47
|
+
* >
|
|
48
|
+
* <BreadcrumbEllipsis />
|
|
49
|
+
* </MenuTrigger>
|
|
50
|
+
*/
|
|
51
|
+
declare function breadcrumbLinkVariants({ className }?: BreadcrumbLinkVariantsOptions): string;
|
|
52
|
+
interface BreadcrumbLinkProps extends Omit<ComponentPropsWithoutRef<"a">, "className"> {
|
|
53
|
+
/** Replace the rendered `a`, e.g. `render={<Link to="…" />}` for a client-side router. Keep it a real link so open-in-new-tab and copy-link work. */
|
|
54
|
+
render?: useRender.RenderProp;
|
|
55
|
+
/** Additional classes merged after the link recipe, e.g. `block max-w-40 truncate` to cap a long crumb. */
|
|
56
|
+
className?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Ancestor link in the trail. Inline text links are exempt from the WCAG
|
|
60
|
+
* 2.5.8 target-size floor, but keep the list's default gap so adjacent
|
|
61
|
+
* targets stay distinguishable on touch screens. For the APG variant where
|
|
62
|
+
* the current page stays a link, add `aria-current="page"` — the recipe
|
|
63
|
+
* restyles it to match `BreadcrumbPage`.
|
|
64
|
+
*/
|
|
65
|
+
declare const BreadcrumbLink: import("react").ForwardRefExoticComponent<BreadcrumbLinkProps & import("react").RefAttributes<HTMLAnchorElement>>;
|
|
66
|
+
interface BreadcrumbPageProps extends Omit<ComponentPropsWithoutRef<"span">, "className"> {
|
|
67
|
+
/** Replace the rendered `span`. Keep it non-interactive — NN/g: the current page must not look or behave like a link. */
|
|
68
|
+
render?: useRender.RenderProp;
|
|
69
|
+
/** Additional classes merged after the current-page recipe. */
|
|
70
|
+
className?: string;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The current page, rendered as plain text with `aria-current="page"` — an
|
|
74
|
+
* honest `span`, not a disabled fake link. Use `BreadcrumbLink` with
|
|
75
|
+
* `aria-current="page"` instead when the current page must stay navigable.
|
|
76
|
+
*/
|
|
77
|
+
declare const BreadcrumbPage: import("react").ForwardRefExoticComponent<BreadcrumbPageProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
78
|
+
interface BreadcrumbSeparatorProps extends Omit<ComponentPropsWithoutRef<"li">, "className"> {
|
|
79
|
+
/** Custom separator glyph, e.g. `/` or `·`. Defaults to a chevron. */
|
|
80
|
+
children?: ComponentPropsWithoutRef<"li">["children"];
|
|
81
|
+
/** Replace the rendered `li`. Keep it hidden from assistive technology — separators are purely visual. */
|
|
82
|
+
render?: useRender.RenderProp;
|
|
83
|
+
/** Additional classes merged after the separator recipe. */
|
|
84
|
+
className?: string;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Visual divider between crumbs. It is `aria-hidden` with
|
|
88
|
+
* `role="presentation"`, so screen readers hear only the crumbs and the
|
|
89
|
+
* list's item count — never the glyphs.
|
|
90
|
+
*/
|
|
91
|
+
declare const BreadcrumbSeparator: import("react").ForwardRefExoticComponent<BreadcrumbSeparatorProps & import("react").RefAttributes<HTMLLIElement>>;
|
|
92
|
+
interface BreadcrumbEllipsisProps extends Omit<ComponentPropsWithoutRef<"span">, "className"> {
|
|
93
|
+
/** Custom collapsed-levels glyph. Defaults to a horizontal ellipsis. */
|
|
94
|
+
children?: ComponentPropsWithoutRef<"span">["children"];
|
|
95
|
+
/** Replace the rendered `span`. Keep it decorative — an interactive wrapper owns the accessible name. */
|
|
96
|
+
render?: useRender.RenderProp;
|
|
97
|
+
/** Additional classes merged after the ellipsis recipe. */
|
|
98
|
+
className?: string;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Marker for collapsed levels in a deep trail. It is decorative
|
|
102
|
+
* (`aria-hidden`); when the hidden levels must stay reachable, wrap it in a
|
|
103
|
+
* labelled trigger — e.g. a Menu trigger styled with
|
|
104
|
+
* `breadcrumbLinkVariants()` — so the control, not the glyph, carries the
|
|
105
|
+
* accessible name.
|
|
106
|
+
*/
|
|
107
|
+
declare const BreadcrumbEllipsis: import("react").ForwardRefExoticComponent<BreadcrumbEllipsisProps & import("react").RefAttributes<HTMLSpanElement>>;
|
|
108
|
+
//#endregion
|
|
109
|
+
export { Breadcrumb, BreadcrumbEllipsis, BreadcrumbEllipsisProps, BreadcrumbItem, BreadcrumbItemProps, BreadcrumbLink, BreadcrumbLinkProps, BreadcrumbLinkVariantsOptions, BreadcrumbList, BreadcrumbListProps, BreadcrumbPage, BreadcrumbPageProps, BreadcrumbProps, BreadcrumbSeparator, BreadcrumbSeparatorProps, breadcrumbLinkVariants };
|