@gerege-systems/ui 0.11.2
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/LICENSE +21 -0
- package/README.md +234 -0
- package/dist-lib/__tests__/helpers/color.d.ts +9 -0
- package/dist-lib/__tests__/helpers/theme.d.ts +18 -0
- package/dist-lib/components/ui/Accordion.d.ts +21 -0
- package/dist-lib/components/ui/Accordion.js +71 -0
- package/dist-lib/components/ui/Accordion.js.map +1 -0
- package/dist-lib/components/ui/Alert.d.ts +51 -0
- package/dist-lib/components/ui/Alert.js +79 -0
- package/dist-lib/components/ui/Alert.js.map +1 -0
- package/dist-lib/components/ui/Avatar.d.ts +52 -0
- package/dist-lib/components/ui/Avatar.js +94 -0
- package/dist-lib/components/ui/Avatar.js.map +1 -0
- package/dist-lib/components/ui/Badge.d.ts +26 -0
- package/dist-lib/components/ui/Badge.js +76 -0
- package/dist-lib/components/ui/Badge.js.map +1 -0
- package/dist-lib/components/ui/Breadcrumbs.d.ts +33 -0
- package/dist-lib/components/ui/Breadcrumbs.js +52 -0
- package/dist-lib/components/ui/Breadcrumbs.js.map +1 -0
- package/dist-lib/components/ui/Button.d.ts +52 -0
- package/dist-lib/components/ui/Button.js +131 -0
- package/dist-lib/components/ui/Button.js.map +1 -0
- package/dist-lib/components/ui/Calendar.d.ts +15 -0
- package/dist-lib/components/ui/Calendar.js +139 -0
- package/dist-lib/components/ui/Calendar.js.map +1 -0
- package/dist-lib/components/ui/Card.d.ts +39 -0
- package/dist-lib/components/ui/Card.js +97 -0
- package/dist-lib/components/ui/Card.js.map +1 -0
- package/dist-lib/components/ui/Carousel.d.ts +27 -0
- package/dist-lib/components/ui/Carousel.js +174 -0
- package/dist-lib/components/ui/Carousel.js.map +1 -0
- package/dist-lib/components/ui/Chart.d.ts +87 -0
- package/dist-lib/components/ui/Chart.js +487 -0
- package/dist-lib/components/ui/Chart.js.map +1 -0
- package/dist-lib/components/ui/Checkbox.d.ts +28 -0
- package/dist-lib/components/ui/Checkbox.js +70 -0
- package/dist-lib/components/ui/Checkbox.js.map +1 -0
- package/dist-lib/components/ui/Combobox.d.ts +67 -0
- package/dist-lib/components/ui/Combobox.js +195 -0
- package/dist-lib/components/ui/Combobox.js.map +1 -0
- package/dist-lib/components/ui/CommandPalette.d.ts +112 -0
- package/dist-lib/components/ui/CommandPalette.js +179 -0
- package/dist-lib/components/ui/CommandPalette.js.map +1 -0
- package/dist-lib/components/ui/ContextMenu.d.ts +50 -0
- package/dist-lib/components/ui/ContextMenu.js +165 -0
- package/dist-lib/components/ui/ContextMenu.js.map +1 -0
- package/dist-lib/components/ui/DataGrid.d.ts +79 -0
- package/dist-lib/components/ui/DataGrid.js +99 -0
- package/dist-lib/components/ui/DataGrid.js.map +1 -0
- package/dist-lib/components/ui/DatePicker.d.ts +45 -0
- package/dist-lib/components/ui/DatePicker.js +193 -0
- package/dist-lib/components/ui/DatePicker.js.map +1 -0
- package/dist-lib/components/ui/DesignSystemProvider.d.ts +147 -0
- package/dist-lib/components/ui/DesignSystemProvider.js +135 -0
- package/dist-lib/components/ui/DesignSystemProvider.js.map +1 -0
- package/dist-lib/components/ui/Dialog.d.ts +77 -0
- package/dist-lib/components/ui/Dialog.js +165 -0
- package/dist-lib/components/ui/Dialog.js.map +1 -0
- package/dist-lib/components/ui/Drawer.d.ts +44 -0
- package/dist-lib/components/ui/Drawer.js +132 -0
- package/dist-lib/components/ui/Drawer.js.map +1 -0
- package/dist-lib/components/ui/DropdownMenu.d.ts +47 -0
- package/dist-lib/components/ui/DropdownMenu.js +169 -0
- package/dist-lib/components/ui/DropdownMenu.js.map +1 -0
- package/dist-lib/components/ui/EmptyState.d.ts +48 -0
- package/dist-lib/components/ui/EmptyState.js +46 -0
- package/dist-lib/components/ui/EmptyState.js.map +1 -0
- package/dist-lib/components/ui/ErrorState.d.ts +51 -0
- package/dist-lib/components/ui/ErrorState.js +69 -0
- package/dist-lib/components/ui/ErrorState.js.map +1 -0
- package/dist-lib/components/ui/FileUpload.d.ts +26 -0
- package/dist-lib/components/ui/FileUpload.js +111 -0
- package/dist-lib/components/ui/FileUpload.js.map +1 -0
- package/dist-lib/components/ui/Form.d.ts +36 -0
- package/dist-lib/components/ui/Form.js +131 -0
- package/dist-lib/components/ui/Form.js.map +1 -0
- package/dist-lib/components/ui/Icon.d.ts +34 -0
- package/dist-lib/components/ui/Icon.js +32 -0
- package/dist-lib/components/ui/Icon.js.map +1 -0
- package/dist-lib/components/ui/IconButton.d.ts +34 -0
- package/dist-lib/components/ui/IconButton.js +64 -0
- package/dist-lib/components/ui/IconButton.js.map +1 -0
- package/dist-lib/components/ui/Input.d.ts +54 -0
- package/dist-lib/components/ui/Input.js +158 -0
- package/dist-lib/components/ui/Input.js.map +1 -0
- package/dist-lib/components/ui/Kbd.d.ts +16 -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 +64 -0
- package/dist-lib/components/ui/MultiSelect.js +210 -0
- package/dist-lib/components/ui/MultiSelect.js.map +1 -0
- package/dist-lib/components/ui/Pagination.d.ts +55 -0
- package/dist-lib/components/ui/Pagination.js +153 -0
- package/dist-lib/components/ui/Pagination.js.map +1 -0
- package/dist-lib/components/ui/Popover.d.ts +16 -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.d.ts +40 -0
- package/dist-lib/components/ui/Progress.js +100 -0
- package/dist-lib/components/ui/Progress.js.map +1 -0
- package/dist-lib/components/ui/RadioGroup.d.ts +27 -0
- package/dist-lib/components/ui/RadioGroup.js +81 -0
- package/dist-lib/components/ui/RadioGroup.js.map +1 -0
- package/dist-lib/components/ui/RelativeTime.d.ts +19 -0
- package/dist-lib/components/ui/RelativeTime.js +31 -0
- package/dist-lib/components/ui/RelativeTime.js.map +1 -0
- package/dist-lib/components/ui/ScrollArea.d.ts +21 -0
- package/dist-lib/components/ui/ScrollArea.js +60 -0
- package/dist-lib/components/ui/ScrollArea.js.map +1 -0
- package/dist-lib/components/ui/Select.d.ts +61 -0
- package/dist-lib/components/ui/Select.js +139 -0
- package/dist-lib/components/ui/Select.js.map +1 -0
- package/dist-lib/components/ui/Separator.d.ts +13 -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 +55 -0
- package/dist-lib/components/ui/Sheet.js +136 -0
- package/dist-lib/components/ui/Sheet.js.map +1 -0
- package/dist-lib/components/ui/Sidebar.d.ts +91 -0
- package/dist-lib/components/ui/Sidebar.js +212 -0
- package/dist-lib/components/ui/Sidebar.js.map +1 -0
- package/dist-lib/components/ui/Skeleton.d.ts +37 -0
- package/dist-lib/components/ui/Skeleton.js +29 -0
- package/dist-lib/components/ui/Skeleton.js.map +1 -0
- package/dist-lib/components/ui/Slider.d.ts +33 -0
- package/dist-lib/components/ui/Slider.js +64 -0
- package/dist-lib/components/ui/Slider.js.map +1 -0
- package/dist-lib/components/ui/Snackbar.d.ts +21 -0
- package/dist-lib/components/ui/Snackbar.js +57 -0
- package/dist-lib/components/ui/Snackbar.js.map +1 -0
- package/dist-lib/components/ui/Spinner.d.ts +23 -0
- package/dist-lib/components/ui/Spinner.js +54 -0
- package/dist-lib/components/ui/Spinner.js.map +1 -0
- package/dist-lib/components/ui/Stepper.d.ts +40 -0
- package/dist-lib/components/ui/Stepper.js +111 -0
- package/dist-lib/components/ui/Stepper.js.map +1 -0
- package/dist-lib/components/ui/Switch.d.ts +33 -0
- package/dist-lib/components/ui/Switch.js +80 -0
- package/dist-lib/components/ui/Switch.js.map +1 -0
- package/dist-lib/components/ui/Table.d.ts +71 -0
- package/dist-lib/components/ui/Table.js +179 -0
- package/dist-lib/components/ui/Table.js.map +1 -0
- package/dist-lib/components/ui/Tabs.d.ts +32 -0
- package/dist-lib/components/ui/Tabs.js +91 -0
- package/dist-lib/components/ui/Tabs.js.map +1 -0
- package/dist-lib/components/ui/TagInput.d.ts +39 -0
- 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.d.ts +34 -0
- package/dist-lib/components/ui/Textarea.js +88 -0
- package/dist-lib/components/ui/Textarea.js.map +1 -0
- package/dist-lib/components/ui/Timeline.d.ts +33 -0
- package/dist-lib/components/ui/Timeline.js +56 -0
- package/dist-lib/components/ui/Timeline.js.map +1 -0
- package/dist-lib/components/ui/Toast.d.ts +53 -0
- package/dist-lib/components/ui/Toast.js +158 -0
- package/dist-lib/components/ui/Toast.js.map +1 -0
- package/dist-lib/components/ui/Tooltip.d.ts +38 -0
- package/dist-lib/components/ui/Tooltip.js +60 -0
- package/dist-lib/components/ui/Tooltip.js.map +1 -0
- package/dist-lib/components/ui/TopNav.d.ts +37 -0
- package/dist-lib/components/ui/TopNav.js +69 -0
- package/dist-lib/components/ui/TopNav.js.map +1 -0
- package/dist-lib/components/ui/Tree.d.ts +51 -0
- package/dist-lib/components/ui/Tree.js +170 -0
- package/dist-lib/components/ui/Tree.js.map +1 -0
- package/dist-lib/hooks/use-debounce.d.ts +19 -0
- package/dist-lib/hooks/use-debounce.js +36 -0
- package/dist-lib/hooks/use-debounce.js.map +1 -0
- package/dist-lib/hooks/use-delayed-loading.d.ts +21 -0
- package/dist-lib/hooks/use-delayed-loading.js +20 -0
- package/dist-lib/hooks/use-delayed-loading.js.map +1 -0
- package/dist-lib/hooks/use-field-ids.d.ts +30 -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.d.ts +11 -0
- package/dist-lib/hooks/use-media-query.js +22 -0
- package/dist-lib/hooks/use-media-query.js.map +1 -0
- package/dist-lib/hooks/use-modifier-key.d.ts +13 -0
- package/dist-lib/hooks/use-modifier-key.js +19 -0
- package/dist-lib/hooks/use-modifier-key.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.d.ts +48 -0
- package/dist-lib/hooks/use-toast.js +67 -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 +80 -0
- package/dist-lib/icons/index.js +163 -0
- package/dist-lib/icons/index.js.map +1 -0
- package/dist-lib/illustrations/index.d.ts +10 -0
- package/dist-lib/illustrations/index.js +114 -0
- package/dist-lib/illustrations/index.js.map +1 -0
- package/dist-lib/index.d.ts +68 -0
- package/dist-lib/index.js +291 -0
- package/dist-lib/index.js.map +1 -0
- package/dist-lib/lib/cva.d.ts +6 -0
- package/dist-lib/lib/format.d.ts +65 -0
- package/dist-lib/lib/format.js +95 -0
- package/dist-lib/lib/format.js.map +1 -0
- package/dist-lib/lib/return-focus.d.ts +11 -0
- package/dist-lib/lib/return-focus.js +12 -0
- package/dist-lib/lib/return-focus.js.map +1 -0
- package/dist-lib/lib/strings.d.ts +190 -0
- package/dist-lib/lib/strings.js +141 -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 +115 -0
- package/dist-lib/lib/strings.mn.js.map +1 -0
- package/dist-lib/lib/utils.d.ts +11 -0
- package/dist-lib/lib/utils.js +10 -0
- package/dist-lib/lib/utils.js.map +1 -0
- package/dist-lib/styles.css +1 -0
- package/dist-lib/theme.css +840 -0
- package/package.json +158 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gerege Systems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
# @gerege-systems/ui
|
|
2
|
+
|
|
3
|
+
A refined-minimal Tailwind v4 + React design system. Production-grade
|
|
4
|
+
primitives — Button through DataGrid — plus composed patterns
|
|
5
|
+
(authentication, app shell, settings, etc.).
|
|
6
|
+
|
|
7
|
+
- **Showcase:** [ui.gecore.mn](https://ui.gecore.mn)
|
|
8
|
+
- **Components:** [ui.gecore.mn#components](https://ui.gecore.mn#components)
|
|
9
|
+
- **Templates:** [ui.gecore.mn#templates](https://ui.gecore.mn#templates)
|
|
10
|
+
|
|
11
|
+
> **Aesthetic direction:** Linear / Vercel / Stripe Dashboard / Notion / Raycast.
|
|
12
|
+
> Neutral-dominant, one accent, hairline borders, generous whitespace, fast quiet
|
|
13
|
+
> motion. See [`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md).
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add @gerege-systems/ui # peers: react ^18 || ^19, react-dom ^18 || ^19
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**1. Fonts** — Geist + Geist Mono are referenced by the tokens but not bundled (Inter is only a fallback in the stack). Google Fonts serves the `cyrillic-ext` subset automatically; self-hosting (≤4 woff2, weights 400/500/600) is equally fine. 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 '@gerege-systems/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 'tw-animate-css'; /* required on this path — overlays use animate-in / animate-out */
|
|
41
|
+
@import '@gerege-systems/ui/theme.css';
|
|
42
|
+
@source "../node_modules/@gerege-systems/ui/dist-lib";
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Path (b) needs `tw-animate-css` installed (`pnpm add -D tw-animate-css`; it is an optional peer). Path (a) already bundles it.
|
|
46
|
+
|
|
47
|
+
**3. Dark mode** — toggle the `dark` class on `<html>`; every token flips and `color-scheme` follows. Set it from a blocking script before first paint to avoid a flash (three states: light / dark / system). `data-theme` is not used.
|
|
48
|
+
|
|
49
|
+
**4. Providers** — `<Toaster />` once near the root if you use `toast()`; wrap the app in `<TooltipProvider>` if you use `Tooltip`. Nothing else is required.
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
import { Button, Toaster, TooltipProvider, toast } from '@gerege-systems/ui';
|
|
53
|
+
|
|
54
|
+
export function App() {
|
|
55
|
+
return (
|
|
56
|
+
<TooltipProvider>
|
|
57
|
+
<Button onClick={() => toast({ title: 'Saved' })}>Save</Button>
|
|
58
|
+
<Toaster />
|
|
59
|
+
</TooltipProvider>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**Next.js App Router** — since 0.10 every built module carries a `'use client'` banner, so `import { Button } from '@gerege-systems/ui'` works directly inside Server Components without a local re-export file. Put the CSS import and fonts in `app/layout.tsx`. Note: the pure helpers (`formatDate`, `formatNumber`, `formatMNT`, `mnStrings`, `defaultStrings`) are also client-marked today — they still run fine in Server Components (the directive only affects the boundary), but they are not tree-shaken into a server-only chunk.
|
|
65
|
+
|
|
66
|
+
**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:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { Icon } from '@gerege-systems/ui/icon';
|
|
70
|
+
<Icon name="calendar" className="size-4" />;
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
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.
|
|
74
|
+
|
|
75
|
+
## Localisation (Mongolian built in)
|
|
76
|
+
|
|
77
|
+
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`.
|
|
78
|
+
|
|
79
|
+
```tsx
|
|
80
|
+
import { DesignSystemProvider, mnStrings } from '@gerege-systems/ui';
|
|
81
|
+
<DesignSystemProvider strings={mnStrings}>…</DesignSystemProvider>; // whole library in Mongolian
|
|
82
|
+
<DesignSystemProvider strings={{ dialog: { close: 'Schließen' } }}>…</DesignSystemProvider>; // partial override, deep-merged over defaults
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Precedence is per-component props (`placeholder`, `labels`, `aria-label`) → nearest provider → `defaultStrings`. Templates use `{name}` placeholders, e.g. `pagination.showing: '{from}–{to} / {total}'`.
|
|
86
|
+
|
|
87
|
+
## Design rules
|
|
88
|
+
|
|
89
|
+
This library implements the [gerege-systems design-research](https://github.com/gerege-systems/design-research) guidelines
|
|
90
|
+
([rendered site](https://gerege-systems.github.io/design-research/)) — colour, type, spacing, components, accessibility, tokens.
|
|
91
|
+
design-research is the source of truth (canonical numbers in [`00-defaults.md`](https://github.com/gerege-systems/design-research/blob/main/00-defaults.md));
|
|
92
|
+
[`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md) is the library-specific summary.
|
|
93
|
+
|
|
94
|
+
## Local development
|
|
95
|
+
|
|
96
|
+
This package lives in the [`gerege-ui`](https://github.com/gerege-systems/gerege-ui)
|
|
97
|
+
monorepo (`packages/ui`). The showcase site is a separate workspace
|
|
98
|
+
(`apps/site`) that consumes this package's source directly. From the repo root:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pnpm install
|
|
102
|
+
pnpm dev # showcase site (apps/site) — edits to this package hot-reload
|
|
103
|
+
pnpm build:lib # build this library → packages/ui/dist-lib/
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Inside `packages/ui` itself, `pnpm build` produces the distributable bundle and
|
|
107
|
+
`pnpm test` runs the component tests.
|
|
108
|
+
|
|
109
|
+
## Tech stack
|
|
110
|
+
|
|
111
|
+
- **Tailwind CSS v4** — tokens defined in `@theme` in `src/styles/theme.css` (`globals.css` = tailwind + tw-animate-css + theme)
|
|
112
|
+
- **React 18 / 19** + **TypeScript 5.7**
|
|
113
|
+
- **Radix UI** primitives for accessibility-correct overlays
|
|
114
|
+
- **class-variance-authority** + `cn()` (`clsx` + `tailwind-merge`)
|
|
115
|
+
- **Lucide** icons (16 / 20px, 1.5 stroke)
|
|
116
|
+
- **Geist** sans + **Geist Mono** monospace
|
|
117
|
+
|
|
118
|
+
## Project structure
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
src/
|
|
122
|
+
├── styles/
|
|
123
|
+
│ ├── theme.css # @theme tokens, semantic vars, dark variant, base layer
|
|
124
|
+
│ └── globals.css # tailwindcss + tw-animate-css + theme.css
|
|
125
|
+
├── lib/
|
|
126
|
+
│ ├── utils.ts # cn() + uid()
|
|
127
|
+
│ ├── format.ts # formatDate / formatNumber / formatMNT
|
|
128
|
+
│ └── strings.ts # UiStrings, defaultStrings (+ strings.mn.ts)
|
|
129
|
+
├── components/
|
|
130
|
+
│ ├── ui/ # primitives — 52 components
|
|
131
|
+
│ └── patterns/ # composed layouts
|
|
132
|
+
├── hooks/
|
|
133
|
+
│ ├── use-toast.ts
|
|
134
|
+
│ └── use-media-query.ts
|
|
135
|
+
└── icons/ # curated Lucide re-exports
|
|
136
|
+
docs/
|
|
137
|
+
├── PHILOSOPHY.md # the 6 refined-minimal principles
|
|
138
|
+
├── VOICE.md # content + tone of voice
|
|
139
|
+
└── ACCESSIBILITY.md # WCAG checklist + contrast ratios
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Component index
|
|
143
|
+
|
|
144
|
+
### Inputs
|
|
145
|
+
|
|
146
|
+
- [Input](./src/components/ui/Input.tsx) — text / email / password / number / search with prefix, suffix, error
|
|
147
|
+
- [Textarea](./src/components/ui/Textarea.tsx) — auto-resize multi-line input
|
|
148
|
+
- [Select](./src/components/ui/Select.tsx) — single-choice menu (Radix)
|
|
149
|
+
- [MultiSelect](./src/components/ui/MultiSelect.tsx) — chip-based multi-choice picker
|
|
150
|
+
- [Combobox](./src/components/ui/Combobox.tsx) — searchable single-select, sync or async
|
|
151
|
+
- [Checkbox](./src/components/ui/Checkbox.tsx) — including indeterminate
|
|
152
|
+
- [RadioGroup](./src/components/ui/RadioGroup.tsx) — mutually-exclusive choices
|
|
153
|
+
- [Switch](./src/components/ui/Switch.tsx) — instant-apply binary toggle
|
|
154
|
+
- [Slider](./src/components/ui/Slider.tsx) — single + range
|
|
155
|
+
- [DatePicker](./src/components/ui/DatePicker.tsx) — single + range
|
|
156
|
+
- [Form primitives](./src/components/ui/Form.tsx) — react-hook-form bindings
|
|
157
|
+
|
|
158
|
+
### Buttons
|
|
159
|
+
|
|
160
|
+
- [Button](./src/components/ui/Button.tsx) — primary · secondary · outline · ghost · destructive · link
|
|
161
|
+
- [IconButton](./src/components/ui/IconButton.tsx) — square icon-only variant
|
|
162
|
+
- [Pagination](./src/components/ui/Pagination.tsx) — numbered + jumps + page-size
|
|
163
|
+
|
|
164
|
+
### Feedback
|
|
165
|
+
|
|
166
|
+
- [Alert](./src/components/ui/Alert.tsx) — inline banner, dismissible
|
|
167
|
+
- [Toast](./src/components/ui/Toast.tsx) + `useToast` hook
|
|
168
|
+
- [Spinner](./src/components/ui/Spinner.tsx) — accent / neutral / on-accent tones
|
|
169
|
+
- [Progress](./src/components/ui/Progress.tsx) — linear + circular, determinate + indeterminate
|
|
170
|
+
- [Skeleton](./src/components/ui/Skeleton.tsx) — text, avatar, card variants
|
|
171
|
+
- [EmptyState](./src/components/ui/EmptyState.tsx)
|
|
172
|
+
- [ErrorState](./src/components/ui/ErrorState.tsx) — 404 / 500 / generic
|
|
173
|
+
|
|
174
|
+
### Navigation
|
|
175
|
+
|
|
176
|
+
- [TopNav](./src/components/ui/TopNav.tsx) + `TopNavLink`
|
|
177
|
+
- [Sidebar](./src/components/ui/Sidebar.tsx) + `SidebarSection` + `SidebarItem` + `SidebarGroup`
|
|
178
|
+
- [Breadcrumbs](./src/components/ui/Breadcrumbs.tsx) — with overflow ellipsis
|
|
179
|
+
- [Tabs](./src/components/ui/Tabs.tsx) — underline + pills variants
|
|
180
|
+
- [Stepper](./src/components/ui/Stepper.tsx) — horizontal + vertical
|
|
181
|
+
|
|
182
|
+
### Layout
|
|
183
|
+
|
|
184
|
+
- [Card](./src/components/ui/Card.tsx) + `CardHeader/Title/Description/Content/Footer`
|
|
185
|
+
- [Separator](./src/components/ui/Separator.tsx)
|
|
186
|
+
- [ScrollArea](./src/components/ui/ScrollArea.tsx) — Radix-backed styled scroll
|
|
187
|
+
- [Accordion](./src/components/ui/Accordion.tsx) — single + multiple
|
|
188
|
+
|
|
189
|
+
### Overlays
|
|
190
|
+
|
|
191
|
+
- [Dialog](./src/components/ui/Dialog.tsx) + `ConfirmationDialog`
|
|
192
|
+
- [Sheet](./src/components/ui/Sheet.tsx) — left / right / top / bottom
|
|
193
|
+
- [Popover](./src/components/ui/Popover.tsx)
|
|
194
|
+
- [Tooltip](./src/components/ui/Tooltip.tsx) — 500ms delay default
|
|
195
|
+
- [DropdownMenu](./src/components/ui/DropdownMenu.tsx) — submenus, separators, kbd
|
|
196
|
+
- [ContextMenu](./src/components/ui/ContextMenu.tsx) — right-click menu
|
|
197
|
+
- [CommandPalette](./src/components/ui/CommandPalette.tsx) — ⌘K palette
|
|
198
|
+
|
|
199
|
+
### Data display
|
|
200
|
+
|
|
201
|
+
- [Table](./src/components/ui/Table.tsx) + `TableSortHeader`
|
|
202
|
+
- [DataGrid](./src/components/ui/DataGrid.tsx) — column visibility, filter, sortable
|
|
203
|
+
- [Badge](./src/components/ui/Badge.tsx) — subtle + outline, 6 tones
|
|
204
|
+
- [Avatar](./src/components/ui/Avatar.tsx) + `AvatarGroup`
|
|
205
|
+
|
|
206
|
+
### Typography
|
|
207
|
+
|
|
208
|
+
- [Kbd](./src/components/ui/Kbd.tsx) — keyboard shortcut indicator
|
|
209
|
+
|
|
210
|
+
## Blocks (page templates)
|
|
211
|
+
|
|
212
|
+
Whole-page compositions — dashboard, settings, auth, pricing, data table,
|
|
213
|
+
record detail, onboarding, first-run — are **not** shipped as importable
|
|
214
|
+
components. They live in the showcase as copy-paste **blocks**: complete pages
|
|
215
|
+
assembled from the primitives above, with the full source on the page. Read it,
|
|
216
|
+
copy it, adapt it — no opaque `<Dashboard />` import. Browse them under
|
|
217
|
+
**Templates** in the [showcase](https://ui.gecore.mn/#templates).
|
|
218
|
+
|
|
219
|
+
## Documentation
|
|
220
|
+
|
|
221
|
+
- [`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md) — the six principles + forbidden list
|
|
222
|
+
- [`docs/VOICE.md`](./docs/VOICE.md) — tone of voice, button labels, error copy formula
|
|
223
|
+
- [`docs/ACCESSIBILITY.md`](./docs/ACCESSIBILITY.md) — WCAG AA contrast table, keyboard map
|
|
224
|
+
|
|
225
|
+
## Contributing
|
|
226
|
+
|
|
227
|
+
1. Read [`docs/PHILOSOPHY.md`](./docs/PHILOSOPHY.md) first — the forbidden list is
|
|
228
|
+
non-negotiable.
|
|
229
|
+
2. Components reference semantic tokens (`bg-card`, `text-accent`), never raw
|
|
230
|
+
palette steps (`bg-indigo-500`).
|
|
231
|
+
3. Every interactive element ships with: default, hover, focus-visible, active,
|
|
232
|
+
disabled, loading, and (where applicable) error / success states.
|
|
233
|
+
4. Forward refs correctly; set `displayName`.
|
|
234
|
+
5. Run `pnpm typecheck && pnpm test` before opening a PR.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** Test-only colour maths: CSS `hsl()` / `oklch()` / hex → sRGB, WCAG 2.x contrast. */
|
|
2
|
+
export type RGB = [number, number, number];
|
|
3
|
+
export declare function parseColor(input: string): RGB;
|
|
4
|
+
export declare function hslToRgb(h: number, s: number, l: number): RGB;
|
|
5
|
+
/** OKLCH → OKLab → linear sRGB → gamma sRGB (clipped to gamut). */
|
|
6
|
+
export declare function oklchToRgb(L: number, C: number, hDeg: number): RGB;
|
|
7
|
+
export declare function luminance([r, g, b]: RGB): number;
|
|
8
|
+
export declare function contrast(a: RGB, b: RGB): number;
|
|
9
|
+
export declare function toHex([r, g, b]: RGB): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const THEME_PATH: any;
|
|
2
|
+
export type TokenMap = Record<string, string>;
|
|
3
|
+
export interface ParsedTheme {
|
|
4
|
+
/** `:root { … }` semantic block. */
|
|
5
|
+
light: TokenMap;
|
|
6
|
+
/** `.dark { … }` semantic block. */
|
|
7
|
+
dark: TokenMap;
|
|
8
|
+
/** `@theme { … }` raw primitives (fonts, text scale, radius, shadow, z, motion, palette). */
|
|
9
|
+
raw: TokenMap;
|
|
10
|
+
/** `[data-accent='x']` presets: light (`:root[...]`) and dark (`.dark[...]`) overrides. */
|
|
11
|
+
accents: Record<string, {
|
|
12
|
+
shared: TokenMap;
|
|
13
|
+
light: TokenMap;
|
|
14
|
+
dark: TokenMap;
|
|
15
|
+
}>;
|
|
16
|
+
}
|
|
17
|
+
export declare function parseDeclarations(body: string): TokenMap;
|
|
18
|
+
export declare function parseTheme(css?: any): ParsedTheme;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as AccordionPrimitive from '@radix-ui/react-accordion';
|
|
2
|
+
/**
|
|
3
|
+
* Accessible disclosure list. Supports single (`type="single"`) and multiple
|
|
4
|
+
* (`type="multiple"`) open behaviour — both come from Radix.
|
|
5
|
+
*
|
|
6
|
+
* @example FAQ
|
|
7
|
+
* <Accordion type="single" collapsible>
|
|
8
|
+
* <AccordionItem value="q1">
|
|
9
|
+
* <AccordionTrigger>Can I cancel anytime?</AccordionTrigger>
|
|
10
|
+
* <AccordionContent>Yes — usage stops at the end of the billing period.</AccordionContent>
|
|
11
|
+
* </AccordionItem>
|
|
12
|
+
* …
|
|
13
|
+
* </Accordion>
|
|
14
|
+
*
|
|
15
|
+
* @do Use for FAQs, settings groups, and progressive-disclosure forms.
|
|
16
|
+
* @dont Nest accordions inside accordions — the focus order becomes opaque.
|
|
17
|
+
*/
|
|
18
|
+
export declare const Accordion: import('react').ForwardRefExoticComponent<(AccordionPrimitive.AccordionSingleProps | AccordionPrimitive.AccordionMultipleProps) & import('react').RefAttributes<HTMLDivElement>>;
|
|
19
|
+
export declare const AccordionItem: import('react').ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionItemProps & import('react').RefAttributes<HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
20
|
+
export declare const AccordionTrigger: import('react').ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionTriggerProps & import('react').RefAttributes<HTMLButtonElement>, "ref"> & import('react').RefAttributes<HTMLButtonElement>>;
|
|
21
|
+
export declare const AccordionContent: import('react').ForwardRefExoticComponent<Omit<AccordionPrimitive.AccordionContentProps & import('react').RefAttributes<HTMLDivElement>, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
|
|
@@ -0,0 +1,71 @@
|
|
|
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(
|
|
9
|
+
n.Item,
|
|
10
|
+
{
|
|
11
|
+
ref: r,
|
|
12
|
+
className: i("border-border border-b", o),
|
|
13
|
+
...e
|
|
14
|
+
}
|
|
15
|
+
);
|
|
16
|
+
});
|
|
17
|
+
f.displayName = "AccordionItem";
|
|
18
|
+
const l = a(function({ className: o, children: e, ...r }, c) {
|
|
19
|
+
return /* @__PURE__ */ t(n.Header, { className: "flex", children: /* @__PURE__ */ d(
|
|
20
|
+
n.Trigger,
|
|
21
|
+
{
|
|
22
|
+
ref: c,
|
|
23
|
+
className: i(
|
|
24
|
+
"text-foreground flex flex-1 items-center justify-between gap-2 py-4 text-left text-sm font-medium",
|
|
25
|
+
"transition-colors duration-[var(--duration-fast)] outline-none",
|
|
26
|
+
"hover:text-accent",
|
|
27
|
+
"focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2",
|
|
28
|
+
"[&[data-state=open]>svg]:rotate-180",
|
|
29
|
+
o
|
|
30
|
+
),
|
|
31
|
+
...r,
|
|
32
|
+
children: [
|
|
33
|
+
e,
|
|
34
|
+
/* @__PURE__ */ t(
|
|
35
|
+
m,
|
|
36
|
+
{
|
|
37
|
+
className: "text-foreground-subtle size-4 shrink-0 transition-transform duration-[var(--duration-base)]",
|
|
38
|
+
"aria-hidden": !0
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
) });
|
|
44
|
+
});
|
|
45
|
+
l.displayName = "AccordionTrigger";
|
|
46
|
+
const u = a(function({ className: o, children: e, ...r }, c) {
|
|
47
|
+
return /* @__PURE__ */ t(
|
|
48
|
+
n.Content,
|
|
49
|
+
{
|
|
50
|
+
ref: c,
|
|
51
|
+
className: i(
|
|
52
|
+
"text-foreground-muted overflow-hidden text-sm",
|
|
53
|
+
// Deliberate motion exception: height animates (layout property). Radix only
|
|
54
|
+
// keeps Content mounted through CSS *animations*, so a grid-rows transition
|
|
55
|
+
// would cut the close. The panel is short and nothing below it reflows
|
|
56
|
+
// except by design; `prefers-reduced-motion` disables it globally.
|
|
57
|
+
"data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
|
58
|
+
),
|
|
59
|
+
...r,
|
|
60
|
+
children: /* @__PURE__ */ t("div", { className: i("pt-0 pb-4", o), children: e })
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
u.displayName = "AccordionContent";
|
|
65
|
+
export {
|
|
66
|
+
v as Accordion,
|
|
67
|
+
u as AccordionContent,
|
|
68
|
+
f as AccordionItem,
|
|
69
|
+
l as AccordionTrigger
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=Accordion.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Accordion.js","sources":["../../../src/components/ui/Accordion.tsx"],"sourcesContent":["'use client';\n\nimport { forwardRef, type ComponentPropsWithoutRef, type ElementRef } 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\n ref={ref}\n className={cn('border-border border-b', className)}\n {...props}\n />\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 'text-foreground flex flex-1 items-center justify-between gap-2 py-4 text-left text-sm font-medium',\n 'transition-colors duration-[var(--duration-fast)] outline-none',\n 'hover:text-accent',\n 'focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2',\n '[&[data-state=open]>svg]:rotate-180',\n className,\n )}\n {...props}\n >\n {children}\n <ChevronDown\n className=\"text-foreground-subtle size-4 shrink-0 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 'text-foreground-muted overflow-hidden text-sm',\n // Deliberate motion exception: height animates (layout property). Radix only\n // keeps Content mounted through CSS *animations*, so a grid-rows transition\n // would cut the close. The panel is short and nothing below it reflows\n // except by design; `prefers-reduced-motion` disables it globally.\n 'data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down',\n )}\n {...props}\n >\n <div className={cn('pt-0 pb-4', 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":";;;;;;AAuBO,MAAMA,IAAYC,EAAmB,MAE/BC,IAAgBC,EAG3B,SAAuB,EAAE,WAAAC,GAAW,GAAGC,EAAA,GAASC,GAAK;AACrD,SACE,gBAAAC;AAAA,IAACN,EAAmB;AAAA,IAAnB;AAAA,MACC,KAAAK;AAAA,MACA,WAAWE,EAAG,0BAA0BJ,CAAS;AAAA,MAChD,GAAGC;AAAA,IAAA;AAAA,EAAA;AAGV,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;AAAA;AAAA;AAAA;AAAA,QAKA;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;"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
|
+
declare const alert: (props?: ({
|
|
4
|
+
variant?: "default" | "success" | "warning" | "danger" | "info" | null | undefined;
|
|
5
|
+
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
6
|
+
export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'>, VariantProps<typeof alert> {
|
|
7
|
+
/** Heading. Renders bold above the description. */
|
|
8
|
+
title?: ReactNode;
|
|
9
|
+
/** Show a dismiss (×) button. */
|
|
10
|
+
dismissible?: boolean;
|
|
11
|
+
/** Called when the user dismisses. */
|
|
12
|
+
onDismiss?: () => void;
|
|
13
|
+
/**
|
|
14
|
+
* Controlled visibility. Omit for the default uncontrolled behaviour
|
|
15
|
+
* (visible until dismissed).
|
|
16
|
+
*/
|
|
17
|
+
open?: boolean;
|
|
18
|
+
/** Called with the next visibility when the user dismisses. */
|
|
19
|
+
onOpenChange?: (open: boolean) => void;
|
|
20
|
+
/** Override the variant's default icon. Pass `false` to suppress the icon. */
|
|
21
|
+
icon?: ReactNode | false;
|
|
22
|
+
/**
|
|
23
|
+
* Announce the alert to screen readers when it appears: `role="alert"` for
|
|
24
|
+
* `danger`, `role="status"` otherwise. Off by default — content that is on
|
|
25
|
+
* the page at load should not be announced as a live update.
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
28
|
+
live?: boolean;
|
|
29
|
+
/** Heading level of `title`. Default 3 — fits under a page h1 / section h2. */
|
|
30
|
+
headingLevel?: 2 | 3 | 4 | 5 | 6;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Inline banner for static page-level or section-level state. For transient
|
|
34
|
+
* notifications use `Toast` instead.
|
|
35
|
+
*
|
|
36
|
+
* @example Inline error
|
|
37
|
+
* <Alert variant="danger" title="Payment failed">
|
|
38
|
+
* Your card was declined. Update your billing info to retry.
|
|
39
|
+
* </Alert>
|
|
40
|
+
*
|
|
41
|
+
* @example Dismissible info
|
|
42
|
+
* <Alert variant="info" dismissible onDismiss={dismiss}>
|
|
43
|
+
* We're improving search — give us feedback at #search-feedback.
|
|
44
|
+
* </Alert>
|
|
45
|
+
*
|
|
46
|
+
* @do Use the closest semantic variant — `info` for neutral facts,
|
|
47
|
+
* `warning` for "watch out", `danger` for "this is broken".
|
|
48
|
+
* @dont Use Alert for one-time confirmations — that's Toast.
|
|
49
|
+
*/
|
|
50
|
+
export declare const Alert: import('react').ForwardRefExoticComponent<AlertProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
51
|
+
export {};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs as l, jsx as e } from "react/jsx-runtime";
|
|
3
|
+
import { forwardRef as k, useState as w, createElement as z } from "react";
|
|
4
|
+
import { X as A, XCircle as j, AlertTriangle as y, CheckCircle2 as I, Info as S } from "../../icons/index.js";
|
|
5
|
+
import { cn as o } from "../../lib/utils.js";
|
|
6
|
+
import { useStrings as V } from "../../hooks/use-strings.js";
|
|
7
|
+
import { cva as X } from "class-variance-authority";
|
|
8
|
+
const E = X(["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
|
+
}), F = {
|
|
20
|
+
default: null,
|
|
21
|
+
info: /* @__PURE__ */ e(S, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
22
|
+
success: /* @__PURE__ */ e(I, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
23
|
+
warning: /* @__PURE__ */ e(y, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 }),
|
|
24
|
+
danger: /* @__PURE__ */ e(j, { className: "mt-0.5 size-4 shrink-0", "aria-hidden": !0 })
|
|
25
|
+
}, R = k(function({
|
|
26
|
+
className: d,
|
|
27
|
+
variant: r = "default",
|
|
28
|
+
title: i,
|
|
29
|
+
dismissible: c,
|
|
30
|
+
onDismiss: t,
|
|
31
|
+
open: n,
|
|
32
|
+
onOpenChange: s,
|
|
33
|
+
icon: a,
|
|
34
|
+
live: f = !1,
|
|
35
|
+
headingLevel: u = 3,
|
|
36
|
+
children: m,
|
|
37
|
+
...g
|
|
38
|
+
}, b) {
|
|
39
|
+
const x = V(), [h, v] = w(!0);
|
|
40
|
+
if (!(n ?? h)) return null;
|
|
41
|
+
const p = () => {
|
|
42
|
+
n === void 0 && v(!1), s == null || s(!1), t == null || t();
|
|
43
|
+
}, N = a === !1 ? null : a ?? F[r ?? "default"];
|
|
44
|
+
return /* @__PURE__ */ l(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
ref: b,
|
|
48
|
+
role: f ? r === "danger" ? "alert" : "status" : void 0,
|
|
49
|
+
className: o(E({ variant: r }), d),
|
|
50
|
+
...g,
|
|
51
|
+
children: [
|
|
52
|
+
N,
|
|
53
|
+
/* @__PURE__ */ l("div", { className: "min-w-0 flex-1", children: [
|
|
54
|
+
i && z(`h${u}`, { className: "mb-1 leading-tight font-medium" }, i),
|
|
55
|
+
/* @__PURE__ */ e("div", { className: "text-sm leading-relaxed [&_p]:leading-relaxed", children: m })
|
|
56
|
+
] }),
|
|
57
|
+
c && /* @__PURE__ */ e(
|
|
58
|
+
"button",
|
|
59
|
+
{
|
|
60
|
+
type: "button",
|
|
61
|
+
"aria-label": x.alert.dismiss,
|
|
62
|
+
onClick: p,
|
|
63
|
+
className: o(
|
|
64
|
+
"inline-flex size-6 shrink-0 items-center justify-center rounded-md",
|
|
65
|
+
"outline-none hover:bg-current/10",
|
|
66
|
+
"focus-visible:ring-ring focus-visible:ring-offset-background focus-visible:ring-2 focus-visible:ring-offset-2"
|
|
67
|
+
),
|
|
68
|
+
children: /* @__PURE__ */ e(A, { className: "size-3.5", "aria-hidden": !0 })
|
|
69
|
+
}
|
|
70
|
+
)
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
R.displayName = "Alert";
|
|
76
|
+
export {
|
|
77
|
+
R as Alert
|
|
78
|
+
};
|
|
79
|
+
//# sourceMappingURL=Alert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Alert.js","sources":["../../../src/components/ui/Alert.tsx"],"sourcesContent":["'use client';\n\nimport { createElement, 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 /**\n * Controlled visibility. Omit for the default uncontrolled behaviour\n * (visible until dismissed).\n */\n open?: boolean;\n /** Called with the next visibility when the user dismisses. */\n onOpenChange?: (open: boolean) => void;\n /** Override the variant's default icon. Pass `false` to suppress the icon. */\n icon?: ReactNode | false;\n /**\n * Announce the alert to screen readers when it appears: `role=\"alert\"` for\n * `danger`, `role=\"status\"` otherwise. Off by default — content that is on\n * the page at load should not be announced as a live update.\n * @default false\n */\n live?: boolean;\n /** Heading level of `title`. Default 3 — fits under a page h1 / section h2. */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\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 {\n className,\n variant = 'default',\n title,\n dismissible,\n onDismiss,\n open: openProp,\n onOpenChange,\n icon,\n live = false,\n headingLevel = 3,\n children,\n ...props\n },\n ref,\n) {\n const strings = useStrings();\n const [internalOpen, setInternalOpen] = useState(true);\n const open = openProp ?? internalOpen;\n if (!open) return null;\n\n const handleDismiss = () => {\n if (openProp === undefined) setInternalOpen(false);\n onOpenChange?.(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={live ? (variant === 'danger' ? 'alert' : 'status') : undefined}\n className={cn(alert({ variant }), className)}\n {...props}\n >\n {renderedIcon}\n <div className=\"min-w-0 flex-1\">\n {title &&\n createElement(`h${headingLevel}`, { className: 'mb-1 leading-tight font-medium' }, title)}\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","openProp","onOpenChange","icon","live","headingLevel","children","props","ref","strings","useStrings","internalOpen","setInternalOpen","useState","handleDismiss","renderedIcon","jsxs","cn","createElement","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,GAgDaC,IAAQC,EAAuC,SAC1D;AAAA,EACE,WAAAC;AAAA,EACA,SAAAC,IAAU;AAAA,EACV,OAAAC;AAAA,EACA,aAAAC;AAAA,EACA,WAAAC;AAAA,EACA,MAAMC;AAAA,EACN,cAAAC;AAAA,EACA,MAAAC;AAAA,EACA,MAAAC,IAAO;AAAA,EACP,cAAAC,IAAe;AAAA,EACf,UAAAC;AAAA,EACA,GAAGC;AACL,GACAC,GACA;AACA,QAAMC,IAAUC,EAAA,GACV,CAACC,GAAcC,CAAe,IAAIC,EAAS,EAAI;AAErD,MAAI,EADSZ,KAAYU,GACd,QAAO;AAElB,QAAMG,IAAgB,MAAM;AAC1B,IAAIb,MAAa,UAAWW,EAAgB,EAAK,GACjDV,KAAA,QAAAA,EAAe,KACfF,KAAA,QAAAA;AAAA,EACF,GAEMe,IAAeZ,MAAS,KAAQ,OAAQA,KAAQf,EAAeS,KAAW,SAAS;AAEzF,SACE,gBAAAmB;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,KAAAR;AAAA,MAEA,MAAMJ,IAAQP,MAAY,WAAW,UAAU,WAAY;AAAA,MAC3D,WAAWoB,EAAG/B,EAAM,EAAE,SAAAW,EAAA,CAAS,GAAGD,CAAS;AAAA,MAC1C,GAAGW;AAAA,MAEH,UAAA;AAAA,QAAAQ;AAAA,QACD,gBAAAC,EAAC,OAAA,EAAI,WAAU,kBACZ,UAAA;AAAA,UAAAlB,KACCoB,EAAc,IAAIb,CAAY,IAAI,EAAE,WAAW,iCAAA,GAAoCP,CAAK;AAAA,UAC1F,gBAAAT,EAAC,OAAA,EAAI,WAAU,iDAAiD,UAAAiB,EAAA,CAAS;AAAA,QAAA,GAC3E;AAAA,QACCP,KACC,gBAAAV;AAAA,UAAC;AAAA,UAAA;AAAA,YACC,MAAK;AAAA,YACL,cAAYoB,EAAQ,MAAM;AAAA,YAC1B,SAASK;AAAA,YACT,WAAWG;AAAA,cACT;AAAA,cACA;AAAA,cACA;AAAA,YAAA;AAAA,YAGF,UAAA,gBAAA5B,EAAC8B,GAAA,EAAE,WAAU,YAAW,eAAW,GAAA,CAAC;AAAA,UAAA;AAAA,QAAA;AAAA,MACtC;AAAA,IAAA;AAAA,EAAA;AAIR,CAAC;AACDzB,EAAM,cAAc;"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { ComponentPropsWithoutRef, HTMLAttributes, ReactNode } from 'react';
|
|
2
|
+
import { VariantProps } from '../../lib/cva.js';
|
|
3
|
+
import * as AvatarPrimitive from '@radix-ui/react-avatar';
|
|
4
|
+
declare const sizeMap: {
|
|
5
|
+
readonly xs: "size-5 text-xs";
|
|
6
|
+
readonly sm: "size-6 text-xs";
|
|
7
|
+
readonly md: "size-8 text-xs";
|
|
8
|
+
readonly lg: "size-10 text-sm";
|
|
9
|
+
readonly xl: "size-12 text-base";
|
|
10
|
+
};
|
|
11
|
+
declare const avatarWrapper: (props?: ({
|
|
12
|
+
size?: "md" | "xs" | "sm" | "lg" | "xl" | null | undefined;
|
|
13
|
+
} & import('class-variance-authority/types').ClassProp) | undefined) => string;
|
|
14
|
+
export interface AvatarProps extends ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>, VariantProps<typeof avatarWrapper> {
|
|
15
|
+
/** Image URL. If absent or fails to load, fallback initials are shown. */
|
|
16
|
+
src?: string;
|
|
17
|
+
/** Alt text for the image; falls back to fallback when there's no `src`. */
|
|
18
|
+
alt?: string;
|
|
19
|
+
/** Initials shown when image is missing or loading. Pass at most 2 characters. */
|
|
20
|
+
fallback?: string;
|
|
21
|
+
/** Status dot rendered on the bottom-right. */
|
|
22
|
+
status?: 'online' | 'busy' | 'away' | 'offline';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Avatar with image, initials fallback, and optional status dot.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <Avatar src={user.photo} alt={user.name} fallback={getInitials(user.name)} status="online" />
|
|
29
|
+
*
|
|
30
|
+
* @example AvatarGroup
|
|
31
|
+
* <AvatarGroup max={3}>
|
|
32
|
+
* {users.map(u => <Avatar key={u.id} src={u.photo} fallback={getInitials(u.name)} />)}
|
|
33
|
+
* </AvatarGroup>
|
|
34
|
+
*
|
|
35
|
+
* @do Provide `alt` for image avatars and meaningful initials for the
|
|
36
|
+
* fallback so screen readers announce something useful.
|
|
37
|
+
* @dont Use the status dot without a tooltip explaining what it means.
|
|
38
|
+
*/
|
|
39
|
+
export declare const Avatar: import('react').ForwardRefExoticComponent<AvatarProps & import('react').RefAttributes<HTMLSpanElement>>;
|
|
40
|
+
export interface AvatarGroupProps extends HTMLAttributes<HTMLDivElement> {
|
|
41
|
+
/** Maximum visible avatars before showing a "+N" overflow chip. */
|
|
42
|
+
max?: number;
|
|
43
|
+
/** Avatar size applied to children (unless a child sets its own) + overflow. */
|
|
44
|
+
size?: keyof typeof sizeMap;
|
|
45
|
+
/** Children — Avatars. */
|
|
46
|
+
children: ReactNode;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Overlapping avatars with overflow indicator. Pass plain `<Avatar>` children.
|
|
50
|
+
*/
|
|
51
|
+
export declare const AvatarGroup: import('react').ForwardRefExoticComponent<AvatarGroupProps & import('react').RefAttributes<HTMLDivElement>>;
|
|
52
|
+
export {};
|