@craftzbay/ui 0.9.0 → 0.10.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 +72 -6
- package/dist-lib/components/ui/Accordion.js +60 -0
- package/dist-lib/components/ui/Accordion.js.map +1 -0
- package/dist-lib/components/ui/Alert.d.ts +1 -1
- package/dist-lib/components/ui/Alert.js +66 -0
- package/dist-lib/components/ui/Alert.js.map +1 -0
- package/dist-lib/components/ui/Avatar.d.ts +2 -2
- package/dist-lib/components/ui/Avatar.js +96 -0
- package/dist-lib/components/ui/Avatar.js.map +1 -0
- package/dist-lib/components/ui/Badge.d.ts +1 -1
- package/dist-lib/components/ui/Badge.js +67 -0
- package/dist-lib/components/ui/Badge.js.map +1 -0
- package/dist-lib/components/ui/Breadcrumbs.js +34 -0
- package/dist-lib/components/ui/Breadcrumbs.js.map +1 -0
- package/dist-lib/components/ui/Button.d.ts +1 -1
- package/dist-lib/components/ui/Button.js +117 -0
- package/dist-lib/components/ui/Button.js.map +1 -0
- package/dist-lib/components/ui/Calendar.d.ts +7 -1
- package/dist-lib/components/ui/Calendar.js +111 -0
- package/dist-lib/components/ui/Calendar.js.map +1 -0
- package/dist-lib/components/ui/Card.d.ts +1 -1
- package/dist-lib/components/ui/Card.js +116 -0
- package/dist-lib/components/ui/Card.js.map +1 -0
- package/dist-lib/components/ui/Carousel.d.ts +5 -1
- package/dist-lib/components/ui/Carousel.js +143 -0
- package/dist-lib/components/ui/Carousel.js.map +1 -0
- package/dist-lib/components/ui/Chart.d.ts +26 -4
- package/dist-lib/components/ui/Chart.js +253 -0
- package/dist-lib/components/ui/Chart.js.map +1 -0
- package/dist-lib/components/ui/Checkbox.js +56 -0
- package/dist-lib/components/ui/Checkbox.js.map +1 -0
- package/dist-lib/components/ui/Combobox.d.ts +7 -0
- package/dist-lib/components/ui/Combobox.js +164 -0
- package/dist-lib/components/ui/Combobox.js.map +1 -0
- package/dist-lib/components/ui/CommandPalette.d.ts +3 -3
- package/dist-lib/components/ui/CommandPalette.js +174 -0
- package/dist-lib/components/ui/CommandPalette.js.map +1 -0
- package/dist-lib/components/ui/ContextMenu.js +150 -0
- package/dist-lib/components/ui/ContextMenu.js.map +1 -0
- package/dist-lib/components/ui/DataGrid.js +84 -0
- package/dist-lib/components/ui/DataGrid.js.map +1 -0
- package/dist-lib/components/ui/DatePicker.d.ts +16 -13
- package/dist-lib/components/ui/DatePicker.js +174 -0
- package/dist-lib/components/ui/DatePicker.js.map +1 -0
- package/dist-lib/components/ui/DesignSystemProvider.d.ts +8 -1
- package/dist-lib/components/ui/DesignSystemProvider.js +79 -0
- package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -0
- package/dist-lib/components/ui/Dialog.d.ts +1 -1
- package/dist-lib/components/ui/Dialog.js +140 -0
- package/dist-lib/components/ui/Dialog.js.map +1 -0
- package/dist-lib/components/ui/Drawer.d.ts +6 -0
- package/dist-lib/components/ui/Drawer.js +98 -0
- package/dist-lib/components/ui/Drawer.js.map +1 -0
- package/dist-lib/components/ui/DropdownMenu.js +164 -0
- package/dist-lib/components/ui/DropdownMenu.js.map +1 -0
- package/dist-lib/components/ui/EmptyState.js +32 -0
- package/dist-lib/components/ui/EmptyState.js.map +1 -0
- package/dist-lib/components/ui/ErrorState.js +48 -0
- package/dist-lib/components/ui/ErrorState.js.map +1 -0
- package/dist-lib/components/ui/FileUpload.d.ts +3 -0
- package/dist-lib/components/ui/FileUpload.js +108 -0
- package/dist-lib/components/ui/FileUpload.js.map +1 -0
- package/dist-lib/components/ui/Form.d.ts +1 -0
- package/dist-lib/components/ui/Form.js +105 -0
- package/dist-lib/components/ui/Form.js.map +1 -0
- package/dist-lib/components/ui/Icon.js +26 -0
- package/dist-lib/components/ui/Icon.js.map +1 -0
- package/dist-lib/components/ui/IconButton.d.ts +1 -1
- package/dist-lib/components/ui/IconButton.js +56 -0
- package/dist-lib/components/ui/IconButton.js.map +1 -0
- package/dist-lib/components/ui/Input.d.ts +6 -3
- package/dist-lib/components/ui/Input.js +135 -0
- package/dist-lib/components/ui/Input.js.map +1 -0
- package/dist-lib/components/ui/Kbd.js +24 -0
- package/dist-lib/components/ui/Kbd.js.map +1 -0
- package/dist-lib/components/ui/MultiSelect.d.ts +3 -1
- package/dist-lib/components/ui/MultiSelect.js +196 -0
- package/dist-lib/components/ui/MultiSelect.js.map +1 -0
- package/dist-lib/components/ui/Pagination.d.ts +23 -0
- package/dist-lib/components/ui/Pagination.js +148 -0
- package/dist-lib/components/ui/Pagination.js.map +1 -0
- package/dist-lib/components/ui/Popover.js +35 -0
- package/dist-lib/components/ui/Popover.js.map +1 -0
- package/dist-lib/components/ui/Progress.js +95 -0
- package/dist-lib/components/ui/Progress.js.map +1 -0
- package/dist-lib/components/ui/RadioGroup.js +64 -0
- package/dist-lib/components/ui/RadioGroup.js.map +1 -0
- package/dist-lib/components/ui/ScrollArea.js +44 -0
- package/dist-lib/components/ui/ScrollArea.js.map +1 -0
- package/dist-lib/components/ui/Select.js +131 -0
- package/dist-lib/components/ui/Select.js.map +1 -0
- package/dist-lib/components/ui/Separator.js +26 -0
- package/dist-lib/components/ui/Separator.js.map +1 -0
- package/dist-lib/components/ui/Sheet.d.ts +1 -1
- package/dist-lib/components/ui/Sheet.js +113 -0
- package/dist-lib/components/ui/Sheet.js.map +1 -0
- package/dist-lib/components/ui/Sidebar.d.ts +18 -6
- package/dist-lib/components/ui/Sidebar.js +169 -0
- package/dist-lib/components/ui/Sidebar.js.map +1 -0
- package/dist-lib/components/ui/Skeleton.js +28 -0
- package/dist-lib/components/ui/Skeleton.js.map +1 -0
- package/dist-lib/components/ui/Slider.js +52 -0
- package/dist-lib/components/ui/Slider.js.map +1 -0
- package/dist-lib/components/ui/Snackbar.d.ts +1 -1
- package/dist-lib/components/ui/Snackbar.js +56 -0
- package/dist-lib/components/ui/Snackbar.js.map +1 -0
- package/dist-lib/components/ui/Spinner.js +42 -0
- package/dist-lib/components/ui/Spinner.js.map +1 -0
- package/dist-lib/components/ui/Stepper.js +88 -0
- package/dist-lib/components/ui/Stepper.js.map +1 -0
- package/dist-lib/components/ui/Switch.js +77 -0
- package/dist-lib/components/ui/Switch.js.map +1 -0
- package/dist-lib/components/ui/Table.d.ts +9 -1
- package/dist-lib/components/ui/Table.js +157 -0
- package/dist-lib/components/ui/Table.js.map +1 -0
- package/dist-lib/components/ui/Tabs.d.ts +1 -1
- package/dist-lib/components/ui/Tabs.js +89 -0
- package/dist-lib/components/ui/Tabs.js.map +1 -0
- package/dist-lib/components/ui/TagInput.d.ts +17 -2
- package/dist-lib/components/ui/TagInput.js +111 -0
- package/dist-lib/components/ui/TagInput.js.map +1 -0
- package/dist-lib/components/ui/Textarea.js +77 -0
- package/dist-lib/components/ui/Textarea.js.map +1 -0
- package/dist-lib/components/ui/Timeline.d.ts +16 -6
- package/dist-lib/components/ui/Timeline.js +52 -0
- package/dist-lib/components/ui/Timeline.js.map +1 -0
- package/dist-lib/components/ui/Toast.d.ts +1 -1
- package/dist-lib/components/ui/Toast.js +171 -0
- package/dist-lib/components/ui/Toast.js.map +1 -0
- package/dist-lib/components/ui/Tooltip.js +58 -0
- package/dist-lib/components/ui/Tooltip.js.map +1 -0
- package/dist-lib/components/ui/TopNav.js +52 -0
- package/dist-lib/components/ui/TopNav.js.map +1 -0
- package/dist-lib/components/ui/Tree.d.ts +27 -5
- package/dist-lib/components/ui/Tree.js +167 -0
- package/dist-lib/components/ui/Tree.js.map +1 -0
- package/dist-lib/hooks/use-field-ids.d.ts +29 -0
- package/dist-lib/hooks/use-field-ids.js +18 -0
- package/dist-lib/hooks/use-field-ids.js.map +1 -0
- package/dist-lib/hooks/use-media-query.js +18 -0
- package/dist-lib/hooks/use-media-query.js.map +1 -0
- package/dist-lib/hooks/use-strings.d.ts +5 -0
- package/dist-lib/hooks/use-strings.js +12 -0
- package/dist-lib/hooks/use-strings.js.map +1 -0
- package/dist-lib/hooks/use-toast.js +36 -0
- package/dist-lib/hooks/use-toast.js.map +1 -0
- package/dist-lib/icon.d.ts +8 -0
- package/dist-lib/icon.js +7 -0
- package/dist-lib/icon.js.map +1 -0
- package/dist-lib/icons/index.d.ts +71 -88
- package/dist-lib/icons/index.js +145 -0
- package/dist-lib/icons/index.js.map +1 -0
- package/dist-lib/illustrations/index.js +105 -0
- package/dist-lib/illustrations/index.js.map +1 -0
- package/dist-lib/index.d.ts +62 -59
- package/dist-lib/index.js +267 -4529
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/lib/strings.d.ts +129 -0
- package/dist-lib/lib/strings.js +85 -0
- package/dist-lib/lib/strings.js.map +1 -0
- package/dist-lib/lib/strings.mn.d.ts +3 -0
- package/dist-lib/lib/strings.mn.js +70 -0
- package/dist-lib/lib/strings.mn.js.map +1 -0
- package/dist-lib/lib/utils.d.ts +0 -1
- package/dist-lib/lib/utils.js +10 -0
- package/dist-lib/lib/utils.js.map +1 -0
- package/dist-lib/styles.css +1 -1
- package/dist-lib/theme.css +777 -0
- package/package.json +31 -8
- package/dist-lib/index.cjs +0 -8
- package/dist-lib/index.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -15,19 +15,77 @@ primitives — Button through DataGrid — plus composed patterns
|
|
|
15
15
|
## Install
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
pnpm add @craftzbay/ui
|
|
18
|
+
pnpm add @craftzbay/ui # peers: react ^18 || ^19, react-dom ^18 || ^19
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
+
**1. Fonts** — Geist + Geist Mono are referenced by the tokens but not bundled. Add to `<head>`:
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
25
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
26
|
+
<link
|
|
27
|
+
href="https://fonts.googleapis.com/css2?family=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap"
|
|
28
|
+
rel="stylesheet"
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**2. CSS** — pick one:
|
|
33
|
+
|
|
34
|
+
```css
|
|
35
|
+
/* a) No Tailwind in your app: one precompiled sheet (tokens + base + every utility the components use) */
|
|
36
|
+
@import '@craftzbay/ui/styles.css';
|
|
37
|
+
|
|
38
|
+
/* b) Your app already uses Tailwind v4: share the tokens and let Tailwind compile the library's classes */
|
|
39
|
+
@import 'tailwindcss';
|
|
40
|
+
@import '@craftzbay/ui/theme.css';
|
|
41
|
+
@source "../node_modules/@craftzbay/ui/dist-lib";
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**3. Dark mode** — toggle the `dark` class on `<html>`; every token flips. Set it before first paint to avoid a flash.
|
|
45
|
+
|
|
46
|
+
**4. Providers** — `<Toaster />` once near the root if you use `toast()`; wrap the app in `<TooltipProvider>` if you use `Tooltip`. Nothing else is required.
|
|
47
|
+
|
|
21
48
|
```tsx
|
|
22
|
-
import { Button,
|
|
23
|
-
import '@craftzbay/ui/styles.css';
|
|
49
|
+
import { Button, Toaster, TooltipProvider, toast } from '@craftzbay/ui';
|
|
24
50
|
|
|
25
51
|
export function App() {
|
|
26
|
-
return
|
|
52
|
+
return (
|
|
53
|
+
<TooltipProvider>
|
|
54
|
+
<Button onClick={() => toast({ title: 'Saved' })}>Save</Button>
|
|
55
|
+
<Toaster />
|
|
56
|
+
</TooltipProvider>
|
|
57
|
+
);
|
|
27
58
|
}
|
|
28
59
|
```
|
|
29
60
|
|
|
30
|
-
|
|
61
|
+
**Next.js App Router** — components use hooks/context, so import them from a file marked `'use client'` (or re-export the ones you need from a local `components/ui.ts` with that directive). Put the CSS import and fonts in `app/layout.tsx`.
|
|
62
|
+
|
|
63
|
+
**Name-addressed icons** — `Icons.*` (curated, tree-shaken) ships in the main entry. The lazy `<Icon name="…">` + `iconNames` list lives in a separate entry so its ~1500-icon import map never enters your bundle unless asked for:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
import { Icon } from '@craftzbay/ui/icon';
|
|
67
|
+
<Icon name="calendar" className="size-4" />;
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The package is ESM-only, ships one module per component (`sideEffects` limited to CSS), so `import { Button }` pulls in about 8 KB gzipped — no calendar, drawer, or form dependencies.
|
|
71
|
+
|
|
72
|
+
## Localisation (Mongolian built in)
|
|
73
|
+
|
|
74
|
+
Every built-in string (close/dismiss labels, placeholders, "No results.", pagination summary, error-state copy…) is typed in `UiStrings` and read through `useStrings()`. Defaults are English; a full Mongolian set ships as `mnStrings`.
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { DesignSystemProvider, mnStrings } from '@craftzbay/ui';
|
|
78
|
+
<DesignSystemProvider strings={mnStrings}>…</DesignSystemProvider>; // whole library in Mongolian
|
|
79
|
+
<DesignSystemProvider strings={{ dialog: { close: 'Schließen' } }}>…</DesignSystemProvider>; // partial override, deep-merged over defaults
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Precedence is per-component props (`placeholder`, `labels`, `aria-label`) → nearest provider → `defaultStrings`. Templates use `{name}` placeholders, e.g. `pagination.showing: '{from}–{to} / {total}'`.
|
|
83
|
+
|
|
84
|
+
## Design rules
|
|
85
|
+
|
|
86
|
+
This library implements the [craftzbay design-research](https://github.com/craftzbay/design-research) guidelines
|
|
87
|
+
([rendered site](https://craftzbay.github.io/design-research/)) — colour, type, spacing, components, accessibility, tokens.
|
|
88
|
+
The library-specific distillation is [`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md).
|
|
31
89
|
|
|
32
90
|
## Local development
|
|
33
91
|
|
|
@@ -47,7 +105,7 @@ Inside `packages/ui` itself, `pnpm build` produces the distributable bundle and
|
|
|
47
105
|
## Tech stack
|
|
48
106
|
|
|
49
107
|
- **Tailwind CSS v4** — tokens defined in `@theme` in `src/styles/globals.css`
|
|
50
|
-
- **React 18** + **TypeScript 5.7**
|
|
108
|
+
- **React 18 / 19** + **TypeScript 5.7**
|
|
51
109
|
- **Radix UI** primitives for accessibility-correct overlays
|
|
52
110
|
- **class-variance-authority** + `cn()` (`clsx` + `tailwind-merge`)
|
|
53
111
|
- **Lucide** icons (16 / 20px, 1.5 stroke)
|
|
@@ -78,6 +136,7 @@ docs/
|
|
|
78
136
|
## Component index
|
|
79
137
|
|
|
80
138
|
### Inputs
|
|
139
|
+
|
|
81
140
|
- [Input](./src/components/ui/Input.tsx) — text / email / password / number / search with prefix, suffix, error
|
|
82
141
|
- [Textarea](./src/components/ui/Textarea.tsx) — auto-resize multi-line input
|
|
83
142
|
- [Select](./src/components/ui/Select.tsx) — single-choice menu (Radix)
|
|
@@ -91,11 +150,13 @@ docs/
|
|
|
91
150
|
- [Form primitives](./src/components/ui/Form.tsx) — react-hook-form bindings
|
|
92
151
|
|
|
93
152
|
### Buttons
|
|
153
|
+
|
|
94
154
|
- [Button](./src/components/ui/Button.tsx) — primary · secondary · outline · ghost · destructive · link
|
|
95
155
|
- [IconButton](./src/components/ui/IconButton.tsx) — square icon-only variant
|
|
96
156
|
- [Pagination](./src/components/ui/Pagination.tsx) — numbered + jumps + page-size
|
|
97
157
|
|
|
98
158
|
### Feedback
|
|
159
|
+
|
|
99
160
|
- [Alert](./src/components/ui/Alert.tsx) — inline banner, dismissible
|
|
100
161
|
- [Toast](./src/components/ui/Toast.tsx) + `useToast` hook
|
|
101
162
|
- [Spinner](./src/components/ui/Spinner.tsx) — accent / neutral / on-accent tones
|
|
@@ -105,6 +166,7 @@ docs/
|
|
|
105
166
|
- [ErrorState](./src/components/ui/ErrorState.tsx) — 404 / 500 / generic
|
|
106
167
|
|
|
107
168
|
### Navigation
|
|
169
|
+
|
|
108
170
|
- [TopNav](./src/components/ui/TopNav.tsx) + `TopNavLink`
|
|
109
171
|
- [Sidebar](./src/components/ui/Sidebar.tsx) + `SidebarSection` + `SidebarItem` + `SidebarGroup`
|
|
110
172
|
- [Breadcrumbs](./src/components/ui/Breadcrumbs.tsx) — with overflow ellipsis
|
|
@@ -112,12 +174,14 @@ docs/
|
|
|
112
174
|
- [Stepper](./src/components/ui/Stepper.tsx) — horizontal + vertical
|
|
113
175
|
|
|
114
176
|
### Layout
|
|
177
|
+
|
|
115
178
|
- [Card](./src/components/ui/Card.tsx) + `CardHeader/Title/Description/Content/Footer`
|
|
116
179
|
- [Separator](./src/components/ui/Separator.tsx)
|
|
117
180
|
- [ScrollArea](./src/components/ui/ScrollArea.tsx) — Radix-backed styled scroll
|
|
118
181
|
- [Accordion](./src/components/ui/Accordion.tsx) — single + multiple
|
|
119
182
|
|
|
120
183
|
### Overlays
|
|
184
|
+
|
|
121
185
|
- [Dialog](./src/components/ui/Dialog.tsx) + `ConfirmationDialog`
|
|
122
186
|
- [Sheet](./src/components/ui/Sheet.tsx) — left / right / top / bottom
|
|
123
187
|
- [Popover](./src/components/ui/Popover.tsx)
|
|
@@ -127,12 +191,14 @@ docs/
|
|
|
127
191
|
- [CommandPalette](./src/components/ui/CommandPalette.tsx) — ⌘K palette
|
|
128
192
|
|
|
129
193
|
### Data display
|
|
194
|
+
|
|
130
195
|
- [Table](./src/components/ui/Table.tsx) + `TableSortHeader`
|
|
131
196
|
- [DataGrid](./src/components/ui/DataGrid.tsx) — column visibility, filter, sortable
|
|
132
197
|
- [Badge](./src/components/ui/Badge.tsx) — subtle + outline, 6 tones
|
|
133
198
|
- [Avatar](./src/components/ui/Avatar.tsx) + `AvatarGroup`
|
|
134
199
|
|
|
135
200
|
### Typography
|
|
201
|
+
|
|
136
202
|
- [Kbd](./src/components/ui/Kbd.tsx) — keyboard shortcut indicator
|
|
137
203
|
|
|
138
204
|
## Blocks (page templates)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as t, jsxs as d } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as a } from "react";
|
|
4
|
+
import * as n from "@radix-ui/react-accordion";
|
|
5
|
+
import { ChevronDown as m } from "../../icons/index.js";
|
|
6
|
+
import { cn as i } from "../../lib/utils.js";
|
|
7
|
+
const v = n.Root, f = a(function({ className: o, ...e }, r) {
|
|
8
|
+
return /* @__PURE__ */ t(n.Item, { ref: r, className: i("border-b border-border", o), ...e });
|
|
9
|
+
});
|
|
10
|
+
f.displayName = "AccordionItem";
|
|
11
|
+
const l = a(function({ className: o, children: e, ...r }, c) {
|
|
12
|
+
return /* @__PURE__ */ t(n.Header, { className: "flex", children: /* @__PURE__ */ d(
|
|
13
|
+
n.Trigger,
|
|
14
|
+
{
|
|
15
|
+
ref: c,
|
|
16
|
+
className: i(
|
|
17
|
+
"flex flex-1 items-center justify-between gap-2 py-4 text-left text-sm font-medium text-foreground",
|
|
18
|
+
"outline-none transition-colors duration-[var(--duration-fast)]",
|
|
19
|
+
"hover:text-accent",
|
|
20
|
+
"focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
|
|
21
|
+
"[&[data-state=open]>svg]:rotate-180",
|
|
22
|
+
o
|
|
23
|
+
),
|
|
24
|
+
...r,
|
|
25
|
+
children: [
|
|
26
|
+
e,
|
|
27
|
+
/* @__PURE__ */ t(
|
|
28
|
+
m,
|
|
29
|
+
{
|
|
30
|
+
className: "size-4 shrink-0 text-foreground-subtle transition-transform duration-[var(--duration-base)]",
|
|
31
|
+
"aria-hidden": !0
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
) });
|
|
37
|
+
});
|
|
38
|
+
l.displayName = "AccordionTrigger";
|
|
39
|
+
const u = a(function({ className: o, children: e, ...r }, c) {
|
|
40
|
+
return /* @__PURE__ */ t(
|
|
41
|
+
n.Content,
|
|
42
|
+
{
|
|
43
|
+
ref: c,
|
|
44
|
+
className: i(
|
|
45
|
+
"overflow-hidden text-sm text-foreground-muted",
|
|
46
|
+
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
47
|
+
),
|
|
48
|
+
...r,
|
|
49
|
+
children: /* @__PURE__ */ t("div", { className: i("pb-4 pt-0", o), children: e })
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
});
|
|
53
|
+
u.displayName = "AccordionContent";
|
|
54
|
+
export {
|
|
55
|
+
v as Accordion,
|
|
56
|
+
u as AccordionContent,
|
|
57
|
+
f as AccordionItem,
|
|
58
|
+
l as AccordionTrigger
|
|
59
|
+
};
|
|
60
|
+
//# sourceMappingURL=Accordion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.js","sources":["../../../src/components/ui/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ElementRef,\n} from 'react';\nimport * as AccordionPrimitive from '@radix-ui/react-accordion';\nimport { ChevronDown } from '@/icons';\nimport { cn } from '@/lib/utils';\n\n/**\n * Accessible disclosure list. Supports single (`type=\"single\"`) and multiple\n * (`type=\"multiple\"`) open behaviour — both come from Radix.\n *\n * @example FAQ\n * <Accordion type=\"single\" collapsible>\n * <AccordionItem value=\"q1\">\n * <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>\n * <AccordionContent>Yes — usage stops at the end of the billing period.</AccordionContent>\n * </AccordionItem>\n * …\n * </Accordion>\n *\n * @do Use for FAQs, settings groups, and progressive-disclosure forms.\n * @dont Nest accordions inside accordions — the focus order becomes opaque.\n */\nexport const Accordion = AccordionPrimitive.Root;\n\nexport const AccordionItem = forwardRef<\n ElementRef<typeof AccordionPrimitive.Item>,\n ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>\n>(function AccordionItem({ className, ...props }, ref) {\n return (\n <AccordionPrimitive.Item ref={ref} className={cn('border-b border-border', className)} {...props} />\n );\n});\nAccordionItem.displayName = 'AccordionItem';\n\nexport const AccordionTrigger = forwardRef<\n ElementRef<typeof AccordionPrimitive.Trigger>,\n ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>\n>(function AccordionTrigger({ className, children, ...props }, ref) {\n return (\n <AccordionPrimitive.Header className=\"flex\">\n <AccordionPrimitive.Trigger\n ref={ref}\n className={cn(\n 'flex flex-1 items-center justify-between gap-2 py-4 text-left text-sm font-medium text-foreground',\n 'outline-none transition-colors duration-[var(--duration-fast)]',\n 'hover:text-accent',\n 'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background',\n '[&[data-state=open]>svg]:rotate-180',\n className,\n )}\n {...props}\n >\n {children}\n <ChevronDown\n className=\"size-4 shrink-0 text-foreground-subtle transition-transform duration-[var(--duration-base)]\"\n aria-hidden\n />\n </AccordionPrimitive.Trigger>\n </AccordionPrimitive.Header>\n );\n});\nAccordionTrigger.displayName = 'AccordionTrigger';\n\nexport const AccordionContent = forwardRef<\n ElementRef<typeof AccordionPrimitive.Content>,\n ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>\n>(function AccordionContent({ className, children, ...props }, ref) {\n return (\n <AccordionPrimitive.Content\n ref={ref}\n className={cn(\n 'overflow-hidden text-sm text-foreground-muted',\n 'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',\n )}\n {...props}\n >\n <div className={cn('pb-4 pt-0', className)}>{children}</div>\n </AccordionPrimitive.Content>\n );\n});\nAccordionContent.displayName = 'AccordionContent';\n"],"names":["Accordion","AccordionPrimitive","AccordionItem","forwardRef","className","props","ref","jsx","cn","AccordionTrigger","children","jsxs","ChevronDown","AccordionContent"],"mappings":";;;;;;AA2BO,MAAMA,IAAYC,EAAmB,MAE/BC,IAAgBC,EAG3B,SAAuB,EAAE,WAAAC,GAAW,GAAGC,EAAA,GAASC,GAAK;AACrD,SACE,gBAAAC,EAACN,EAAmB,MAAnB,EAAwB,KAAAK,GAAU,WAAWE,EAAG,0BAA0BJ,CAAS,GAAI,GAAGC,EAAA,CAAO;AAEtG,CAAC;AACDH,EAAc,cAAc;AAErB,MAAMO,IAAmBN,EAG9B,SAA0B,EAAE,WAAAC,GAAW,UAAAM,GAAU,GAAGL,EAAA,GAASC,GAAK;AAClE,SACE,gBAAAC,EAACN,EAAmB,QAAnB,EAA0B,WAAU,QACnC,UAAA,gBAAAU;AAAA,IAACV,EAAmB;AAAA,IAAnB;AAAA,MACC,KAAAK;AAAA,MACA,WAAWE;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACAJ;AAAA,MAAA;AAAA,MAED,GAAGC;AAAA,MAEH,UAAA;AAAA,QAAAK;AAAA,QACD,gBAAAH;AAAA,UAACK;AAAA,UAAA;AAAA,YACC,WAAU;AAAA,YACV,eAAW;AAAA,UAAA;AAAA,QAAA;AAAA,MACb;AAAA,IAAA;AAAA,EAAA,GAEJ;AAEJ,CAAC;AACDH,EAAiB,cAAc;AAExB,MAAMI,IAAmBV,EAG9B,SAA0B,EAAE,WAAAC,GAAW,UAAAM,GAAU,GAAGL,EAAA,GAASC,GAAK;AAClE,SACE,gBAAAC;AAAA,IAACN,EAAmB;AAAA,IAAnB;AAAA,MACC,KAAAK;AAAA,MACA,WAAWE;AAAA,QACT;AAAA,QACA;AAAA,MAAA;AAAA,MAED,GAAGH;AAAA,MAEJ,4BAAC,OAAA,EAAI,WAAWG,EAAG,aAAaJ,CAAS,GAAI,UAAAM,EAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAG5D,CAAC;AACDG,EAAiB,cAAc;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
import { VariantProps } from '../../lib/cva';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
3
|
declare const alert: (props?: ({
|
|
4
4
|
variant?: "default" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
5
5
|
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as a, jsx as e } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as x, useState as p } from "react";
|
|
4
|
+
import { X as v, XCircle as N, AlertTriangle as k, CheckCircle2 as w, Info as z } from "../../icons/index.js";
|
|
5
|
+
import { cn as i } from "../../lib/utils.js";
|
|
6
|
+
import { useStrings as A } from "../../hooks/use-strings.js";
|
|
7
|
+
import { cva as C } from "class-variance-authority";
|
|
8
|
+
const j = C(["relative flex w-full gap-3 rounded-lg border p-4", "text-sm"], {
|
|
9
|
+
variants: {
|
|
10
|
+
variant: {
|
|
11
|
+
default: "border-border bg-background-subtle text-foreground",
|
|
12
|
+
info: "border-info-border-soft bg-info-soft text-info-text",
|
|
13
|
+
success: "border-success-border-soft bg-success-soft text-success-text",
|
|
14
|
+
warning: "border-warning-border-soft bg-warning-soft text-warning-text",
|
|
15
|
+
danger: "border-danger-border-soft bg-danger-soft text-danger-text"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: { variant: "default" }
|
|
19
|
+
}), y = {
|
|
20
|
+
default: null,
|
|
21
|
+
info: /* @__PURE__ */ e(z, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
22
|
+
success: /* @__PURE__ */ e(w, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
23
|
+
warning: /* @__PURE__ */ e(k, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
24
|
+
danger: /* @__PURE__ */ e(N, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 })
|
|
25
|
+
}, I = x(function({ className: o, variant: r = "default", title: s, dismissible: l, onDismiss: t, icon: n, children: d, ...c }, f) {
|
|
26
|
+
const u = A(), [g, m] = p(!0);
|
|
27
|
+
if (!g) return null;
|
|
28
|
+
const b = () => {
|
|
29
|
+
m(!1), t == null || t();
|
|
30
|
+
}, h = n === !1 ? null : n ?? y[r ?? "default"];
|
|
31
|
+
return /* @__PURE__ */ a(
|
|
32
|
+
"div",
|
|
33
|
+
{
|
|
34
|
+
ref: f,
|
|
35
|
+
role: r === "danger" ? "alert" : "status",
|
|
36
|
+
className: i(j({ variant: r }), o),
|
|
37
|
+
...c,
|
|
38
|
+
children: [
|
|
39
|
+
h,
|
|
40
|
+
/* @__PURE__ */ a("div", { className: "min-w-0 flex-1", children: [
|
|
41
|
+
s && /* @__PURE__ */ e("h5", { className: "mb-1 leading-tight font-medium", children: s }),
|
|
42
|
+
/* @__PURE__ */ e("div", { className: "text-sm leading-relaxed [&_p]:leading-relaxed", children: d })
|
|
43
|
+
] }),
|
|
44
|
+
l && /* @__PURE__ */ e(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
type: "button",
|
|
48
|
+
"aria-label": u.alert.dismiss,
|
|
49
|
+
onClick: b,
|
|
50
|
+
className: i(
|
|
51
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md",
|
|
52
|
+
"outline-none hover:bg-current/10",
|
|
53
|
+
"focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
54
|
+
),
|
|
55
|
+
children: /* @__PURE__ */ e(v, { className: "size-3.5", "aria-hidden": !0 })
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
I.displayName = "Alert";
|
|
63
|
+
export {
|
|
64
|
+
I as Alert
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=Alert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Alert.js","sources":["../../../src/components/ui/Alert.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, useState, type HTMLAttributes, type ReactNode } from 'react';\nimport { AlertTriangle, CheckCircle2, Info, X, XCircle } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { cva, type VariantProps } from '@/lib/cva';\n\nconst alert = cva(['relative flex w-full gap-3 rounded-lg border p-4', 'text-sm'], {\n variants: {\n variant: {\n default: 'border-border bg-background-subtle text-foreground',\n info: 'border-info-border-soft bg-info-soft text-info-text',\n success: 'border-success-border-soft bg-success-soft text-success-text',\n warning: 'border-warning-border-soft bg-warning-soft text-warning-text',\n danger: 'border-danger-border-soft bg-danger-soft text-danger-text',\n },\n },\n defaultVariants: { variant: 'default' },\n});\n\nconst iconForVariant = {\n default: null,\n info: <Info className=\"mt-0.5 size-4 shrink-0\" aria-hidden />,\n success: <CheckCircle2 className=\"mt-0.5 size-4 shrink-0\" aria-hidden />,\n warning: <AlertTriangle className=\"mt-0.5 size-4 shrink-0\" aria-hidden />,\n danger: <XCircle className=\"mt-0.5 size-4 shrink-0\" aria-hidden />,\n};\n\nexport interface AlertProps\n extends Omit<HTMLAttributes<HTMLDivElement>, 'title'>, VariantProps<typeof alert> {\n /** Heading. Renders bold above the description. */\n title?: ReactNode;\n /** Show a dismiss (×) button. */\n dismissible?: boolean;\n /** Called when the user dismisses. */\n onDismiss?: () => void;\n /** Override the variant's default icon. Pass `false` to suppress the icon. */\n icon?: ReactNode | false;\n}\n\n/**\n * Inline banner for static page-level or section-level state. For transient\n * notifications use `Toast` instead.\n *\n * @example Inline error\n * <Alert variant=\"danger\" title=\"Payment failed\">\n * Your card was declined. Update your billing info to retry.\n * </Alert>\n *\n * @example Dismissible info\n * <Alert variant=\"info\" dismissible onDismiss={dismiss}>\n * We're improving search — give us feedback at #search-feedback.\n * </Alert>\n *\n * @do Use the closest semantic variant — `info` for neutral facts,\n * `warning` for \"watch out\", `danger` for \"this is broken\".\n * @dont Use Alert for one-time confirmations — that's Toast.\n */\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(\n { className, variant = 'default', title, dismissible, onDismiss, icon, children, ...props },\n ref,\n) {\n const strings = useStrings();\n const [open, setOpen] = useState(true);\n if (!open) return null;\n\n const handleDismiss = () => {\n setOpen(false);\n onDismiss?.();\n };\n\n const renderedIcon = icon === false ? null : (icon ?? iconForVariant[variant ?? 'default']);\n\n return (\n <div\n ref={ref}\n // Only errors interrupt the screen reader; everything else is polite.\n role={variant === 'danger' ? 'alert' : 'status'}\n className={cn(alert({ variant }), className)}\n {...props}\n >\n {renderedIcon}\n <div className=\"min-w-0 flex-1\">\n {title && <h5 className=\"mb-1 leading-tight font-medium\">{title}</h5>}\n <div className=\"text-sm leading-relaxed [&_p]:leading-relaxed\">{children}</div>\n </div>\n {dismissible && (\n <button\n type=\"button\"\n aria-label={strings.alert.dismiss}\n onClick={handleDismiss}\n className={cn(\n 'inline-flex size-6 shrink-0 items-center justify-center rounded-md',\n 'outline-none hover:bg-current/10',\n 'focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2',\n )}\n >\n <X className=\"size-3.5\" aria-hidden />\n </button>\n )}\n </div>\n );\n});\nAlert.displayName = 'Alert';\n"],"names":["alert","cva","iconForVariant","jsx","Info","CheckCircle2","AlertTriangle","XCircle","Alert","forwardRef","className","variant","title","dismissible","onDismiss","icon","children","props","ref","strings","useStrings","open","setOpen","useState","handleDismiss","renderedIcon","jsxs","cn","X"],"mappings":";;;;;;;AAQA,MAAMA,IAAQC,EAAI,CAAC,oDAAoD,SAAS,GAAG;AAAA,EACjF,UAAU;AAAA,IACR,SAAS;AAAA,MACP,SAAS;AAAA,MACT,MAAM;AAAA,MACN,SAAS;AAAA,MACT,SAAS;AAAA,MACT,QAAQ;AAAA,IAAA;AAAA,EACV;AAAA,EAEF,iBAAiB,EAAE,SAAS,UAAA;AAC9B,CAAC,GAEKC,IAAiB;AAAA,EACrB,SAAS;AAAA,EACT,MAAM,gBAAAC,EAACC,GAAA,EAAK,WAAU,0BAAyB,eAAW,IAAC;AAAA,EAC3D,SAAS,gBAAAD,EAACE,GAAA,EAAa,WAAU,0BAAyB,eAAW,IAAC;AAAA,EACtE,SAAS,gBAAAF,EAACG,GAAA,EAAc,WAAU,0BAAyB,eAAW,IAAC;AAAA,EACvE,QAAQ,gBAAAH,EAACI,GAAA,EAAQ,WAAU,0BAAyB,eAAW,GAAA,CAAC;AAClE,GAgCaC,IAAQC,EAAuC,SAC1D,EAAE,WAAAC,GAAW,SAAAC,IAAU,WAAW,OAAAC,GAAO,aAAAC,GAAa,WAAAC,GAAW,MAAAC,GAAM,UAAAC,GAAU,GAAGC,EAAA,GACpFC,GACA;AACA,QAAMC,IAAUC,EAAA,GACV,CAACC,GAAMC,CAAO,IAAIC,EAAS,EAAI;AACrC,MAAI,CAACF,EAAM,QAAO;AAElB,QAAMG,IAAgB,MAAM;AAC1B,IAAAF,EAAQ,EAAK,GACbR,KAAA,QAAAA;AAAA,EACF,GAEMW,IAAeV,MAAS,KAAQ,OAAQA,KAAQb,EAAeS,KAAW,SAAS;AAEzF,SACE,gBAAAe;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAR;AAAA,MAEA,MAAMP,MAAY,WAAW,UAAU;AAAA,MACvC,WAAWgB,EAAG3B,EAAM,EAAE,SAAAW,EAAA,CAAS,GAAGD,CAAS;AAAA,MAC1C,GAAGO;AAAA,MAEH,UAAA;AAAA,QAAAQ;AAAA,QACD,gBAAAC,EAAC,OAAA,EAAI,WAAU,kBACZ,UAAA;AAAA,UAAAd,KAAS,gBAAAT,EAAC,MAAA,EAAG,WAAU,kCAAkC,UAAAS,GAAM;AAAA,UAChE,gBAAAT,EAAC,OAAA,EAAI,WAAU,iDAAiD,UAAAa,EAAA,CAAS;AAAA,QAAA,GAC3E;AAAA,QACCH,KACC,gBAAAV;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAYgB,EAAQ,MAAM;AAAA,YAC1B,SAASK;AAAA,YACT,WAAWG;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,gBAAAxB,EAACyB,GAAA,EAAE,WAAU,YAAW,eAAW,GAAA,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,MACtC;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AACDpB,EAAM,cAAc;"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ComponentPropsWithoutRef, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
import { VariantProps } from '../../lib/cva';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
3
|
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
4
4
|
declare const sizeMap: {
|
|
5
|
-
readonly xs: "size-5 text-
|
|
5
|
+
readonly xs: "size-5 text-xs";
|
|
6
6
|
readonly sm: "size-6 text-xs";
|
|
7
7
|
readonly md: "size-8 text-xs";
|
|
8
8
|
readonly lg: "size-10 text-sm";
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as n, jsx as i } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as g } from "react";
|
|
4
|
+
import * as f from "@radix-ui/react-avatar";
|
|
5
|
+
import { cn as o } from "../../lib/utils.js";
|
|
6
|
+
import { useStrings as b } from "../../hooks/use-strings.js";
|
|
7
|
+
import { formatString as p } from "../../lib/strings.js";
|
|
8
|
+
import { cva as N } from "class-variance-authority";
|
|
9
|
+
const v = {
|
|
10
|
+
xs: "size-5 text-xs",
|
|
11
|
+
sm: "size-6 text-xs",
|
|
12
|
+
md: "size-8 text-xs",
|
|
13
|
+
lg: "size-10 text-sm",
|
|
14
|
+
xl: "size-12 text-base"
|
|
15
|
+
}, h = N("relative inline-flex shrink-0", {
|
|
16
|
+
variants: { size: v },
|
|
17
|
+
defaultVariants: { size: "md" }
|
|
18
|
+
}), y = {
|
|
19
|
+
online: "bg-success",
|
|
20
|
+
busy: "bg-danger",
|
|
21
|
+
away: "bg-warning",
|
|
22
|
+
offline: "bg-foreground-subtle"
|
|
23
|
+
}, A = g(
|
|
24
|
+
function({ className: l, size: m, src: e, alt: r, fallback: c, status: a, ...u }, t) {
|
|
25
|
+
const s = b();
|
|
26
|
+
return /* @__PURE__ */ n("span", { className: h({ size: m }), children: [
|
|
27
|
+
/* @__PURE__ */ n(
|
|
28
|
+
f.Root,
|
|
29
|
+
{
|
|
30
|
+
ref: t,
|
|
31
|
+
className: o(
|
|
32
|
+
"block size-full overflow-hidden rounded-full bg-background-muted text-foreground-muted",
|
|
33
|
+
l
|
|
34
|
+
),
|
|
35
|
+
...u,
|
|
36
|
+
children: [
|
|
37
|
+
e && /* @__PURE__ */ i(
|
|
38
|
+
f.Image,
|
|
39
|
+
{
|
|
40
|
+
src: e,
|
|
41
|
+
alt: r ?? "",
|
|
42
|
+
className: "aspect-square size-full object-cover"
|
|
43
|
+
}
|
|
44
|
+
),
|
|
45
|
+
/* @__PURE__ */ i(
|
|
46
|
+
f.Fallback,
|
|
47
|
+
{
|
|
48
|
+
delayMs: e ? 200 : 0,
|
|
49
|
+
className: "flex size-full items-center justify-center bg-background-muted font-medium uppercase",
|
|
50
|
+
children: c ?? "?"
|
|
51
|
+
}
|
|
52
|
+
)
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
),
|
|
56
|
+
a && /* @__PURE__ */ i(
|
|
57
|
+
"span",
|
|
58
|
+
{
|
|
59
|
+
"aria-label": p(s.avatar.status, { status: a }),
|
|
60
|
+
className: o(
|
|
61
|
+
"absolute bottom-0 right-0 block size-[28%] rounded-full ring-2 ring-background",
|
|
62
|
+
y[a]
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
] });
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
A.displayName = "Avatar";
|
|
70
|
+
const j = g(function({ max: l = 4, size: m = "md", className: e, children: r, ...c }, a) {
|
|
71
|
+
const u = b(), t = Array.isArray(r) ? r : [r], s = t.slice(0, l), d = t.length - s.length;
|
|
72
|
+
return /* @__PURE__ */ n("div", { ref: a, className: o("flex items-center -space-x-2", e), ...c, children: [
|
|
73
|
+
s.map((z, k) => /* @__PURE__ */ i("div", { className: "ring-2 ring-background rounded-full", children: z }, k)),
|
|
74
|
+
d > 0 && /* @__PURE__ */ n(
|
|
75
|
+
"div",
|
|
76
|
+
{
|
|
77
|
+
className: o(
|
|
78
|
+
"inline-flex items-center justify-center rounded-full bg-background-muted text-foreground-muted ring-2 ring-background",
|
|
79
|
+
v[m],
|
|
80
|
+
"font-medium"
|
|
81
|
+
),
|
|
82
|
+
"aria-label": p(u.avatar.more, { n: d }),
|
|
83
|
+
children: [
|
|
84
|
+
"+",
|
|
85
|
+
d
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
] });
|
|
90
|
+
});
|
|
91
|
+
j.displayName = "AvatarGroup";
|
|
92
|
+
export {
|
|
93
|
+
A as Avatar,
|
|
94
|
+
j as AvatarGroup
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=Avatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Avatar.js","sources":["../../../src/components/ui/Avatar.tsx"],"sourcesContent":["'use client';\n\nimport {\n forwardRef,\n type ComponentPropsWithoutRef,\n type ElementRef,\n type HTMLAttributes,\n type ReactNode,\n} from 'react';\nimport * as AvatarPrimitive from '@radix-ui/react-avatar';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\nimport { formatString } from '@/lib/strings';\nimport { cva, type VariantProps } from '@/lib/cva';\n\nconst sizeMap = {\n xs: 'size-5 text-xs',\n sm: 'size-6 text-xs',\n md: 'size-8 text-xs',\n lg: 'size-10 text-sm',\n xl: 'size-12 text-base',\n} as const;\n\n// The wrapper sets size — Root sits inside and clips image/fallback only.\nconst avatarWrapper = cva('relative inline-flex shrink-0', {\n variants: { size: sizeMap },\n defaultVariants: { size: 'md' },\n});\n\nexport interface AvatarProps\n extends ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>,\n VariantProps<typeof avatarWrapper> {\n /** Image URL. If absent or fails to load, fallback initials are shown. */\n src?: string;\n /** Alt text for the image; falls back to fallback when there's no `src`. */\n alt?: string;\n /** Initials shown when image is missing or loading. Pass at most 2 characters. */\n fallback?: string;\n /** Status dot rendered on the bottom-right. */\n status?: 'online' | 'busy' | 'away' | 'offline';\n}\n\nconst statusColour: Record<NonNullable<AvatarProps['status']>, string> = {\n online: 'bg-success',\n busy: 'bg-danger',\n away: 'bg-warning',\n offline: 'bg-foreground-subtle',\n};\n\n/**\n * Avatar with image, initials fallback, and optional status dot.\n *\n * @example\n * <Avatar src={user.photo} alt={user.name} fallback={getInitials(user.name)} status=\"online\" />\n *\n * @example AvatarGroup\n * <AvatarGroup max={3}>\n * {users.map(u => <Avatar key={u.id} src={u.photo} fallback={getInitials(u.name)} />)}\n * </AvatarGroup>\n *\n * @do Provide `alt` for image avatars and meaningful initials for the\n * fallback so screen readers announce something useful.\n * @dont Use the status dot without a tooltip explaining what it means.\n */\nexport const Avatar = forwardRef<ElementRef<typeof AvatarPrimitive.Root>, AvatarProps>(\n function Avatar({ className, size, src, alt, fallback, status, ...props }, ref) {\n const strings = useStrings();\n return (\n <span className={avatarWrapper({ size })}>\n <AvatarPrimitive.Root\n ref={ref}\n className={cn(\n 'block size-full overflow-hidden rounded-full bg-background-muted text-foreground-muted',\n className,\n )}\n {...props}\n >\n {src && (\n <AvatarPrimitive.Image\n src={src}\n alt={alt ?? ''}\n className=\"aspect-square size-full object-cover\"\n />\n )}\n <AvatarPrimitive.Fallback\n delayMs={src ? 200 : 0}\n className=\"flex size-full items-center justify-center bg-background-muted font-medium uppercase\"\n >\n {fallback ?? '?'}\n </AvatarPrimitive.Fallback>\n </AvatarPrimitive.Root>\n {status && (\n <span\n aria-label={formatString(strings.avatar.status, { status })}\n className={cn(\n 'absolute bottom-0 right-0 block size-[28%] rounded-full ring-2 ring-background',\n statusColour[status],\n )}\n />\n )}\n </span>\n );\n },\n);\nAvatar.displayName = 'Avatar';\n\nexport interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {\n /** Maximum visible avatars before showing a \"+N\" overflow chip. */\n max?: number;\n /** Avatar size applied to children + overflow. */\n size?: keyof typeof sizeMap;\n /** Children — Avatars. */\n children: ReactNode;\n}\n\n/**\n * Overlapping avatars with overflow indicator. Pass plain `<Avatar>` children.\n */\nexport const AvatarGroup = forwardRef<HTMLDivElement, AvatarGroupProps>(function AvatarGroup(\n { max = 4, size = 'md', className, children, ...props },\n ref,\n) {\n const strings = useStrings();\n const items = Array.isArray(children) ? children : [children];\n const visible = items.slice(0, max);\n const overflow = items.length - visible.length;\n\n return (\n <div ref={ref} className={cn('flex items-center -space-x-2', className)} {...props}>\n {visible.map((child, i) => (\n <div key={i} className=\"ring-2 ring-background rounded-full\">\n {child}\n </div>\n ))}\n {overflow > 0 && (\n <div\n className={cn(\n 'inline-flex items-center justify-center rounded-full bg-background-muted text-foreground-muted ring-2 ring-background',\n sizeMap[size],\n 'font-medium',\n )}\n aria-label={formatString(strings.avatar.more, { n: overflow })}\n >\n +{overflow}\n </div>\n )}\n </div>\n );\n});\nAvatarGroup.displayName = 'AvatarGroup';\n"],"names":["sizeMap","avatarWrapper","cva","statusColour","Avatar","forwardRef","className","size","src","alt","fallback","status","props","ref","strings","useStrings","jsxs","AvatarPrimitive","cn","jsx","formatString","AvatarGroup","max","children","items","visible","overflow","child","i"],"mappings":";;;;;;;;AAeA,MAAMA,IAAU;AAAA,EACd,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN,GAGMC,IAAgBC,EAAI,iCAAiC;AAAA,EACzD,UAAU,EAAE,MAAMF,EAAA;AAAA,EAClB,iBAAiB,EAAE,MAAM,KAAA;AAC3B,CAAC,GAeKG,IAAmE;AAAA,EACvE,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,MAAM;AAAA,EACN,SAAS;AACX,GAiBaC,IAASC;AAAA,EACpB,SAAgB,EAAE,WAAAC,GAAW,MAAAC,GAAM,KAAAC,GAAK,KAAAC,GAAK,UAAAC,GAAU,QAAAC,GAAQ,GAAGC,EAAA,GAASC,GAAK;AAC9E,UAAMC,IAAUC,EAAA;AAChB,6BACG,QAAA,EAAK,WAAWd,EAAc,EAAE,MAAAM,EAAA,CAAM,GACrC,UAAA;AAAA,MAAA,gBAAAS;AAAA,QAACC,EAAgB;AAAA,QAAhB;AAAA,UACC,KAAAJ;AAAA,UACA,WAAWK;AAAA,YACT;AAAA,YACAZ;AAAA,UAAA;AAAA,UAED,GAAGM;AAAA,UAEH,UAAA;AAAA,YAAAJ,KACC,gBAAAW;AAAA,cAACF,EAAgB;AAAA,cAAhB;AAAA,gBACC,KAAAT;AAAA,gBACA,KAAKC,KAAO;AAAA,gBACZ,WAAU;AAAA,cAAA;AAAA,YAAA;AAAA,YAGd,gBAAAU;AAAA,cAACF,EAAgB;AAAA,cAAhB;AAAA,gBACC,SAAST,IAAM,MAAM;AAAA,gBACrB,WAAU;AAAA,gBAET,UAAAE,KAAY;AAAA,cAAA;AAAA,YAAA;AAAA,UACf;AAAA,QAAA;AAAA,MAAA;AAAA,MAEDC,KACC,gBAAAQ;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,cAAYC,EAAaN,EAAQ,OAAO,QAAQ,EAAE,QAAAH,GAAQ;AAAA,UAC1D,WAAWO;AAAA,YACT;AAAA,YACAf,EAAaQ,CAAM;AAAA,UAAA;AAAA,QACrB;AAAA,MAAA;AAAA,IACF,GAEJ;AAAA,EAEJ;AACF;AACAP,EAAO,cAAc;AAcd,MAAMiB,IAAchB,EAA6C,SACtE,EAAE,KAAAiB,IAAM,GAAG,MAAAf,IAAO,MAAM,WAAAD,GAAW,UAAAiB,GAAU,GAAGX,EAAA,GAChDC,GACA;AACA,QAAMC,IAAUC,EAAA,GACVS,IAAQ,MAAM,QAAQD,CAAQ,IAAIA,IAAW,CAACA,CAAQ,GACtDE,IAAUD,EAAM,MAAM,GAAGF,CAAG,GAC5BI,IAAWF,EAAM,SAASC,EAAQ;AAExC,SACE,gBAAAT,EAAC,SAAI,KAAAH,GAAU,WAAWK,EAAG,gCAAgCZ,CAAS,GAAI,GAAGM,GAC1E,UAAA;AAAA,IAAAa,EAAQ,IAAI,CAACE,GAAOC,MACnB,gBAAAT,EAAC,SAAY,WAAU,uCACpB,UAAAQ,EAAA,GADOC,CAEV,CACD;AAAA,IACAF,IAAW,KACV,gBAAAV;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAWE;AAAA,UACT;AAAA,UACAlB,EAAQO,CAAI;AAAA,UACZ;AAAA,QAAA;AAAA,QAEF,cAAYa,EAAaN,EAAQ,OAAO,MAAM,EAAE,GAAGY,GAAU;AAAA,QAC9D,UAAA;AAAA,UAAA;AAAA,UACGA;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EACJ,GAEJ;AAEJ,CAAC;AACDL,EAAY,cAAc;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { HTMLAttributes } from 'react';
|
|
2
|
-
import { VariantProps } from '../../lib/cva';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
3
|
declare const badge: (props?: ({
|
|
4
4
|
variant?: "subtle" | "outline" | null | undefined;
|
|
5
5
|
tone?: "success" | "warning" | "danger" | "info" | "neutral" | "accent" | null | undefined;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as i, jsx as u } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as l } from "react";
|
|
4
|
+
import { cn as e } from "../../lib/utils.js";
|
|
5
|
+
import { cva as d } from "class-variance-authority";
|
|
6
|
+
const g = d(
|
|
7
|
+
[
|
|
8
|
+
"inline-flex items-center gap-1 rounded-full px-2 py-0.5",
|
|
9
|
+
"text-xs font-medium whitespace-nowrap"
|
|
10
|
+
],
|
|
11
|
+
{
|
|
12
|
+
variants: {
|
|
13
|
+
variant: {
|
|
14
|
+
subtle: "",
|
|
15
|
+
outline: "border bg-transparent"
|
|
16
|
+
},
|
|
17
|
+
tone: {
|
|
18
|
+
neutral: "",
|
|
19
|
+
accent: "",
|
|
20
|
+
success: "",
|
|
21
|
+
warning: "",
|
|
22
|
+
danger: "",
|
|
23
|
+
info: ""
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
compoundVariants: [
|
|
27
|
+
// Subtle — coloured fill + matching text.
|
|
28
|
+
{ variant: "subtle", tone: "neutral", class: "bg-background-muted text-foreground-muted" },
|
|
29
|
+
{ variant: "subtle", tone: "accent", class: "bg-accent-soft text-on-accent-soft" },
|
|
30
|
+
{ variant: "subtle", tone: "success", class: "bg-success-soft text-success-text" },
|
|
31
|
+
{ variant: "subtle", tone: "warning", class: "bg-warning-soft text-warning-text" },
|
|
32
|
+
{ variant: "subtle", tone: "danger", class: "bg-danger-soft text-danger-text" },
|
|
33
|
+
{ variant: "subtle", tone: "info", class: "bg-info-soft text-info-text" },
|
|
34
|
+
// Outline — coloured border + text, transparent fill.
|
|
35
|
+
{ variant: "outline", tone: "neutral", class: "border-border text-foreground-muted" },
|
|
36
|
+
{ variant: "outline", tone: "accent", class: "border-accent text-accent" },
|
|
37
|
+
{ variant: "outline", tone: "success", class: "border-success-border-soft text-success-text" },
|
|
38
|
+
{ variant: "outline", tone: "warning", class: "border-warning-border-soft text-warning-text" },
|
|
39
|
+
{ variant: "outline", tone: "danger", class: "border-danger-border-soft text-danger-text" },
|
|
40
|
+
{ variant: "outline", tone: "info", class: "border-info-border-soft text-info-text" }
|
|
41
|
+
],
|
|
42
|
+
defaultVariants: { variant: "subtle", tone: "neutral" }
|
|
43
|
+
}
|
|
44
|
+
), b = {
|
|
45
|
+
neutral: "bg-foreground-subtle",
|
|
46
|
+
accent: "bg-accent",
|
|
47
|
+
success: "bg-success",
|
|
48
|
+
warning: "bg-warning",
|
|
49
|
+
danger: "bg-danger",
|
|
50
|
+
info: "bg-info"
|
|
51
|
+
}, f = l(function({ className: n, variant: a, tone: t = "neutral", dot: r, children: s, ...o }, c) {
|
|
52
|
+
return /* @__PURE__ */ i("span", { ref: c, className: e(g({ variant: a, tone: t }), n), ...o, children: [
|
|
53
|
+
r && /* @__PURE__ */ u(
|
|
54
|
+
"span",
|
|
55
|
+
{
|
|
56
|
+
"aria-hidden": !0,
|
|
57
|
+
className: e("inline-block size-1.5 rounded-full", b[t ?? "neutral"])
|
|
58
|
+
}
|
|
59
|
+
),
|
|
60
|
+
s
|
|
61
|
+
] });
|
|
62
|
+
});
|
|
63
|
+
f.displayName = "Badge";
|
|
64
|
+
export {
|
|
65
|
+
f as Badge
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=Badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Badge.js","sources":["../../../src/components/ui/Badge.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type HTMLAttributes } from 'react';\nimport { cn } from '@/lib/utils';\nimport { cva, type VariantProps } from '@/lib/cva';\n\nconst badge = cva(\n [\n 'inline-flex items-center gap-1 rounded-full px-2 py-0.5',\n 'text-xs font-medium whitespace-nowrap',\n ],\n {\n variants: {\n variant: {\n subtle: '',\n outline: 'border bg-transparent',\n },\n tone: {\n neutral: '',\n accent: '',\n success: '',\n warning: '',\n danger: '',\n info: '',\n },\n },\n compoundVariants: [\n // Subtle — coloured fill + matching text.\n { variant: 'subtle', tone: 'neutral', class: 'bg-background-muted text-foreground-muted' },\n { variant: 'subtle', tone: 'accent', class: 'bg-accent-soft text-on-accent-soft' },\n { variant: 'subtle', tone: 'success', class: 'bg-success-soft text-success-text' },\n { variant: 'subtle', tone: 'warning', class: 'bg-warning-soft text-warning-text' },\n { variant: 'subtle', tone: 'danger', class: 'bg-danger-soft text-danger-text' },\n { variant: 'subtle', tone: 'info', class: 'bg-info-soft text-info-text' },\n // Outline — coloured border + text, transparent fill.\n { variant: 'outline', tone: 'neutral', class: 'border-border text-foreground-muted' },\n { variant: 'outline', tone: 'accent', class: 'border-accent text-accent' },\n { variant: 'outline', tone: 'success', class: 'border-success-border-soft text-success-text' },\n { variant: 'outline', tone: 'warning', class: 'border-warning-border-soft text-warning-text' },\n { variant: 'outline', tone: 'danger', class: 'border-danger-border-soft text-danger-text' },\n { variant: 'outline', tone: 'info', class: 'border-info-border-soft text-info-text' },\n ],\n defaultVariants: { variant: 'subtle', tone: 'neutral' },\n },\n);\n\nexport interface BadgeProps\n extends HTMLAttributes<HTMLSpanElement>,\n VariantProps<typeof badge> {\n /** Show a leading status dot in the same tone. */\n dot?: boolean;\n}\n\nconst dotColour = {\n neutral: 'bg-foreground-subtle',\n accent: 'bg-accent',\n success: 'bg-success',\n warning: 'bg-warning',\n danger: 'bg-danger',\n info: 'bg-info',\n} as const;\n\n/**\n * Status pill. Use `subtle` for high-volume contexts (table cells, lists);\n * `outline` when the badge sits on a busy or coloured background.\n *\n * @example\n * <Badge tone=\"success\" dot>Active</Badge>\n * <Badge tone=\"danger\" variant=\"outline\">Failed</Badge>\n *\n * @do Use badges to summarise state, not to label categories — for taxonomies\n * pick a tone palette and stick with it.\n * @dont Use a badge as a button. Wrap it in a Button or use a Toggle.\n */\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(function Badge(\n { className, variant, tone = 'neutral', dot, children, ...props },\n ref,\n) {\n return (\n <span ref={ref} className={cn(badge({ variant, tone }), className)} {...props}>\n {dot && (\n <span\n aria-hidden\n className={cn('inline-block size-1.5 rounded-full', dotColour[tone ?? 'neutral'])}\n />\n )}\n {children}\n </span>\n );\n});\nBadge.displayName = 'Badge';\n"],"names":["badge","cva","dotColour","Badge","forwardRef","className","variant","tone","dot","children","props","ref","jsxs","cn","jsx"],"mappings":";;;;;AAMA,MAAMA,IAAQC;AAAA,EACZ;AAAA,IACE;AAAA,IACA;AAAA,EAAA;AAAA,EAEF;AAAA,IACE,UAAU;AAAA,MACR,SAAS;AAAA,QACP,QAAQ;AAAA,QACR,SAAS;AAAA,MAAA;AAAA,MAEX,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,MAAA;AAAA,IACR;AAAA,IAEF,kBAAkB;AAAA;AAAA,MAEhB,EAAE,SAAS,UAAU,MAAM,WAAW,OAAO,4CAAA;AAAA,MAC7C,EAAE,SAAS,UAAU,MAAM,UAAU,OAAO,qCAAA;AAAA,MAC5C,EAAE,SAAS,UAAU,MAAM,WAAW,OAAO,oCAAA;AAAA,MAC7C,EAAE,SAAS,UAAU,MAAM,WAAW,OAAO,oCAAA;AAAA,MAC7C,EAAE,SAAS,UAAU,MAAM,UAAU,OAAO,kCAAA;AAAA,MAC5C,EAAE,SAAS,UAAU,MAAM,QAAQ,OAAO,8BAAA;AAAA;AAAA,MAE1C,EAAE,SAAS,WAAW,MAAM,WAAW,OAAO,sCAAA;AAAA,MAC9C,EAAE,SAAS,WAAW,MAAM,UAAU,OAAO,4BAAA;AAAA,MAC7C,EAAE,SAAS,WAAW,MAAM,WAAW,OAAO,+CAAA;AAAA,MAC9C,EAAE,SAAS,WAAW,MAAM,WAAW,OAAO,+CAAA;AAAA,MAC9C,EAAE,SAAS,WAAW,MAAM,UAAU,OAAO,6CAAA;AAAA,MAC7C,EAAE,SAAS,WAAW,MAAM,QAAQ,OAAO,yCAAA;AAAA,IAAyC;AAAA,IAEtF,iBAAiB,EAAE,SAAS,UAAU,MAAM,UAAA;AAAA,EAAU;AAE1D,GASMC,IAAY;AAAA,EAChB,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AACR,GAcaC,IAAQC,EAAwC,SAC3D,EAAE,WAAAC,GAAW,SAAAC,GAAS,MAAAC,IAAO,WAAW,KAAAC,GAAK,UAAAC,GAAU,GAAGC,EAAA,GAC1DC,GACA;AACA,SACE,gBAAAC,EAAC,QAAA,EAAK,KAAAD,GAAU,WAAWE,EAAGb,EAAM,EAAE,SAAAM,GAAS,MAAAC,EAAA,CAAM,GAAGF,CAAS,GAAI,GAAGK,GACrE,UAAA;AAAA,IAAAF,KACC,gBAAAM;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,eAAW;AAAA,QACX,WAAWD,EAAG,sCAAsCX,EAAUK,KAAQ,SAAS,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAGnFE;AAAA,EAAA,GACH;AAEJ,CAAC;AACDN,EAAM,cAAc;"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as e, jsxs as c } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as b } from "react";
|
|
4
|
+
import { MoreHorizontal as g, ChevronRight as d } from "../../icons/index.js";
|
|
5
|
+
import { cn as x } from "../../lib/utils.js";
|
|
6
|
+
import { useStrings as N } from "../../hooks/use-strings.js";
|
|
7
|
+
const v = b(function({ items: s, maxItems: m = 4, renderLink: l, className: u, ...i }, f) {
|
|
8
|
+
const h = N(), n = s.length > m ? [s[0], { collapsed: !0 }, ...s.slice(-2)] : s;
|
|
9
|
+
return /* @__PURE__ */ e("nav", { ref: f, "aria-label": i["aria-label"] ?? h.breadcrumbs.label, className: x("text-sm", u), ...i, children: /* @__PURE__ */ e("ol", { className: "flex flex-wrap items-center gap-1.5 text-foreground-subtle", children: n.map((o, a) => {
|
|
10
|
+
const t = a === n.length - 1;
|
|
11
|
+
if ("collapsed" in o)
|
|
12
|
+
return /* @__PURE__ */ c("li", { className: "flex items-center gap-1.5", children: [
|
|
13
|
+
/* @__PURE__ */ e(g, { className: "size-4", "aria-hidden": !0 }),
|
|
14
|
+
/* @__PURE__ */ e(d, { className: "size-3.5", "aria-hidden": !0 })
|
|
15
|
+
] }, `ellipsis-${a}`);
|
|
16
|
+
const r = o, p = t ? /* @__PURE__ */ e("span", { "aria-current": "page", className: "font-medium text-foreground", children: r.label }) : r.href ? l ? l(r.href, r.label) : /* @__PURE__ */ e(
|
|
17
|
+
"a",
|
|
18
|
+
{
|
|
19
|
+
href: r.href,
|
|
20
|
+
className: "hover:text-foreground transition-colors duration-[var(--duration-fast)] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm",
|
|
21
|
+
children: r.label
|
|
22
|
+
}
|
|
23
|
+
) : /* @__PURE__ */ e("span", { children: r.label });
|
|
24
|
+
return /* @__PURE__ */ c("li", { className: "flex items-center gap-1.5", children: [
|
|
25
|
+
p,
|
|
26
|
+
!t && /* @__PURE__ */ e(d, { className: "size-3.5", "aria-hidden": !0 })
|
|
27
|
+
] }, a);
|
|
28
|
+
}) }) });
|
|
29
|
+
});
|
|
30
|
+
v.displayName = "Breadcrumbs";
|
|
31
|
+
export {
|
|
32
|
+
v as Breadcrumbs
|
|
33
|
+
};
|
|
34
|
+
//# sourceMappingURL=Breadcrumbs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Breadcrumbs.js","sources":["../../../src/components/ui/Breadcrumbs.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from 'react';\nimport { ChevronRight, MoreHorizontal } from '@/icons';\nimport { cn } from '@/lib/utils';\nimport { useStrings } from '@/hooks/use-strings';\n\nexport interface BreadcrumbItem {\n /** Visible label. */\n label: ReactNode;\n /** Optional URL. Last item is treated as current page even if href is set. */\n href?: string;\n}\n\nexport interface BreadcrumbsProps extends HTMLAttributes<HTMLElement> {\n items: BreadcrumbItem[];\n /** Collapse to first + ellipsis + last `n` when items.length exceeds this. */\n maxItems?: number;\n /** Custom link renderer (for next/link, react-router, etc.). */\n renderLink?: (href: string, children: ReactNode) => ReactNode;\n}\n\n/**\n * Breadcrumb trail. Collapses with an ellipsis when there are too many crumbs.\n *\n * @example\n * <Breadcrumbs items={[\n * { label: 'Projects', href: '/projects' },\n * { label: 'Nova', href: '/projects/nova' },\n * { label: 'Settings' },\n * ]} />\n *\n * @example With react-router\n * <Breadcrumbs items={trail} renderLink={(href, c) => <Link to={href}>{c}</Link>} />\n *\n * @do Always omit `href` on the last item — it represents the current page.\n * @dont Use Breadcrumbs for flat navigation. Use TopNav links instead.\n */\nexport const Breadcrumbs = forwardRef<HTMLElement, BreadcrumbsProps>(function Breadcrumbs(\n { items, maxItems = 4, renderLink, className, ...props },\n ref,\n) {\n const strings = useStrings();\n const shouldCollapse = items.length > maxItems;\n const displayItems =\n !shouldCollapse\n ? items\n : [items[0], { collapsed: true } as const, ...items.slice(-2)];\n\n return (\n <nav ref={ref} aria-label={props['aria-label'] ?? strings.breadcrumbs.label} className={cn('text-sm', className)} {...props}>\n <ol className=\"flex flex-wrap items-center gap-1.5 text-foreground-subtle\">\n {displayItems.map((it, i) => {\n const isLast = i === displayItems.length - 1;\n if ('collapsed' in it) {\n return (\n <li key={`ellipsis-${i}`} className=\"flex items-center gap-1.5\">\n <MoreHorizontal className=\"size-4\" aria-hidden />\n <ChevronRight className=\"size-3.5\" aria-hidden />\n </li>\n );\n }\n const item = it as BreadcrumbItem;\n const labelNode = isLast ? (\n <span aria-current=\"page\" className=\"font-medium text-foreground\">\n {item.label}\n </span>\n ) : item.href ? (\n renderLink ? (\n renderLink(item.href, item.label)\n ) : (\n <a\n href={item.href}\n className=\"hover:text-foreground transition-colors duration-[var(--duration-fast)] outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 rounded-sm\"\n >\n {item.label}\n </a>\n )\n ) : (\n <span>{item.label}</span>\n );\n return (\n <li key={i} className=\"flex items-center gap-1.5\">\n {labelNode}\n {!isLast && <ChevronRight className=\"size-3.5\" aria-hidden />}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n});\nBreadcrumbs.displayName = 'Breadcrumbs';\n"],"names":["Breadcrumbs","forwardRef","items","maxItems","renderLink","className","props","ref","strings","useStrings","displayItems","jsx","cn","it","i","isLast","jsxs","MoreHorizontal","ChevronRight","item","labelNode"],"mappings":";;;;;;AAsCO,MAAMA,IAAcC,EAA0C,SACnE,EAAE,OAAAC,GAAO,UAAAC,IAAW,GAAG,YAAAC,GAAY,WAAAC,GAAW,GAAGC,EAAA,GACjDC,GACA;AACA,QAAMC,IAAUC,EAAA,GAEVC,IADiBR,EAAM,SAASC,IAIhC,CAACD,EAAM,CAAC,GAAG,EAAE,WAAW,MAAiB,GAAGA,EAAM,MAAM,EAAE,CAAC,IAD3DA;AAGN,SACE,gBAAAS,EAAC,OAAA,EAAI,KAAAJ,GAAU,cAAYD,EAAM,YAAY,KAAKE,EAAQ,YAAY,OAAO,WAAWI,EAAG,WAAWP,CAAS,GAAI,GAAGC,GACpH,UAAA,gBAAAK,EAAC,MAAA,EAAG,WAAU,8DACX,UAAAD,EAAa,IAAI,CAACG,GAAIC,MAAM;AAC3B,UAAMC,IAASD,MAAMJ,EAAa,SAAS;AAC3C,QAAI,eAAeG;AACjB,aACE,gBAAAG,EAAC,MAAA,EAAyB,WAAU,6BAClC,UAAA;AAAA,QAAA,gBAAAL,EAACM,GAAA,EAAe,WAAU,UAAS,eAAW,IAAC;AAAA,QAC/C,gBAAAN,EAACO,GAAA,EAAa,WAAU,YAAW,eAAW,GAAA,CAAC;AAAA,MAAA,EAAA,GAFxC,YAAYJ,CAAC,EAGtB;AAGJ,UAAMK,IAAON,GACPO,IAAYL,IAChB,gBAAAJ,EAAC,UAAK,gBAAa,QAAO,WAAU,+BACjC,UAAAQ,EAAK,OACR,IACEA,EAAK,OACPf,IACEA,EAAWe,EAAK,MAAMA,EAAK,KAAK,IAEhC,gBAAAR;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAMQ,EAAK;AAAA,QACX,WAAU;AAAA,QAET,UAAAA,EAAK;AAAA,MAAA;AAAA,IAAA,IAIV,gBAAAR,EAAC,QAAA,EAAM,UAAAQ,EAAK,MAAA,CAAM;AAEpB,WACE,gBAAAH,EAAC,MAAA,EAAW,WAAU,6BACnB,UAAA;AAAA,MAAAI;AAAA,MACA,CAACL,KAAU,gBAAAJ,EAACO,KAAa,WAAU,YAAW,eAAW,GAAA,CAAC;AAAA,IAAA,EAAA,GAFpDJ,CAGT;AAAA,EAEJ,CAAC,GACH,GACF;AAEJ,CAAC;AACDd,EAAY,cAAc;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
2
|
-
import { VariantProps } from '../../lib/cva';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
3
|
declare const button: (props?: ({
|
|
4
4
|
variant?: "link" | "outline" | "primary" | "secondary" | "ghost" | "destructive" | null | undefined;
|
|
5
5
|
size?: "icon" | "md" | "sm" | "lg" | null | undefined;
|