@azmr/ui 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -0
- package/dist/avatar.d.ts +58 -0
- package/dist/avatar.js +84 -0
- package/dist/breadcrumb.d.ts +47 -0
- package/dist/breadcrumb.js +120 -0
- package/dist/button-CYt2Zw3D.d.ts +64 -0
- package/dist/button.d.ts +54 -0
- package/dist/button.js +19 -0
- package/dist/chunk-2RIWL363.js +27 -0
- package/dist/chunk-2TQSHVXA.js +12 -0
- package/dist/chunk-7EOSDFS3.js +10 -0
- package/dist/chunk-C2HKIPLQ.js +118 -0
- package/dist/chunk-CAJLW6K2.js +16 -0
- package/dist/chunk-CZJZYN54.js +36 -0
- package/dist/chunk-EE4V6XMB.js +35 -0
- package/dist/chunk-EFHRRLAW.js +211 -0
- package/dist/chunk-GLB75NT3.js +21 -0
- package/dist/chunk-GX2YEJ43.js +36 -0
- package/dist/chunk-LTOLV67A.js +19 -0
- package/dist/chunk-LZMX27IZ.js +10 -0
- package/dist/chunk-NIIDYIZA.js +1 -0
- package/dist/chunk-Q4PPM2SU.js +97 -0
- package/dist/chunk-WVE2E37B.js +20 -0
- package/dist/chunk-XRZHP6BD.js +92 -0
- package/dist/disclosure.d.ts +72 -0
- package/dist/disclosure.js +221 -0
- package/dist/display.d.ts +111 -0
- package/dist/display.js +237 -0
- package/dist/features-dom-MCGKRLN5.js +7 -0
- package/dist/features-max-COBTQCO6.js +7 -0
- package/dist/floating.d.ts +81 -0
- package/dist/floating.js +231 -0
- package/dist/hooks.d.ts +33 -0
- package/dist/hooks.js +55 -0
- package/dist/icons/lucide.d.ts +77 -0
- package/dist/icons/lucide.js +135 -0
- package/dist/icons/mui-map.d.ts +1268 -0
- package/dist/icons/mui-map.js +352 -0
- package/dist/icons.d.ts +48 -0
- package/dist/icons.js +19 -0
- package/dist/index.d.ts +238 -3
- package/dist/index.js +242 -15
- package/dist/input.d.ts +14 -0
- package/dist/input.js +8 -0
- package/dist/layout/mui-map.d.ts +80 -0
- package/dist/layout/mui-map.js +104 -0
- package/dist/layout.d.ts +89 -0
- package/dist/layout.js +455 -0
- package/dist/lib.d.ts +11 -0
- package/dist/lib.js +6 -0
- package/dist/menu.d.ts +105 -0
- package/dist/menu.js +56 -0
- package/dist/modal.d.ts +105 -0
- package/dist/modal.js +273 -0
- package/dist/motion.css +295 -0
- package/dist/motion.d.ts +49 -0
- package/dist/motion.js +48 -0
- package/dist/pagination.d.ts +82 -0
- package/dist/pagination.js +211 -0
- package/dist/presets.d.ts +97 -0
- package/dist/presets.js +94 -0
- package/dist/progress.d.ts +70 -0
- package/dist/progress.js +138 -0
- package/dist/react.d.ts +81 -0
- package/dist/react.js +281 -0
- package/dist/responsive-YJBnvsjt.d.ts +69 -0
- package/dist/scroll-area.d.ts +30 -0
- package/dist/scroll-area.js +80 -0
- package/dist/shared-Fkx-sjPt.d.ts +15 -0
- package/dist/spinner.d.ts +18 -0
- package/dist/spinner.js +39 -0
- package/dist/split-button.d.ts +32 -0
- package/dist/split-button.js +54 -0
- package/dist/stepper.d.ts +27 -0
- package/dist/stepper.js +104 -0
- package/dist/styles.css +24 -0
- package/dist/table.d.ts +52 -0
- package/dist/table.js +124 -0
- package/dist/theme.d.ts +111 -0
- package/dist/theme.js +414 -0
- package/dist/toast.d.ts +77 -0
- package/dist/toast.js +203 -0
- package/dist/toggle.d.ts +55 -0
- package/dist/toggle.js +88 -0
- package/dist/tokens.css +337 -0
- package/dist/transition.d.ts +51 -0
- package/dist/transition.js +79 -0
- package/dist/types-BBudf463.d.ts +39 -0
- package/dist/typography/mui-map.d.ts +91 -0
- package/dist/typography/mui-map.js +62 -0
- package/dist/typography.d.ts +90 -0
- package/dist/typography.js +193 -0
- package/dist/useSignal-C-3ZGURN.d.ts +21 -0
- package/dist/variants-De0c0ucO.d.ts +60 -0
- package/package.json +179 -13
package/README.md
CHANGED
|
@@ -10,6 +10,177 @@ pnpm add @azmr/ui @azmr/core react react-dom
|
|
|
10
10
|
npm install @azmr/ui @azmr/core react react-dom
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
`@azmr/core` is a **peer dependency**: install it yourself so your app and `@azmr/ui` share one copy (two copies give incompatible `Signal` types).
|
|
14
|
+
|
|
15
|
+
## Styles and design tokens
|
|
16
|
+
|
|
17
|
+
`@azmr/ui` ships the design tokens and the Tailwind v4 theme mapping in two stylesheets. In your global CSS, pick one:
|
|
18
|
+
|
|
19
|
+
```css
|
|
20
|
+
@import "tailwindcss";
|
|
21
|
+
@import "@azmr/ui/styles.css"; /* tokens + global base rules (new app) */
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```css
|
|
25
|
+
@import "tailwindcss";
|
|
26
|
+
@import "@azmr/ui/tokens.css"; /* tokens only (app that already has global styles) */
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **`styles.css`** is `tokens.css` plus two global base rules: the page background and text colour on `body`, and our border and outline colours as the default for every element. Use it in a new app.
|
|
30
|
+
- **`tokens.css`** has the same tokens, themes, density, motion and utilities and **changes nothing outside our own classes**. Use it when the app already has global styles: another UI library mid-migration, or a documentation site. Components look right either way.
|
|
31
|
+
|
|
32
|
+
- **Token names follow shadcn/ui** (`--background`, `--primary`, `--ring`, `--radius`, `--sidebar-*`, ...), so shadcn components from any registry look right next to ours. Azmara adds `--success`, `--warning`, `--info`, `--chart-1` to `--chart-8`, `--motion-*`, `--elevation-*`, `--z-*` and `--density-*`.
|
|
33
|
+
- **Utilities** such as `bg-background`, `text-muted-foreground`, `rounded-lg`, `shadow-elevation-2` and `ease-standard` work out of the box. The stylesheets include an `@source` rule, so Tailwind scans the package's classes inside `node_modules`.
|
|
34
|
+
- **Light and dark** use OKLCH colours with `light-dark()`. The page follows the system by default; force a mode with `class="dark"` / `class="light"` or `data-theme="dark|light"` on any ancestor. No JavaScript, no flash.
|
|
35
|
+
- **Density:** set `data-density="compact"` or `"spacious"` on any ancestor to change control heights, padding and row height.
|
|
36
|
+
- **Reduced motion:** with `prefers-reduced-motion: reduce`, every `--motion-duration-*` token collapses to `0.01ms` and a safety net shortens other animations. Mark genuinely essential motion with `data-motion="essential"`.
|
|
37
|
+
- **Accessibility is tested:** every foreground/background token pair meets WCAG AA text contrast (4.5:1) in both modes, the focus ring and chart colours meet 3:1, and the eight chart colours stay distinguishable under protanopia, deuteranopia and tritanopia simulation.
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
## Theme, density and locale
|
|
41
|
+
|
|
42
|
+
**Server layout (Next.js App Router).** Read the stored preference from the cookie on the server and render it into `<html>`, so the first paint is already correct, with no inline script (works under a strict CSP):
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// app/layout.tsx
|
|
46
|
+
import { cookies } from "next/headers";
|
|
47
|
+
import { htmlAttributes, parseDensity, parseThemePreference, THEME_COOKIE, DENSITY_COOKIE } from "@azmr/ui/theme";
|
|
48
|
+
import { LocaleProvider, ThemeProvider } from "@azmr/ui/react";
|
|
49
|
+
|
|
50
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
51
|
+
const jar = await cookies();
|
|
52
|
+
const theme = parseThemePreference(jar.get(THEME_COOKIE)?.value);
|
|
53
|
+
const density = parseDensity(jar.get(DENSITY_COOKIE)?.value);
|
|
54
|
+
return (
|
|
55
|
+
<html lang="en-NZ" {...htmlAttributes({ theme, density })}>
|
|
56
|
+
<body>
|
|
57
|
+
<ThemeProvider defaultTheme={theme} defaultDensity={density}>
|
|
58
|
+
<LocaleProvider locale="en-NZ" currency="NZD">{children}</LocaleProvider>
|
|
59
|
+
</ThemeProvider>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- `system` (the default) needs no attribute and no JavaScript: the tokens use `light-dark()`, so the page follows the operating system.
|
|
67
|
+
- `useTheme()` returns `{ theme, resolvedTheme, setTheme, density, setDensity }`. Changes update `<html>` immediately and are remembered in a cookie (`persist={false}` turns that off).
|
|
68
|
+
- Static sites that cannot read cookies on the server can inline `themeInitScript()` instead (add your CSP nonce to the tag).
|
|
69
|
+
- `createTheme({ primary, secondary, success, warning, info, destructive, radius, font, density })` turns brand colours (`#rrggbb`, `oklch(L C H)` or `{ l, c, h }`) into token overrides. Every colour option is optional and keeps its default when omitted (`destructive` is the "danger" colour). It guarantees WCAG AA in both modes, adjusting lightness where needed and keeping the hue:
|
|
70
|
+
- each `-foreground` is at least 4.5:1 on its colour, **including on a solid button's hover surface** (a button blends with the page on hover, which can pull a colour that only just passes below 4.5:1; the derivation checks the blend);
|
|
71
|
+
- `success`, `info` and `destructive` are also readable as **text** on the page and on cards (`Text tone`, form errors), and `warning` gets a separate readable `--warning-text`;
|
|
72
|
+
- the focus ring is at least 3:1.
|
|
73
|
+
Input is validated strictly because the output is CSS, and an error names the option that was wrong.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { createTheme } from "@azmr/ui/theme";
|
|
77
|
+
const theme = createTheme({
|
|
78
|
+
primary: "#1E88E5",
|
|
79
|
+
success: "#2E7D32",
|
|
80
|
+
destructive: "#D32F2F",
|
|
81
|
+
radius: "0.5rem",
|
|
82
|
+
});
|
|
83
|
+
// <style>{theme.css}</style> or <html style={theme.cssVariables}>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Locale.** `LocaleProvider` defaults to `en-NZ`, `NZD` and a Monday week start. `useLocale()` gives cached `Intl` formatters (`formatNumber`, `formatCurrency`, `formatDate`, `formatDateTime`) and the strings components show (`messages`, overridable). Date-only strings such as `2026-10-15` print that calendar day in every time zone; missing or invalid values format to an empty string instead of throwing.
|
|
87
|
+
|
|
88
|
+
## Components
|
|
89
|
+
|
|
90
|
+
Components read the design tokens only (no raw colours), accept `className` (merged last, so it can override defaults), forward `ref` to the real DOM element, and follow the density tokens for height and padding. Import from the root or from a subpath; subpaths and the root are both tree-shakeable.
|
|
91
|
+
|
|
92
|
+
### Button
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
import { Button } from "@azmr/ui/button";
|
|
96
|
+
|
|
97
|
+
<Button>Save</Button>
|
|
98
|
+
<Button variant="destructive" loading loadingText="Deleting">Delete</Button>
|
|
99
|
+
<Button variant="outline" startIcon={<PlusIcon />}>New customer</Button>
|
|
100
|
+
<Button size="icon" aria-label="Close"><XIcon /></Button> {/* icon-only needs a name */}
|
|
101
|
+
<Button nativeButton={false} render={<a href="/docs" />}>Docs</Button> {/* renders a link */}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
- **Variants:** `default`, `secondary`, `outline`, `ghost`, `destructive`, `success`, `warning`, `info`, `link`. **Sizes:** `sm`, `md` (follows the density token), `lg`, `icon`.
|
|
105
|
+
- **`loading`:** shows a spinner, sets `aria-busy`, ignores clicks and keyboard activation, **stays focusable** (keyboard users keep their place). `loadingText` replaces the label while loading; `startIcon` is replaced by the spinner, `endIcon` stays.
|
|
106
|
+
- **`type` is `"button"` by default**, so a button inside a form never submits by accident; pass `type="submit"` explicitly.
|
|
107
|
+
- **Icon-only buttons are type-checked:** `size="icon"` requires `aria-label` or `aria-labelledby`.
|
|
108
|
+
|
|
109
|
+
### Input
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
import { Input } from "@azmr/ui/input";
|
|
113
|
+
import { useForm } from "react-hook-form";
|
|
114
|
+
|
|
115
|
+
const { register } = useForm();
|
|
116
|
+
<label htmlFor="email">Email</label>
|
|
117
|
+
<Input id="email" type="email" {...register("email")} /> {/* no Controller needed */}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`ref` is the real `<input>`, so `register()` works directly. Native `onChange` and the typed `onValueChange(value)` both fire. `aria-invalid="true"` styles the invalid state; focus uses a full-strength ring (WCAG 2.2 focus appearance).
|
|
121
|
+
|
|
122
|
+
### Spinner
|
|
123
|
+
|
|
124
|
+
`<Spinner />` is decorative; `<Spinner label="Loading customers" />` is announced as a status. It keeps spinning under `prefers-reduced-motion` because a frozen spinner reads as a hung page.
|
|
125
|
+
|
|
126
|
+
## Icons
|
|
127
|
+
|
|
128
|
+
`Icon` renders any icon component with consistent size, stroke, motion and accessibility. It is a **Server Component** (no hooks, no `"use client"`), so it works in `loading.tsx`, layouts and Suspense fallbacks.
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { Icon } from "@azmr/ui/icons";
|
|
132
|
+
import { Search } from "lucide-react";
|
|
133
|
+
import { IconBell } from "@tabler/icons-react"; // optional: any set, or your own SVG component
|
|
134
|
+
|
|
135
|
+
<Icon icon={Search} /> {/* decorative: aria-hidden */}
|
|
136
|
+
<Icon icon={Search} aria-label="Search" size="lg" /> {/* meaningful: role="img" + name */}
|
|
137
|
+
<Icon icon={IconBell} strokeWidth={1.5} />
|
|
138
|
+
<Icon icon={Loader} spin /> {/* keeps spinning under reduced motion */}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
- **Sizes:** `xs`, `sm`, `md` (default), `lg`, `xl` follow the size tokens; a number is a pixel size. `className` merges last, so `className="size-8"` wins.
|
|
142
|
+
- **Accessible by default:** decorative (`aria-hidden`) unless you pass `aria-label` or `aria-labelledby`. An icon-only button is named by a labelled icon.
|
|
143
|
+
- **Bring any set.** Lucide (ISC) is the default; Tabler (MIT) and your own SVG components work through the same `icon` prop with no adapter. `react-icons` is deliberately not bundled: it mixes licences and only tree-shakes through exact subpath imports.
|
|
144
|
+
- **Tree-shaking is tested:** the Icon core bundles no icon at all, and an app that imports one icon bundles that icon only.
|
|
145
|
+
|
|
146
|
+
**By name.** Three ways, from most to least isolated:
|
|
147
|
+
|
|
148
|
+
```tsx
|
|
149
|
+
// 1. The curated Lucide set, with typed names (about 60 everyday icons; Lucide's own kebab-case names)
|
|
150
|
+
import { Icon } from "@azmr/ui/icons/lucide";
|
|
151
|
+
<Icon name="circle-alert" />
|
|
152
|
+
|
|
153
|
+
// 2. Your own typed set: nothing global, only the icons you list are bundled
|
|
154
|
+
import { createIconSet } from "@azmr/ui/icons";
|
|
155
|
+
export const { Icon } = createIconSet({ check: Check, "my-logo": MyLogo });
|
|
156
|
+
|
|
157
|
+
// 3. A global registry for custom SVGs (names are untyped strings)
|
|
158
|
+
import { registerIcons } from "@azmr/ui/icons";
|
|
159
|
+
registerIcons({ "my-logo": MyLogo }); // then <Icon name="my-logo" />
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
An unknown name renders nothing and logs one development warning; it never crashes the page.
|
|
163
|
+
|
|
164
|
+
**Migrating from `@mui/icons-material`.** `@azmr/ui/icons/mui-map` is plain data: `resolveMuiIcon("SearchOutlined")` returns `{ name: "Search", variant: "outlined", lucide: "search", tabler: "IconSearch" }`. It covers over 300 common Material icons, strips the `Outlined` / `Rounded` / `Sharp` / `TwoTone` variants (and the legacy `Outline` spelling older MUI versions export), maps brand icons to Tabler only (Lucide has none), and lists the common names it deliberately does not map in `unmappedMuiIcons`. Every name in it is checked by tests against the real `@mui/icons-material`, `lucide-react` and `@tabler/icons-react`. The migration codemod uses it and reports anything unmapped instead of guessing.
|
|
165
|
+
|
|
166
|
+
## Entry points
|
|
167
|
+
|
|
168
|
+
| Import | Contents | Directive |
|
|
169
|
+
|---|---|---|
|
|
170
|
+
| `@azmr/ui` | everything (`Grid`, `useSignal`, `cn`) | `"use client"` |
|
|
171
|
+
| `@azmr/ui/hooks` | React hooks (`useSignal`) | `"use client"` |
|
|
172
|
+
| `@azmr/ui/theme` | server-safe theme helpers: `createTheme`, cookies, `htmlAttributes`, `createLocale` | none, importable from Server Components and middleware |
|
|
173
|
+
| `@azmr/ui/react` | `ThemeProvider`, `useTheme`, `LocaleProvider`, `useLocale` | `"use client"` |
|
|
174
|
+
| `@azmr/ui/button` | `Button`, `buttonVariants` | `"use client"` |
|
|
175
|
+
| `@azmr/ui/input` | `Input` | `"use client"` |
|
|
176
|
+
| `@azmr/ui/spinner` | `Spinner` | none, importable from Server Components |
|
|
177
|
+
| `@azmr/ui/icons` | `Icon`, `createIconSet`, `registerIcons` | none, importable from Server Components |
|
|
178
|
+
| `@azmr/ui/icons/lucide` | `Icon` bound to the curated Lucide set, `lucideIcons` | none, importable from Server Components |
|
|
179
|
+
| `@azmr/ui/icons/mui-map` | `resolveMuiIcon`, `muiIconMap`, `unmappedMuiIcons` (migration data) | none, pure data |
|
|
180
|
+
| `@azmr/ui/lib` | `cn()` and other server-safe helpers | none, importable from Server Components |
|
|
181
|
+
|
|
182
|
+
`cn(...classes)` merges class names and resolves conflicting Tailwind utilities, last one wins: `cn("p-2", "p-4")` → `"p-4"`.
|
|
183
|
+
|
|
13
184
|
## Grid
|
|
14
185
|
|
|
15
186
|
```tsx
|
package/dist/avatar.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode, HTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Up to two initials from a name: the first character of the first two words, uppercased.
|
|
6
|
+
* Counts whole characters (so a surrogate pair or an emoji is never cut in half).
|
|
7
|
+
*/
|
|
8
|
+
declare function getInitials(name: string): string;
|
|
9
|
+
declare const SIZE: {
|
|
10
|
+
readonly sm: "size-6 text-[0.625rem]";
|
|
11
|
+
readonly md: "size-10 text-sm";
|
|
12
|
+
readonly lg: "size-14 text-base";
|
|
13
|
+
readonly xl: "size-20 text-xl";
|
|
14
|
+
};
|
|
15
|
+
declare const SHAPE: {
|
|
16
|
+
readonly circle: "rounded-full";
|
|
17
|
+
readonly square: "rounded-md";
|
|
18
|
+
};
|
|
19
|
+
type AvatarSize = keyof typeof SIZE;
|
|
20
|
+
interface AvatarOwnProps {
|
|
21
|
+
/** The image. When it fails to load (or is absent) the fallback is shown. */
|
|
22
|
+
src?: string;
|
|
23
|
+
size?: AvatarSize;
|
|
24
|
+
shape?: keyof typeof SHAPE;
|
|
25
|
+
/** What to show instead of the image. Default: the initials of `name`, or a placeholder. */
|
|
26
|
+
fallback?: ReactNode;
|
|
27
|
+
/** Wait this long before showing the fallback, to avoid a flash while the image loads. */
|
|
28
|
+
delayMs?: number;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
/** An avatar must say who it is, or be explicitly decorative. */
|
|
32
|
+
type Identity = {
|
|
33
|
+
name: string;
|
|
34
|
+
alt?: string;
|
|
35
|
+
decorative?: boolean;
|
|
36
|
+
} | {
|
|
37
|
+
alt: string;
|
|
38
|
+
name?: string;
|
|
39
|
+
decorative?: boolean;
|
|
40
|
+
} | {
|
|
41
|
+
decorative: true;
|
|
42
|
+
name?: string;
|
|
43
|
+
alt?: string;
|
|
44
|
+
};
|
|
45
|
+
type AvatarProps = AvatarOwnProps & Identity & Omit<HTMLAttributes<HTMLSpanElement>, keyof AvatarOwnProps | "role" | "aria-label">;
|
|
46
|
+
/** A person or entity as an image with initials as the fallback. Requires a name, alt or `decorative`. */
|
|
47
|
+
declare const Avatar: React.ForwardRefExoticComponent<AvatarProps & React.RefAttributes<HTMLSpanElement>>;
|
|
48
|
+
interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
49
|
+
/** Show at most this many avatars, then a `+N` for the rest. */
|
|
50
|
+
max?: number;
|
|
51
|
+
/** The accessible name of the `+N` avatar. Default `N more`. */
|
|
52
|
+
overflowLabel?: (hidden: number) => string;
|
|
53
|
+
children?: ReactNode;
|
|
54
|
+
}
|
|
55
|
+
/** Overlapping avatars with a labelled `+N` for the rest. */
|
|
56
|
+
declare const AvatarGroup: react.ForwardRefExoticComponent<AvatarGroupProps & react.RefAttributes<HTMLDivElement>>;
|
|
57
|
+
|
|
58
|
+
export { Avatar, AvatarGroup, type AvatarGroupProps, type AvatarProps, type AvatarSize, getInitials };
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import {
|
|
3
|
+
cn
|
|
4
|
+
} from "./chunk-2TQSHVXA.js";
|
|
5
|
+
|
|
6
|
+
// src/components/avatar/avatar.tsx
|
|
7
|
+
import { Children, forwardRef } from "react";
|
|
8
|
+
|
|
9
|
+
// src/primitives/avatar.ts
|
|
10
|
+
import { Avatar } from "@base-ui/react/avatar";
|
|
11
|
+
|
|
12
|
+
// src/components/avatar/avatar.tsx
|
|
13
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
14
|
+
function getInitials(name) {
|
|
15
|
+
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
16
|
+
const first = (word) => Array.from(word)[0] ?? "";
|
|
17
|
+
return words.slice(0, 2).map(first).join("").toLocaleUpperCase();
|
|
18
|
+
}
|
|
19
|
+
var SIZE = {
|
|
20
|
+
sm: "size-6 text-[0.625rem]",
|
|
21
|
+
md: "size-10 text-sm",
|
|
22
|
+
lg: "size-14 text-base",
|
|
23
|
+
xl: "size-20 text-xl"
|
|
24
|
+
};
|
|
25
|
+
var SHAPE = { circle: "rounded-full", square: "rounded-md" };
|
|
26
|
+
var AvatarImpl = forwardRef(function Avatar2({
|
|
27
|
+
src,
|
|
28
|
+
name,
|
|
29
|
+
alt,
|
|
30
|
+
decorative = false,
|
|
31
|
+
size = "md",
|
|
32
|
+
shape = "circle",
|
|
33
|
+
fallback,
|
|
34
|
+
delayMs,
|
|
35
|
+
className,
|
|
36
|
+
...props
|
|
37
|
+
}, ref) {
|
|
38
|
+
const label = alt ?? name;
|
|
39
|
+
return /* @__PURE__ */ jsxs(
|
|
40
|
+
Avatar.Root,
|
|
41
|
+
{
|
|
42
|
+
ref,
|
|
43
|
+
...decorative ? { "aria-hidden": true } : { role: "img", "aria-label": label },
|
|
44
|
+
className: cn(
|
|
45
|
+
"relative inline-flex shrink-0 select-none items-center justify-center overflow-hidden bg-muted font-medium text-muted-foreground",
|
|
46
|
+
SIZE[size],
|
|
47
|
+
SHAPE[shape],
|
|
48
|
+
className
|
|
49
|
+
),
|
|
50
|
+
...props,
|
|
51
|
+
children: [
|
|
52
|
+
src && /* @__PURE__ */ jsx(Avatar.Image, { src, alt: "", className: "aspect-square size-full object-cover" }),
|
|
53
|
+
/* @__PURE__ */ jsx(Avatar.Fallback, { delay: delayMs, "aria-hidden": "true", children: fallback ?? (name ? getInitials(name) : "?") })
|
|
54
|
+
]
|
|
55
|
+
}
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
var Avatar3 = AvatarImpl;
|
|
59
|
+
var AvatarGroup = forwardRef(function AvatarGroup2({ max, overflowLabel = (n) => `${n} more`, className, children, ...props }, ref) {
|
|
60
|
+
const all = Children.toArray(children);
|
|
61
|
+
const visible = max === void 0 ? all : all.slice(0, Math.max(0, max));
|
|
62
|
+
const hidden = all.length - visible.length;
|
|
63
|
+
return (
|
|
64
|
+
// biome-ignore lint/a11y/useSemanticElements: a fieldset groups form controls; this groups images
|
|
65
|
+
/* @__PURE__ */ jsxs(
|
|
66
|
+
"div",
|
|
67
|
+
{
|
|
68
|
+
ref,
|
|
69
|
+
role: "group",
|
|
70
|
+
className: cn("flex -space-x-2 [&>*]:ring-2 [&>*]:ring-background", className),
|
|
71
|
+
...props,
|
|
72
|
+
children: [
|
|
73
|
+
visible,
|
|
74
|
+
hidden > 0 && /* @__PURE__ */ jsx(Avatar3, { alt: overflowLabel(hidden), fallback: `+${hidden}` })
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
)
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
export {
|
|
81
|
+
Avatar3 as Avatar,
|
|
82
|
+
AvatarGroup,
|
|
83
|
+
getInitials
|
|
84
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { HTMLAttributes, ReactNode, LiHTMLAttributes, AnchorHTMLAttributes, ReactElement, OlHTMLAttributes } from 'react';
|
|
3
|
+
|
|
4
|
+
interface BreadcrumbProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** The landmark's name. Default "Breadcrumb"; change it for translation, or with two trails on a page. */
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
/** A navigation landmark holding the trail. Server-safe. */
|
|
9
|
+
declare const Breadcrumb: react.ForwardRefExoticComponent<BreadcrumbProps & react.RefAttributes<HTMLElement>>;
|
|
10
|
+
declare const BreadcrumbList: react.ForwardRefExoticComponent<OlHTMLAttributes<HTMLOListElement> & react.RefAttributes<HTMLOListElement>>;
|
|
11
|
+
declare const BreadcrumbItem: react.ForwardRefExoticComponent<LiHTMLAttributes<HTMLLIElement> & react.RefAttributes<HTMLLIElement>>;
|
|
12
|
+
interface BreadcrumbLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
13
|
+
/** Render your router's `Link` instead of an `<a>`: `render={<NextLink href="/x" />}`. */
|
|
14
|
+
render?: ReactElement;
|
|
15
|
+
}
|
|
16
|
+
declare const BreadcrumbLink: react.ForwardRefExoticComponent<BreadcrumbLinkProps & react.RefAttributes<HTMLAnchorElement>>;
|
|
17
|
+
/** Where you are. Plain text, not a link, marked `aria-current="page"`. */
|
|
18
|
+
declare const BreadcrumbPage: react.ForwardRefExoticComponent<HTMLAttributes<HTMLSpanElement> & react.RefAttributes<HTMLSpanElement>>;
|
|
19
|
+
/** The mark between items. Decorative: hidden from assistive technology. A chevron by default. */
|
|
20
|
+
declare function BreadcrumbSeparator({ children, className, ...props }: HTMLAttributes<HTMLLIElement>): react.JSX.Element;
|
|
21
|
+
/** Stands for items left out of a long trail. For a menu of the hidden pages, wrap it in a `DropdownMenu`. */
|
|
22
|
+
declare function BreadcrumbEllipsis({ label, className, ...props }: HTMLAttributes<HTMLSpanElement> & {
|
|
23
|
+
label?: string;
|
|
24
|
+
}): react.JSX.Element;
|
|
25
|
+
interface BreadcrumbEntry {
|
|
26
|
+
label: ReactNode;
|
|
27
|
+
/** Where the item links. The last item is always shown as the current page, whatever it has. */
|
|
28
|
+
href?: string;
|
|
29
|
+
}
|
|
30
|
+
interface BreadcrumbsProps extends Omit<BreadcrumbProps, "children"> {
|
|
31
|
+
items: readonly BreadcrumbEntry[];
|
|
32
|
+
/**
|
|
33
|
+
* Collapse a longer trail: keep the first item, an ellipsis, and the last items, `maxItems` in all
|
|
34
|
+
* (the ellipsis counts as one). Default: never collapse.
|
|
35
|
+
*/
|
|
36
|
+
maxItems?: number;
|
|
37
|
+
/** Replace the chevron between items. */
|
|
38
|
+
separator?: ReactNode;
|
|
39
|
+
/** Render your router's `Link`: `renderLink={(href) => <NextLink href={href} />}`. */
|
|
40
|
+
renderLink?: (href: string) => ReactElement;
|
|
41
|
+
/** The ellipsis's spoken name. Default "More pages". */
|
|
42
|
+
ellipsisLabel?: string;
|
|
43
|
+
}
|
|
44
|
+
/** A trail from data. Built from the parts above; use those directly for custom layouts. */
|
|
45
|
+
declare function Breadcrumbs({ items, maxItems, separator, renderLink, ellipsisLabel, ...props }: BreadcrumbsProps): react.JSX.Element | null;
|
|
46
|
+
|
|
47
|
+
export { Breadcrumb, BreadcrumbEllipsis, type BreadcrumbEntry, BreadcrumbItem, BreadcrumbLink, type BreadcrumbLinkProps, BreadcrumbList, BreadcrumbPage, type BreadcrumbProps, BreadcrumbSeparator, Breadcrumbs, type BreadcrumbsProps };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
renderElement
|
|
3
|
+
} from "./chunk-LTOLV67A.js";
|
|
4
|
+
import {
|
|
5
|
+
Icon
|
|
6
|
+
} from "./chunk-Q4PPM2SU.js";
|
|
7
|
+
import {
|
|
8
|
+
cn
|
|
9
|
+
} from "./chunk-7EOSDFS3.js";
|
|
10
|
+
|
|
11
|
+
// src/components/breadcrumb/breadcrumb.tsx
|
|
12
|
+
import { ChevronRight, Ellipsis } from "lucide-react";
|
|
13
|
+
import {
|
|
14
|
+
Fragment,
|
|
15
|
+
forwardRef
|
|
16
|
+
} from "react";
|
|
17
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
18
|
+
var Breadcrumb = forwardRef(function Breadcrumb2({ label = "Breadcrumb", ...props }, ref) {
|
|
19
|
+
return /* @__PURE__ */ jsx("nav", { ref, "aria-label": label, ...props });
|
|
20
|
+
});
|
|
21
|
+
var BreadcrumbList = forwardRef(
|
|
22
|
+
function BreadcrumbList2({ className, ...props }, ref) {
|
|
23
|
+
return /* @__PURE__ */ jsx(
|
|
24
|
+
"ol",
|
|
25
|
+
{
|
|
26
|
+
ref,
|
|
27
|
+
className: cn(
|
|
28
|
+
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground",
|
|
29
|
+
className
|
|
30
|
+
),
|
|
31
|
+
...props
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
var BreadcrumbItem = forwardRef(
|
|
37
|
+
function BreadcrumbItem2({ className, ...props }, ref) {
|
|
38
|
+
return /* @__PURE__ */ jsx("li", { ref, className: cn("inline-flex items-center gap-1.5", className), ...props });
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
var LINK = "rounded-sm text-muted-foreground transition-colors duration-(--motion-duration-fast) hover:text-foreground outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
42
|
+
var BreadcrumbLink = forwardRef(
|
|
43
|
+
function BreadcrumbLink2({ render, className, children, ...props }, ref) {
|
|
44
|
+
return renderElement(render, "a", { ref, ...props, className: cn(LINK, className) }, children);
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
var BreadcrumbPage = forwardRef(
|
|
48
|
+
function BreadcrumbPage2({ className, ...props }, ref) {
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
"span",
|
|
51
|
+
{
|
|
52
|
+
ref,
|
|
53
|
+
"aria-current": "page",
|
|
54
|
+
className: cn("font-medium text-foreground", className),
|
|
55
|
+
...props
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
function BreadcrumbSeparator({
|
|
61
|
+
children,
|
|
62
|
+
className,
|
|
63
|
+
...props
|
|
64
|
+
}) {
|
|
65
|
+
return /* @__PURE__ */ jsx(
|
|
66
|
+
"li",
|
|
67
|
+
{
|
|
68
|
+
role: "presentation",
|
|
69
|
+
"aria-hidden": "true",
|
|
70
|
+
"data-azmr-separator": "",
|
|
71
|
+
className: cn("[&>svg]:size-3.5", className),
|
|
72
|
+
...props,
|
|
73
|
+
children: children ?? /* @__PURE__ */ jsx(Icon, { icon: ChevronRight })
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
function BreadcrumbEllipsis({
|
|
78
|
+
label = "More pages",
|
|
79
|
+
className,
|
|
80
|
+
...props
|
|
81
|
+
}) {
|
|
82
|
+
return /* @__PURE__ */ jsxs("span", { className: cn("flex size-9 items-center justify-center", className), ...props, children: [
|
|
83
|
+
/* @__PURE__ */ jsx(Icon, { icon: Ellipsis }),
|
|
84
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: label })
|
|
85
|
+
] });
|
|
86
|
+
}
|
|
87
|
+
function Breadcrumbs({
|
|
88
|
+
items,
|
|
89
|
+
maxItems,
|
|
90
|
+
separator,
|
|
91
|
+
renderLink,
|
|
92
|
+
ellipsisLabel,
|
|
93
|
+
...props
|
|
94
|
+
}) {
|
|
95
|
+
if (items.length === 0) return null;
|
|
96
|
+
const collapse = maxItems !== void 0 && items.length > Math.max(maxItems, 2);
|
|
97
|
+
const lastCount = Math.max(1, (maxItems ?? 0) - 2);
|
|
98
|
+
const visible = collapse ? [items[0], "ellipsis", ...items.slice(items.length - lastCount)] : [...items];
|
|
99
|
+
return /* @__PURE__ */ jsx(Breadcrumb, { ...props, children: /* @__PURE__ */ jsx(BreadcrumbList, { children: visible.map((entry, index) => {
|
|
100
|
+
const isLast = index === visible.length - 1;
|
|
101
|
+
return (
|
|
102
|
+
// The position is the identity: the list is static and never reordered.
|
|
103
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: static, never reordered
|
|
104
|
+
/* @__PURE__ */ jsxs(Fragment, { children: [
|
|
105
|
+
/* @__PURE__ */ jsx(BreadcrumbItem, { children: entry === "ellipsis" ? /* @__PURE__ */ jsx(BreadcrumbEllipsis, { label: ellipsisLabel }) : isLast ? /* @__PURE__ */ jsx(BreadcrumbPage, { children: entry.label }) : entry.href ? /* @__PURE__ */ jsx(BreadcrumbLink, { href: entry.href, render: renderLink?.(entry.href), children: entry.label }) : /* @__PURE__ */ jsx("span", { children: entry.label }) }),
|
|
106
|
+
!isLast && /* @__PURE__ */ jsx(BreadcrumbSeparator, { children: separator })
|
|
107
|
+
] }, index)
|
|
108
|
+
);
|
|
109
|
+
}) }) });
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
Breadcrumb,
|
|
113
|
+
BreadcrumbEllipsis,
|
|
114
|
+
BreadcrumbItem,
|
|
115
|
+
BreadcrumbLink,
|
|
116
|
+
BreadcrumbList,
|
|
117
|
+
BreadcrumbPage,
|
|
118
|
+
BreadcrumbSeparator,
|
|
119
|
+
Breadcrumbs
|
|
120
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { VariantProps } from 'class-variance-authority';
|
|
4
|
+
import { ButtonProps as ButtonProps$1 } from '@base-ui/react/button';
|
|
5
|
+
import * as class_variance_authority_types from 'class-variance-authority/types';
|
|
6
|
+
|
|
7
|
+
/** The look of a button, as classes. Pure (no React), so server components can use it too. */
|
|
8
|
+
declare const buttonVariants: (props?: ({
|
|
9
|
+
variant?: "link" | "default" | "secondary" | "outline" | "ghost" | "destructive" | "success" | "warning" | "info" | null | undefined;
|
|
10
|
+
size?: "sm" | "md" | "lg" | "icon" | null | undefined;
|
|
11
|
+
} & class_variance_authority_types.ClassProp) | undefined) => string;
|
|
12
|
+
|
|
13
|
+
type ButtonVariant = NonNullable<VariantProps<typeof buttonVariants>["variant"]>;
|
|
14
|
+
type TextButtonSize = "sm" | "md" | "lg";
|
|
15
|
+
/**
|
|
16
|
+
* The props `Button` adds on top of a native button. Kept as its own interface so the docs can
|
|
17
|
+
* generate a props table from it. Everything else a button accepts (`onClick`, `type`, `disabled`,
|
|
18
|
+
* `render`, `nativeButton`, `focusableWhenDisabled`, any `aria-*`, ...) is passed through.
|
|
19
|
+
*/
|
|
20
|
+
interface ButtonOwnProps {
|
|
21
|
+
/** Visual style. `default` is the primary action; use `destructive` for irreversible ones. */
|
|
22
|
+
variant?: ButtonVariant;
|
|
23
|
+
/**
|
|
24
|
+
* `md` follows the density token, so it grows or shrinks with `data-density`. `icon` is a square
|
|
25
|
+
* button for an icon alone and requires `aria-label` or `aria-labelledby`.
|
|
26
|
+
*/
|
|
27
|
+
size?: "sm" | "md" | "lg" | "icon";
|
|
28
|
+
/** Show a spinner, mark the button busy and ignore activation. It stays focusable. */
|
|
29
|
+
loading?: boolean;
|
|
30
|
+
/** Replaces the label while `loading`. */
|
|
31
|
+
loadingText?: ReactNode;
|
|
32
|
+
/** Shown before the label; replaced by the spinner while `loading`. */
|
|
33
|
+
startIcon?: ReactNode;
|
|
34
|
+
/** Shown after the label. */
|
|
35
|
+
endIcon?: ReactNode;
|
|
36
|
+
}
|
|
37
|
+
interface BaseButtonProps extends Omit<ButtonProps$1, "className" | "children">, Omit<ButtonOwnProps, "size"> {
|
|
38
|
+
className?: string;
|
|
39
|
+
children?: ReactNode;
|
|
40
|
+
}
|
|
41
|
+
/** An icon-only button has no text, so it must be given an accessible name. */
|
|
42
|
+
type IconOnly = {
|
|
43
|
+
size: "icon";
|
|
44
|
+
} & ({
|
|
45
|
+
"aria-label": string;
|
|
46
|
+
} | {
|
|
47
|
+
"aria-labelledby": string;
|
|
48
|
+
});
|
|
49
|
+
type ButtonProps = BaseButtonProps & ({
|
|
50
|
+
size?: TextButtonSize;
|
|
51
|
+
} | IconOnly);
|
|
52
|
+
declare const Button: react.ForwardRefExoticComponent<(Omit<BaseButtonProps & {
|
|
53
|
+
size: "icon";
|
|
54
|
+
} & {
|
|
55
|
+
"aria-label": string;
|
|
56
|
+
}, "ref"> | Omit<BaseButtonProps & {
|
|
57
|
+
size: "icon";
|
|
58
|
+
} & {
|
|
59
|
+
"aria-labelledby": string;
|
|
60
|
+
}, "ref"> | Omit<BaseButtonProps & {
|
|
61
|
+
size?: TextButtonSize;
|
|
62
|
+
}, "ref">) & react.RefAttributes<HTMLButtonElement>>;
|
|
63
|
+
|
|
64
|
+
export { type ButtonOwnProps as B, Button as a, type ButtonProps as b, buttonVariants as c };
|
package/dist/button.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { B as ButtonOwnProps } from './button-CYt2Zw3D.js';
|
|
2
|
+
export { a as Button, b as ButtonProps, c as buttonVariants } from './button-CYt2Zw3D.js';
|
|
3
|
+
import * as react from 'react';
|
|
4
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
5
|
+
import { ButtonProps } from '@base-ui/react/button';
|
|
6
|
+
import 'class-variance-authority';
|
|
7
|
+
import 'class-variance-authority/types';
|
|
8
|
+
|
|
9
|
+
type Named = {
|
|
10
|
+
"aria-label": string;
|
|
11
|
+
} | {
|
|
12
|
+
"aria-labelledby": string;
|
|
13
|
+
};
|
|
14
|
+
type ButtonGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "aria-label" | "aria-labelledby"> & Named & {
|
|
15
|
+
/** Lay the buttons out in a row (default) or a column. */
|
|
16
|
+
orientation?: "horizontal" | "vertical";
|
|
17
|
+
/** Join the buttons into one control (default), or keep them apart with a gap. */
|
|
18
|
+
attached?: boolean;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* A named group of related buttons, visually joined. It is only a grouping: Tab still visits every
|
|
22
|
+
* button. For a group that behaves as one control with arrow keys and a pressed state, use
|
|
23
|
+
* `ToggleGroup`. Server-safe.
|
|
24
|
+
*/
|
|
25
|
+
declare const ButtonGroup: react.ForwardRefExoticComponent<ButtonGroupProps & react.RefAttributes<HTMLDivElement>>;
|
|
26
|
+
|
|
27
|
+
declare const SQUARE: {
|
|
28
|
+
readonly sm: "size-8";
|
|
29
|
+
readonly md: "";
|
|
30
|
+
readonly lg: "size-11";
|
|
31
|
+
};
|
|
32
|
+
interface IconButtonProps extends Omit<ButtonProps, "className" | "children" | "aria-label" | "aria-labelledby"> {
|
|
33
|
+
/**
|
|
34
|
+
* The icon, as an element: `<Icon icon={Plus} />`. An element, not a component, so the button can be
|
|
35
|
+
* used from a Server Component (a function cannot be passed across that boundary).
|
|
36
|
+
*/
|
|
37
|
+
icon: ReactNode;
|
|
38
|
+
/**
|
|
39
|
+
* What the button does, as text: "Add item", "Close". It is the accessible name, and it is
|
|
40
|
+
* required because an icon alone cannot be announced.
|
|
41
|
+
*/
|
|
42
|
+
label: string;
|
|
43
|
+
/** Default `ghost`, so a row of icon buttons is quiet. */
|
|
44
|
+
variant?: ButtonOwnProps["variant"];
|
|
45
|
+
/** A square size. `md` follows the density token. Default `md`. */
|
|
46
|
+
size?: keyof typeof SQUARE;
|
|
47
|
+
/** Replace the icon with a spinner, mark the button busy and ignore activation. It stays focusable. */
|
|
48
|
+
loading?: boolean;
|
|
49
|
+
className?: string;
|
|
50
|
+
}
|
|
51
|
+
/** A square button with only an icon. The label is required and becomes its `aria-label`. */
|
|
52
|
+
declare const IconButton: react.ForwardRefExoticComponent<Omit<IconButtonProps, "ref"> & react.RefAttributes<HTMLButtonElement>>;
|
|
53
|
+
|
|
54
|
+
export { ButtonGroup, type ButtonGroupProps, ButtonOwnProps, IconButton, type IconButtonProps };
|
package/dist/button.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import "./chunk-NIIDYIZA.js";
|
|
3
|
+
import {
|
|
4
|
+
IconButton
|
|
5
|
+
} from "./chunk-CZJZYN54.js";
|
|
6
|
+
import {
|
|
7
|
+
Button,
|
|
8
|
+
buttonVariants
|
|
9
|
+
} from "./chunk-C2HKIPLQ.js";
|
|
10
|
+
import {
|
|
11
|
+
ButtonGroup
|
|
12
|
+
} from "./chunk-EE4V6XMB.js";
|
|
13
|
+
import "./chunk-2TQSHVXA.js";
|
|
14
|
+
export {
|
|
15
|
+
Button,
|
|
16
|
+
ButtonGroup,
|
|
17
|
+
IconButton,
|
|
18
|
+
buttonVariants
|
|
19
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Icon,
|
|
3
|
+
assertIconEntry,
|
|
4
|
+
warnMissingIcon
|
|
5
|
+
} from "./chunk-Q4PPM2SU.js";
|
|
6
|
+
|
|
7
|
+
// src/icons/create-icon-set.tsx
|
|
8
|
+
import { forwardRef } from "react";
|
|
9
|
+
import { jsx } from "react/jsx-runtime";
|
|
10
|
+
function createIconSet(icons) {
|
|
11
|
+
for (const [name, component] of Object.entries(icons)) assertIconEntry(name, component);
|
|
12
|
+
const own = Object.freeze({ ...icons });
|
|
13
|
+
const names = Object.freeze(Object.keys(own));
|
|
14
|
+
const SetIcon = forwardRef(function SetIcon2({ name, ...props }, ref) {
|
|
15
|
+
const icon = Object.hasOwn(own, name) ? own[name] : void 0;
|
|
16
|
+
if (!icon) {
|
|
17
|
+
warnMissingIcon(name);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return /* @__PURE__ */ jsx(Icon, { ref, icon, ...props });
|
|
21
|
+
});
|
|
22
|
+
return { Icon: SetIcon, names };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
createIconSet
|
|
27
|
+
};
|