@nibapp/ui 0.0.0-stage → 0.1.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/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +124 -2
- package/TRADEMARKS.md +22 -0
- package/dist/_bundled/money/index.d.ts +98 -0
- package/dist/_bundled/money/index.js +152 -0
- package/dist/_bundled/tokens/index.d.ts +2 -0
- package/dist/_bundled/tokens/index.js +2 -0
- package/dist/_bundled/tokens/palette.gen.d.ts +51 -0
- package/dist/_bundled/tokens/palette.gen.js +206 -0
- package/dist/_bundled/tokens/spec.d.ts +291 -0
- package/dist/_bundled/tokens/spec.js +221 -0
- package/dist/components/accordion.d.ts +27 -0
- package/dist/components/accordion.js +66 -0
- package/dist/components/alert-dialog.d.ts +59 -0
- package/dist/components/alert-dialog.js +82 -0
- package/dist/components/alert.d.ts +141 -0
- package/dist/components/alert.js +55 -0
- package/dist/components/appear.d.ts +21 -0
- package/dist/components/appear.js +42 -0
- package/dist/components/avatar.d.ts +196 -0
- package/dist/components/avatar.js +135 -0
- package/dist/components/badge.d.ts +183 -0
- package/dist/components/badge.js +68 -0
- package/dist/components/block-stack.d.ts +33 -0
- package/dist/components/block-stack.js +197 -0
- package/dist/components/button.d.ts +327 -0
- package/dist/components/button.js +101 -0
- package/dist/components/card.d.ts +131 -0
- package/dist/components/card.js +100 -0
- package/dist/components/checkbox.d.ts +34 -0
- package/dist/components/checkbox.js +37 -0
- package/dist/components/choice-row.d.ts +104 -0
- package/dist/components/choice-row.js +56 -0
- package/dist/components/clamped-text.d.ts +17 -0
- package/dist/components/clamped-text.js +47 -0
- package/dist/components/code-block.d.ts +48 -0
- package/dist/components/code-block.js +124 -0
- package/dist/components/code-input.d.ts +49 -0
- package/dist/components/code-input.js +59 -0
- package/dist/components/combobox.d.ts +194 -0
- package/dist/components/combobox.js +176 -0
- package/dist/components/command-palette.d.ts +57 -0
- package/dist/components/command-palette.js +83 -0
- package/dist/components/context-menu.d.ts +24 -0
- package/dist/components/context-menu.js +30 -0
- package/dist/components/copy-button.d.ts +9 -0
- package/dist/components/copy-button.js +20 -0
- package/dist/components/copy-command.d.ts +18 -0
- package/dist/components/copy-command.js +19 -0
- package/dist/components/data-table.d.ts +240 -0
- package/dist/components/data-table.js +63 -0
- package/dist/components/dialog.d.ts +80 -0
- package/dist/components/dialog.js +85 -0
- package/dist/components/diff-lines.d.ts +20 -0
- package/dist/components/diff-lines.js +10 -0
- package/dist/components/diff-view.d.ts +128 -0
- package/dist/components/diff-view.js +675 -0
- package/dist/components/disclosure.d.ts +33 -0
- package/dist/components/disclosure.js +51 -0
- package/dist/components/drawn-check.d.ts +8 -0
- package/dist/components/drawn-check.js +16 -0
- package/dist/components/edge-resizer.d.ts +26 -0
- package/dist/components/edge-resizer.js +53 -0
- package/dist/components/empty-state.d.ts +112 -0
- package/dist/components/empty-state.js +56 -0
- package/dist/components/expand-toggle.d.ts +19 -0
- package/dist/components/expand-toggle.js +10 -0
- package/dist/components/field.d.ts +48 -0
- package/dist/components/field.js +60 -0
- package/dist/components/filter-chip.d.ts +47 -0
- package/dist/components/filter-chip.js +33 -0
- package/dist/components/group.d.ts +10 -0
- package/dist/components/group.js +9 -0
- package/dist/components/icon-text.d.ts +11 -0
- package/dist/components/icon-text.js +10 -0
- package/dist/components/image-sources.d.ts +57 -0
- package/dist/components/image-sources.js +58 -0
- package/dist/components/image-thumbnail.d.ts +34 -0
- package/dist/components/image-thumbnail.js +58 -0
- package/dist/components/image-viewer.d.ts +60 -0
- package/dist/components/image-viewer.js +411 -0
- package/dist/components/inline-edit.d.ts +22 -0
- package/dist/components/inline-edit.js +51 -0
- package/dist/components/input.d.ts +155 -0
- package/dist/components/input.js +109 -0
- package/dist/components/item-label.d.ts +11 -0
- package/dist/components/item-label.js +5 -0
- package/dist/components/item-row.d.ts +23 -0
- package/dist/components/item-row.js +19 -0
- package/dist/components/kbd.d.ts +55 -0
- package/dist/components/kbd.js +86 -0
- package/dist/components/link-policy.d.ts +24 -0
- package/dist/components/link-policy.js +32 -0
- package/dist/components/linked-text.d.ts +15 -0
- package/dist/components/linked-text.js +48 -0
- package/dist/components/loading-region.d.ts +18 -0
- package/dist/components/loading-region.js +17 -0
- package/dist/components/markdown.d.ts +34 -0
- package/dist/components/markdown.js +307 -0
- package/dist/components/menu.d.ts +132 -0
- package/dist/components/menu.js +102 -0
- package/dist/components/message-bubble.d.ts +57 -0
- package/dist/components/message-bubble.js +28 -0
- package/dist/components/meta-line.d.ts +28 -0
- package/dist/components/meta-line.js +36 -0
- package/dist/components/money-input.d.ts +23 -0
- package/dist/components/money-input.js +34 -0
- package/dist/components/native-select.d.ts +120 -0
- package/dist/components/native-select.js +85 -0
- package/dist/components/page-trail.d.ts +30 -0
- package/dist/components/page-trail.js +43 -0
- package/dist/components/page.d.ts +80 -0
- package/dist/components/page.js +60 -0
- package/dist/components/path-text.d.ts +8 -0
- package/dist/components/path-text.js +11 -0
- package/dist/components/percent-input.d.ts +20 -0
- package/dist/components/percent-input.js +32 -0
- package/dist/components/popover.d.ts +52 -0
- package/dist/components/popover.js +36 -0
- package/dist/components/progress.d.ts +389 -0
- package/dist/components/progress.js +142 -0
- package/dist/components/radio.d.ts +65 -0
- package/dist/components/radio.js +54 -0
- package/dist/components/remote-image-gate.d.ts +34 -0
- package/dist/components/remote-image-gate.js +60 -0
- package/dist/components/resizable.d.ts +23 -0
- package/dist/components/resizable.js +30 -0
- package/dist/components/rolling-number.d.ts +13 -0
- package/dist/components/rolling-number.js +69 -0
- package/dist/components/scroll-area.d.ts +17 -0
- package/dist/components/scroll-area.js +25 -0
- package/dist/components/section-label.d.ts +10 -0
- package/dist/components/section-label.js +21 -0
- package/dist/components/section-nav.d.ts +22 -0
- package/dist/components/section-nav.js +80 -0
- package/dist/components/segmented-control.d.ts +125 -0
- package/dist/components/segmented-control.js +101 -0
- package/dist/components/select.d.ts +120 -0
- package/dist/components/select.js +89 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.js +12 -0
- package/dist/components/sheet.d.ts +101 -0
- package/dist/components/sheet.js +129 -0
- package/dist/components/shortcut-recorder.d.ts +31 -0
- package/dist/components/shortcut-recorder.js +67 -0
- package/dist/components/skeleton.d.ts +31 -0
- package/dist/components/skeleton.js +33 -0
- package/dist/components/slider.d.ts +138 -0
- package/dist/components/slider.js +98 -0
- package/dist/components/spinner.d.ts +37 -0
- package/dist/components/spinner.js +16 -0
- package/dist/components/switch.d.ts +59 -0
- package/dist/components/switch.js +56 -0
- package/dist/components/tab-strip.d.ts +29 -0
- package/dist/components/tab-strip.js +42 -0
- package/dist/components/tabs.d.ts +13 -0
- package/dist/components/tabs.js +68 -0
- package/dist/components/text-link.d.ts +54 -0
- package/dist/components/text-link.js +38 -0
- package/dist/components/textarea.d.ts +75 -0
- package/dist/components/textarea.js +90 -0
- package/dist/components/toast.d.ts +74 -0
- package/dist/components/toast.js +128 -0
- package/dist/components/tooltip.d.ts +50 -0
- package/dist/components/tooltip.js +46 -0
- package/dist/components/truncated-text.d.ts +15 -0
- package/dist/components/truncated-text.js +19 -0
- package/dist/i18n/defaults.d.ts +7 -0
- package/dist/i18n/defaults.js +7 -0
- package/dist/i18n/format.d.ts +84 -0
- package/dist/i18n/format.js +169 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +8 -0
- package/dist/i18n/locales/ru-formatters.d.ts +3 -0
- package/dist/i18n/locales/ru-formatters.js +22 -0
- package/dist/i18n/locales/ru.d.ts +5 -0
- package/dist/i18n/locales/ru.js +185 -0
- package/dist/i18n/plural.d.ts +20 -0
- package/dist/i18n/plural.js +26 -0
- package/dist/i18n/provider.d.ts +36 -0
- package/dist/i18n/provider.js +39 -0
- package/dist/i18n/rich.d.ts +14 -0
- package/dist/i18n/rich.js +44 -0
- package/dist/i18n/ru-plural.d.ts +3 -0
- package/dist/i18n/ru-plural.js +4 -0
- package/dist/i18n/strings/alert-dialog.d.ts +5 -0
- package/dist/i18n/strings/alert-dialog.js +5 -0
- package/dist/i18n/strings/block-stack.d.ts +17 -0
- package/dist/i18n/strings/block-stack.js +11 -0
- package/dist/i18n/strings/code-block.d.ts +18 -0
- package/dist/i18n/strings/code-block.js +13 -0
- package/dist/i18n/strings/combobox.d.ts +12 -0
- package/dist/i18n/strings/combobox.js +10 -0
- package/dist/i18n/strings/command-palette.d.ts +17 -0
- package/dist/i18n/strings/command-palette.js +11 -0
- package/dist/i18n/strings/diff-view.d.ts +92 -0
- package/dist/i18n/strings/diff-view.js +68 -0
- package/dist/i18n/strings/image-viewer.d.ts +43 -0
- package/dist/i18n/strings/image-viewer.js +32 -0
- package/dist/i18n/strings/index.d.ts +314 -0
- package/dist/i18n/strings/index.js +43 -0
- package/dist/i18n/strings/kbd.d.ts +42 -0
- package/dist/i18n/strings/kbd.js +39 -0
- package/dist/i18n/strings/markdown.d.ts +23 -0
- package/dist/i18n/strings/markdown.js +18 -0
- package/dist/i18n/strings/money.d.ts +7 -0
- package/dist/i18n/strings/money.js +5 -0
- package/dist/i18n/strings/primitives.d.ts +25 -0
- package/dist/i18n/strings/primitives.js +18 -0
- package/dist/i18n/strings/progress.d.ts +4 -0
- package/dist/i18n/strings/progress.js +4 -0
- package/dist/i18n/strings/shortcut-recorder.d.ts +15 -0
- package/dist/i18n/strings/shortcut-recorder.js +11 -0
- package/dist/i18n/strings/time.d.ts +41 -0
- package/dist/i18n/strings/time.js +20 -0
- package/dist/i18n/strings/toast.d.ts +6 -0
- package/dist/i18n/strings/toast.js +6 -0
- package/dist/i18n/time-zones.d.ts +20 -0
- package/dist/i18n/time-zones.js +64 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.js +88 -0
- package/dist/lib/ansi.d.ts +55 -0
- package/dist/lib/ansi.js +438 -0
- package/dist/lib/clock.d.ts +32 -0
- package/dist/lib/clock.js +106 -0
- package/dist/lib/cn.d.ts +12 -0
- package/dist/lib/cn.js +28 -0
- package/dist/lib/control-props.d.ts +21 -0
- package/dist/lib/control-props.js +14 -0
- package/dist/lib/direction.d.ts +13 -0
- package/dist/lib/direction.js +25 -0
- package/dist/lib/image-size.d.ts +12 -0
- package/dist/lib/image-size.js +66 -0
- package/dist/lib/keycaps.d.ts +21 -0
- package/dist/lib/keycaps.js +62 -0
- package/dist/lib/keyed.d.ts +9 -0
- package/dist/lib/keyed.js +14 -0
- package/dist/lib/keys.d.ts +49 -0
- package/dist/lib/keys.js +159 -0
- package/dist/lib/kit-motion.d.ts +9 -0
- package/dist/lib/kit-motion.js +12 -0
- package/dist/lib/link-check-dialog.d.ts +11 -0
- package/dist/lib/link-check-dialog.js +18 -0
- package/dist/lib/motion-features.d.ts +1 -0
- package/dist/lib/motion-features.js +4 -0
- package/dist/lib/motion-provider.d.ts +5 -0
- package/dist/lib/motion-provider.js +8 -0
- package/dist/lib/motion.d.ts +39 -0
- package/dist/lib/motion.js +49 -0
- package/dist/lib/number-format.d.ts +114 -0
- package/dist/lib/number-format.js +313 -0
- package/dist/lib/patch.d.ts +72 -0
- package/dist/lib/patch.js +344 -0
- package/dist/lib/path.d.ts +34 -0
- package/dist/lib/path.js +85 -0
- package/dist/lib/search.d.ts +10 -0
- package/dist/lib/search.js +20 -0
- package/dist/lib/shiki-themes.d.ts +7 -0
- package/dist/lib/shiki-themes.js +115 -0
- package/dist/lib/shiki.d.ts +101 -0
- package/dist/lib/shiki.js +353 -0
- package/dist/lib/shortcut-context.d.ts +23 -0
- package/dist/lib/shortcut-context.js +29 -0
- package/dist/lib/text.d.ts +56 -0
- package/dist/lib/text.js +149 -0
- package/dist/lib/theme-scope.d.ts +18 -0
- package/dist/lib/theme-scope.js +24 -0
- package/dist/lib/theme.d.ts +40 -0
- package/dist/lib/theme.js +96 -0
- package/dist/lib/tree-keys.d.ts +72 -0
- package/dist/lib/tree-keys.js +126 -0
- package/dist/lib/url.d.ts +17 -0
- package/dist/lib/url.js +38 -0
- package/dist/lib/use-copy-to-clipboard.d.ts +13 -0
- package/dist/lib/use-copy-to-clipboard.js +40 -0
- package/dist/lib/use-delayed-loading.d.ts +11 -0
- package/dist/lib/use-delayed-loading.js +31 -0
- package/dist/lib/use-file-picker.d.ts +26 -0
- package/dist/lib/use-file-picker.js +43 -0
- package/dist/lib/use-hydrated.d.ts +6 -0
- package/dist/lib/use-hydrated.js +11 -0
- package/dist/lib/use-latest.d.ts +12 -0
- package/dist/lib/use-latest.js +22 -0
- package/dist/lib/use-resolved-theme.d.ts +12 -0
- package/dist/lib/use-resolved-theme.js +25 -0
- package/dist/styles/base.css +393 -0
- package/dist/styles/code.css +35 -0
- package/dist/styles/fonts.css +12 -0
- package/dist/styles/index.css +13 -0
- package/dist/styles/tokens.gen.css +494 -0
- package/package.json +208 -4
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useReducedMotion } from "motion/react";
|
|
4
|
+
import * as m from "motion/react-m";
|
|
5
|
+
import { createContext, use, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { KitMotion } from "../lib/kit-motion.js";
|
|
7
|
+
import { spring } from "../lib/motion.js";
|
|
8
|
+
const AppearContext = createContext({ live: false, seen: new Set() });
|
|
9
|
+
export function AppearProvider({ live, children }) {
|
|
10
|
+
const seen = useRef(new Set());
|
|
11
|
+
return _jsx(AppearContext, { value: { live, seen: seen.current }, children: children });
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* A container that arrives as a whole (a row of a live list) and animates itself: what it holds when it
|
|
15
|
+
* mounts doesn't animate again; only what arrives inside it later does.
|
|
16
|
+
*/
|
|
17
|
+
export function AppearScope({ children }) {
|
|
18
|
+
const parent = use(AppearContext);
|
|
19
|
+
const [mounted, setMounted] = useState(false);
|
|
20
|
+
useEffect(() => setMounted(true), []);
|
|
21
|
+
return (_jsx(AppearContext, { value: { live: parent.live && mounted, seen: parent.seen }, children: children }));
|
|
22
|
+
}
|
|
23
|
+
/** Whether a child with this id should play its entrance; the id counts as seen once it has mounted. */
|
|
24
|
+
function useAppear(id) {
|
|
25
|
+
const { live, seen } = use(AppearContext);
|
|
26
|
+
const reduced = useReducedMotion() === true;
|
|
27
|
+
// Decided once per mount and without side effects, so StrictMode's double render agrees.
|
|
28
|
+
const [enter] = useState(() => live && !reduced && !seen.has(id));
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
seen.add(id);
|
|
31
|
+
}, [seen, id]);
|
|
32
|
+
return enter;
|
|
33
|
+
}
|
|
34
|
+
/** A 6 px rise and fade on the snappy spring for an item that just arrived. */
|
|
35
|
+
export function Appear({ id, ...props }) {
|
|
36
|
+
const enter = useAppear(id);
|
|
37
|
+
return (_jsx(KitMotion, { children: _jsx(m.div, { "data-slot": "appear", initial: enter ? { opacity: 0, y: 6 } : false, animate: { opacity: 1, y: 0 }, transition: spring.snappy, ...props }) }));
|
|
38
|
+
}
|
|
39
|
+
/** Content that replaces a skeleton fades in where the skeleton was (no jump, no movement). */
|
|
40
|
+
export function Reveal(props) {
|
|
41
|
+
return (_jsx(KitMotion, { children: _jsx(m.div, { "data-slot": "reveal", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.18, ease: [0.2, 0, 0, 1] }, ...props }) }));
|
|
42
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { PersonaTint } from "../_bundled/tokens/index.js";
|
|
2
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
3
|
+
import { type VariantProps } from "../lib/cn.js";
|
|
4
|
+
export type Persona = PersonaTint;
|
|
5
|
+
export type AvatarSize = "xs" | "sm" | "md" | "lg" | "xl";
|
|
6
|
+
export type AvatarStatus = "online" | "busy" | "away" | "offline";
|
|
7
|
+
declare const avatarStyles: import("tailwind-variants").TVReturnType<{
|
|
8
|
+
size: {
|
|
9
|
+
xs: {
|
|
10
|
+
wrapper: string;
|
|
11
|
+
root: string;
|
|
12
|
+
status: string;
|
|
13
|
+
};
|
|
14
|
+
sm: {
|
|
15
|
+
wrapper: string;
|
|
16
|
+
root: string;
|
|
17
|
+
status: string;
|
|
18
|
+
};
|
|
19
|
+
md: {
|
|
20
|
+
wrapper: string;
|
|
21
|
+
root: string;
|
|
22
|
+
status: string;
|
|
23
|
+
};
|
|
24
|
+
lg: {
|
|
25
|
+
wrapper: string;
|
|
26
|
+
root: string;
|
|
27
|
+
status: string;
|
|
28
|
+
};
|
|
29
|
+
xl: {
|
|
30
|
+
wrapper: string;
|
|
31
|
+
root: string;
|
|
32
|
+
status: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
shape: {
|
|
36
|
+
circle: {
|
|
37
|
+
root: string;
|
|
38
|
+
};
|
|
39
|
+
square: {};
|
|
40
|
+
};
|
|
41
|
+
status: {
|
|
42
|
+
online: {
|
|
43
|
+
status: string;
|
|
44
|
+
};
|
|
45
|
+
busy: {
|
|
46
|
+
status: string;
|
|
47
|
+
};
|
|
48
|
+
away: {
|
|
49
|
+
status: string;
|
|
50
|
+
};
|
|
51
|
+
offline: {
|
|
52
|
+
status: string;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}, {
|
|
56
|
+
wrapper: string;
|
|
57
|
+
root: string[];
|
|
58
|
+
image: string[];
|
|
59
|
+
fallback: string;
|
|
60
|
+
status: string[];
|
|
61
|
+
}, undefined, {
|
|
62
|
+
size: {
|
|
63
|
+
xs: {
|
|
64
|
+
wrapper: string;
|
|
65
|
+
root: string;
|
|
66
|
+
status: string;
|
|
67
|
+
};
|
|
68
|
+
sm: {
|
|
69
|
+
wrapper: string;
|
|
70
|
+
root: string;
|
|
71
|
+
status: string;
|
|
72
|
+
};
|
|
73
|
+
md: {
|
|
74
|
+
wrapper: string;
|
|
75
|
+
root: string;
|
|
76
|
+
status: string;
|
|
77
|
+
};
|
|
78
|
+
lg: {
|
|
79
|
+
wrapper: string;
|
|
80
|
+
root: string;
|
|
81
|
+
status: string;
|
|
82
|
+
};
|
|
83
|
+
xl: {
|
|
84
|
+
wrapper: string;
|
|
85
|
+
root: string;
|
|
86
|
+
status: string;
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
shape: {
|
|
90
|
+
circle: {
|
|
91
|
+
root: string;
|
|
92
|
+
};
|
|
93
|
+
square: {};
|
|
94
|
+
};
|
|
95
|
+
status: {
|
|
96
|
+
online: {
|
|
97
|
+
status: string;
|
|
98
|
+
};
|
|
99
|
+
busy: {
|
|
100
|
+
status: string;
|
|
101
|
+
};
|
|
102
|
+
away: {
|
|
103
|
+
status: string;
|
|
104
|
+
};
|
|
105
|
+
offline: {
|
|
106
|
+
status: string;
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
}, {
|
|
110
|
+
wrapper: string;
|
|
111
|
+
root: string[];
|
|
112
|
+
image: string[];
|
|
113
|
+
fallback: string;
|
|
114
|
+
status: string[];
|
|
115
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
116
|
+
size: {
|
|
117
|
+
xs: {
|
|
118
|
+
wrapper: string;
|
|
119
|
+
root: string;
|
|
120
|
+
status: string;
|
|
121
|
+
};
|
|
122
|
+
sm: {
|
|
123
|
+
wrapper: string;
|
|
124
|
+
root: string;
|
|
125
|
+
status: string;
|
|
126
|
+
};
|
|
127
|
+
md: {
|
|
128
|
+
wrapper: string;
|
|
129
|
+
root: string;
|
|
130
|
+
status: string;
|
|
131
|
+
};
|
|
132
|
+
lg: {
|
|
133
|
+
wrapper: string;
|
|
134
|
+
root: string;
|
|
135
|
+
status: string;
|
|
136
|
+
};
|
|
137
|
+
xl: {
|
|
138
|
+
wrapper: string;
|
|
139
|
+
root: string;
|
|
140
|
+
status: string;
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
shape: {
|
|
144
|
+
circle: {
|
|
145
|
+
root: string;
|
|
146
|
+
};
|
|
147
|
+
square: {};
|
|
148
|
+
};
|
|
149
|
+
status: {
|
|
150
|
+
online: {
|
|
151
|
+
status: string;
|
|
152
|
+
};
|
|
153
|
+
busy: {
|
|
154
|
+
status: string;
|
|
155
|
+
};
|
|
156
|
+
away: {
|
|
157
|
+
status: string;
|
|
158
|
+
};
|
|
159
|
+
offline: {
|
|
160
|
+
status: string;
|
|
161
|
+
};
|
|
162
|
+
};
|
|
163
|
+
}, {
|
|
164
|
+
wrapper: string;
|
|
165
|
+
root: string[];
|
|
166
|
+
image: string[];
|
|
167
|
+
fallback: string;
|
|
168
|
+
status: string[];
|
|
169
|
+
}>>;
|
|
170
|
+
export interface AvatarProps extends Omit<ComponentProps<"span">, "children">, Omit<VariantProps<typeof avatarStyles>, "status" | "size"> {
|
|
171
|
+
/** The person's (or a bot's) name: the accessible label, the initials and the tint. */
|
|
172
|
+
name: string;
|
|
173
|
+
src?: string | undefined;
|
|
174
|
+
size?: AvatarSize;
|
|
175
|
+
/** Presence dot. Announced in the accessible label, not just shown in color. */
|
|
176
|
+
status?: AvatarStatus | undefined;
|
|
177
|
+
/** Override the hashed persona tint. */
|
|
178
|
+
persona?: Persona;
|
|
179
|
+
/** Replace the initials, e.g. with an icon for a bot or a team. */
|
|
180
|
+
fallback?: ReactNode;
|
|
181
|
+
/** Optional role/descriptor appended to the label, e.g. "CEO". */
|
|
182
|
+
description?: string;
|
|
183
|
+
}
|
|
184
|
+
/** Image avatar with deterministic tinted initials as fallback, plus optional presence dot. */
|
|
185
|
+
export declare function Avatar({ name, src, size, shape, status, persona, fallback, description, className, ...props }: AvatarProps): import("react").JSX.Element;
|
|
186
|
+
export interface AvatarGroupProps extends ComponentProps<"div"> {
|
|
187
|
+
size?: AvatarSize;
|
|
188
|
+
/** Show at most this many avatars, then a "+N" chip. */
|
|
189
|
+
max?: number;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Overlapping stack of avatars. Set `--avatar-ring` to the surface color when the group sits on
|
|
193
|
+
* anything other than the page background (e.g. `[--avatar-ring:var(--surface)]` inside a Card).
|
|
194
|
+
*/
|
|
195
|
+
export declare function AvatarGroup({ size, max, className, children, ...props }: AvatarGroupProps): import("react").JSX.Element;
|
|
196
|
+
export {};
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Avatar as BaseAvatar } from "@base-ui/react/avatar";
|
|
4
|
+
import { Children, createContext, use } from "react";
|
|
5
|
+
import { useUiLocale, useUiStrings } from "../i18n/provider.js";
|
|
6
|
+
import { cn, tv } from "../lib/cn.js";
|
|
7
|
+
import { initialsOf, spokenParts } from "../lib/text.js";
|
|
8
|
+
import { Group } from "./group.js";
|
|
9
|
+
const PERSONAS = [
|
|
10
|
+
"clay",
|
|
11
|
+
"ochre",
|
|
12
|
+
"sage",
|
|
13
|
+
"teal",
|
|
14
|
+
"slate",
|
|
15
|
+
"iris",
|
|
16
|
+
"plum",
|
|
17
|
+
"rose",
|
|
18
|
+
];
|
|
19
|
+
/** Static class strings so Tailwind can see every tint. */
|
|
20
|
+
const PERSONA_TINT = {
|
|
21
|
+
clay: "bg-persona-clay-bg text-persona-clay-fg",
|
|
22
|
+
ochre: "bg-persona-ochre-bg text-persona-ochre-fg",
|
|
23
|
+
sage: "bg-persona-sage-bg text-persona-sage-fg",
|
|
24
|
+
teal: "bg-persona-teal-bg text-persona-teal-fg",
|
|
25
|
+
slate: "bg-persona-slate-bg text-persona-slate-fg",
|
|
26
|
+
iris: "bg-persona-iris-bg text-persona-iris-fg",
|
|
27
|
+
plum: "bg-persona-plum-bg text-persona-plum-fg",
|
|
28
|
+
rose: "bg-persona-rose-bg text-persona-rose-fg",
|
|
29
|
+
};
|
|
30
|
+
/** Deterministic persona for a name (FNV-1a). The same name always gets the same tint. */
|
|
31
|
+
function personaFor(name) {
|
|
32
|
+
let hash = 0x811c9dc5;
|
|
33
|
+
for (const char of name.trim().toLowerCase()) {
|
|
34
|
+
hash ^= char.codePointAt(0) ?? 0;
|
|
35
|
+
hash = Math.imul(hash, 0x01000193);
|
|
36
|
+
}
|
|
37
|
+
return PERSONAS[(hash >>> 0) % PERSONAS.length] ?? "slate";
|
|
38
|
+
}
|
|
39
|
+
const avatarStyles = tv({
|
|
40
|
+
slots: {
|
|
41
|
+
wrapper: "relative inline-flex shrink-0 align-middle",
|
|
42
|
+
root: [
|
|
43
|
+
"relative inline-flex size-full select-none items-center justify-center overflow-hidden font-medium leading-none",
|
|
44
|
+
// Hairline inner edge keeps photos from bleeding into the surface behind them.
|
|
45
|
+
"after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] after:shadow-[inset_0_0_0_1px_var(--border-subtle)]",
|
|
46
|
+
],
|
|
47
|
+
image: [
|
|
48
|
+
"size-full object-cover transition-opacity duration-(--duration-base) ease-(--ease-standard)",
|
|
49
|
+
"data-starting-style:opacity-0 motion-reduce:transition-none",
|
|
50
|
+
],
|
|
51
|
+
fallback: "flex size-full items-center justify-center [&_svg]:size-[55%]",
|
|
52
|
+
status: [
|
|
53
|
+
"absolute end-0 bottom-0 rounded-full",
|
|
54
|
+
"ring-[1.5px] ring-[color:var(--avatar-ring,var(--bg))]",
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
variants: {
|
|
58
|
+
size: {
|
|
59
|
+
xs: { wrapper: "size-5", root: "text-[0.5625rem]", status: "size-1.5" },
|
|
60
|
+
sm: { wrapper: "size-6", root: "text-[0.625rem]", status: "size-2" },
|
|
61
|
+
md: { wrapper: "size-8", root: "text-xs", status: "size-2.5 ring-2" },
|
|
62
|
+
lg: { wrapper: "size-10", root: "text-sm", status: "size-3 ring-2" },
|
|
63
|
+
xl: { wrapper: "size-14", root: "text-md", status: "size-3.5 ring-[2.5px] end-0.5 bottom-0.5" },
|
|
64
|
+
},
|
|
65
|
+
shape: {
|
|
66
|
+
circle: { root: "rounded-full" },
|
|
67
|
+
square: {},
|
|
68
|
+
},
|
|
69
|
+
status: {
|
|
70
|
+
online: { status: "bg-success" },
|
|
71
|
+
busy: { status: "bg-danger" },
|
|
72
|
+
away: { status: "bg-warning" },
|
|
73
|
+
// Hollow ring: reads as "absent" without relying on hue.
|
|
74
|
+
offline: {
|
|
75
|
+
status: "bg-[color:var(--avatar-ring,var(--bg))] shadow-[inset_0_0_0_1.5px_var(--fg-subtle)]",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
compoundVariants: [
|
|
80
|
+
{ shape: "square", size: "xs", class: { root: "rounded-sm" } },
|
|
81
|
+
{ shape: "square", size: "sm", class: { root: "rounded-md" } },
|
|
82
|
+
{ shape: "square", size: "md", class: { root: "rounded-lg" } },
|
|
83
|
+
{ shape: "square", size: "lg", class: { root: "rounded-xl" } },
|
|
84
|
+
{ shape: "square", size: "xl", class: { root: "rounded-2xl", status: "-end-0.5 -bottom-0.5" } },
|
|
85
|
+
],
|
|
86
|
+
defaultVariants: { size: "md", shape: "circle" },
|
|
87
|
+
});
|
|
88
|
+
const AvatarSizeContext = createContext(undefined);
|
|
89
|
+
/** Image avatar with deterministic tinted initials as fallback, plus optional presence dot. */
|
|
90
|
+
export function Avatar({ name, src, size, shape, status, persona, fallback, description, className, ...props }) {
|
|
91
|
+
const groupSize = use(AvatarSizeContext);
|
|
92
|
+
const locale = useUiLocale();
|
|
93
|
+
const styles = avatarStyles({ size: size ?? groupSize ?? "md", shape, status });
|
|
94
|
+
const statusWords = useUiStrings().primitives.avatarStatus;
|
|
95
|
+
const label = spokenParts([name, description, status && statusWords[status]].flatMap((part) => (part ? [part] : [])), locale);
|
|
96
|
+
return (_jsxs("span", { "data-slot": "avatar", role: "img", "aria-label": label, className: styles.wrapper({ className }), ...props, children: [_jsxs(BaseAvatar.Root, { "data-slot": "avatar-root", className: cn(styles.root(), PERSONA_TINT[persona ?? personaFor(name)]), children: [src && _jsx(BaseAvatar.Image, { src: src, alt: "", className: styles.image() }), _jsx(BaseAvatar.Fallback, { className: styles.fallback(), children: fallback ?? initialsOf(name, locale) })] }), status && _jsx("span", { "aria-hidden": true, "data-slot": "avatar-status", className: styles.status() })] }));
|
|
97
|
+
}
|
|
98
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
99
|
+
/* AvatarGroup */
|
|
100
|
+
/* ------------------------------------------------------------------------------------------ */
|
|
101
|
+
const avatarGroup = tv({
|
|
102
|
+
slots: {
|
|
103
|
+
root: [
|
|
104
|
+
"flex items-center",
|
|
105
|
+
// Each avatar is cut out of the surface behind it with a ring in the surface color.
|
|
106
|
+
"[&_[data-slot=avatar-root]]:ring-2 [&_[data-slot=avatar-root]]:ring-[color:var(--avatar-ring,var(--bg))]",
|
|
107
|
+
],
|
|
108
|
+
overflow: [
|
|
109
|
+
"relative inline-flex shrink-0 select-none items-center justify-center rounded-full font-medium tabular",
|
|
110
|
+
"bg-bg-muted text-fg-muted ring-2 ring-[color:var(--avatar-ring,var(--bg))]",
|
|
111
|
+
],
|
|
112
|
+
},
|
|
113
|
+
variants: {
|
|
114
|
+
size: {
|
|
115
|
+
xs: { root: "-space-x-1", overflow: "size-5 text-[0.5625rem]" },
|
|
116
|
+
sm: { root: "-space-x-1.5", overflow: "size-6 text-[0.625rem]" },
|
|
117
|
+
md: { root: "-space-x-2", overflow: "size-8 text-xs" },
|
|
118
|
+
lg: { root: "-space-x-2.5", overflow: "size-10 text-sm" },
|
|
119
|
+
xl: { root: "-space-x-3.5", overflow: "size-14 text-base" },
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
defaultVariants: { size: "md" },
|
|
123
|
+
});
|
|
124
|
+
/**
|
|
125
|
+
* Overlapping stack of avatars. Set `--avatar-ring` to the surface color when the group sits on
|
|
126
|
+
* anything other than the page background (e.g. `[--avatar-ring:var(--surface)]` inside a Card).
|
|
127
|
+
*/
|
|
128
|
+
export function AvatarGroup({ size = "md", max, className, children, ...props }) {
|
|
129
|
+
const t = useUiStrings().primitives;
|
|
130
|
+
const items = Children.toArray(children);
|
|
131
|
+
const visible = max === undefined ? items : items.slice(0, Math.max(0, max));
|
|
132
|
+
const hidden = items.length - visible.length;
|
|
133
|
+
const styles = avatarGroup({ size });
|
|
134
|
+
return (_jsx(AvatarSizeContext, { value: size, children: _jsxs(Group, { "data-slot": "avatar-group", className: styles.root({ className }), ...props, children: [visible, hidden > 0 && (_jsxs("span", { "data-slot": "avatar-overflow", role: "img", "aria-label": t.moreAvatars({ count: hidden }), className: styles.overflow(), children: ["+", hidden] }))] }) }));
|
|
135
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import type { ComponentProps, ReactNode } from "react";
|
|
2
|
+
import { type VariantProps } from "../lib/cn.js";
|
|
3
|
+
declare const badgeStyles: import("tailwind-variants").TVReturnType<{
|
|
4
|
+
variant: {
|
|
5
|
+
/** Quiet gray — metadata, counts, model names. */
|
|
6
|
+
neutral: {
|
|
7
|
+
root: string;
|
|
8
|
+
dot: string;
|
|
9
|
+
};
|
|
10
|
+
/** Hairline only — for dense lists where fills get noisy. */
|
|
11
|
+
outline: {
|
|
12
|
+
root: string;
|
|
13
|
+
dot: string;
|
|
14
|
+
};
|
|
15
|
+
success: {
|
|
16
|
+
root: string;
|
|
17
|
+
dot: string;
|
|
18
|
+
};
|
|
19
|
+
warning: {
|
|
20
|
+
root: string;
|
|
21
|
+
dot: string;
|
|
22
|
+
};
|
|
23
|
+
danger: {
|
|
24
|
+
root: string;
|
|
25
|
+
dot: string;
|
|
26
|
+
};
|
|
27
|
+
/** Neutral wash + fg-muted ink, like every info surface; its `Info` icon is what says "info". */
|
|
28
|
+
info: {
|
|
29
|
+
root: string;
|
|
30
|
+
dot: string;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
size: {
|
|
34
|
+
sm: {
|
|
35
|
+
root: string;
|
|
36
|
+
};
|
|
37
|
+
md: {
|
|
38
|
+
root: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
}, {
|
|
42
|
+
root: string[];
|
|
43
|
+
dot: string;
|
|
44
|
+
}, undefined, {
|
|
45
|
+
variant: {
|
|
46
|
+
/** Quiet gray — metadata, counts, model names. */
|
|
47
|
+
neutral: {
|
|
48
|
+
root: string;
|
|
49
|
+
dot: string;
|
|
50
|
+
};
|
|
51
|
+
/** Hairline only — for dense lists where fills get noisy. */
|
|
52
|
+
outline: {
|
|
53
|
+
root: string;
|
|
54
|
+
dot: string;
|
|
55
|
+
};
|
|
56
|
+
success: {
|
|
57
|
+
root: string;
|
|
58
|
+
dot: string;
|
|
59
|
+
};
|
|
60
|
+
warning: {
|
|
61
|
+
root: string;
|
|
62
|
+
dot: string;
|
|
63
|
+
};
|
|
64
|
+
danger: {
|
|
65
|
+
root: string;
|
|
66
|
+
dot: string;
|
|
67
|
+
};
|
|
68
|
+
/** Neutral wash + fg-muted ink, like every info surface; its `Info` icon is what says "info". */
|
|
69
|
+
info: {
|
|
70
|
+
root: string;
|
|
71
|
+
dot: string;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
size: {
|
|
75
|
+
sm: {
|
|
76
|
+
root: string;
|
|
77
|
+
};
|
|
78
|
+
md: {
|
|
79
|
+
root: string;
|
|
80
|
+
};
|
|
81
|
+
};
|
|
82
|
+
}, {
|
|
83
|
+
root: string[];
|
|
84
|
+
dot: string;
|
|
85
|
+
}, import("tailwind-variants").TVReturnTypeLike<{
|
|
86
|
+
variant: {
|
|
87
|
+
/** Quiet gray — metadata, counts, model names. */
|
|
88
|
+
neutral: {
|
|
89
|
+
root: string;
|
|
90
|
+
dot: string;
|
|
91
|
+
};
|
|
92
|
+
/** Hairline only — for dense lists where fills get noisy. */
|
|
93
|
+
outline: {
|
|
94
|
+
root: string;
|
|
95
|
+
dot: string;
|
|
96
|
+
};
|
|
97
|
+
success: {
|
|
98
|
+
root: string;
|
|
99
|
+
dot: string;
|
|
100
|
+
};
|
|
101
|
+
warning: {
|
|
102
|
+
root: string;
|
|
103
|
+
dot: string;
|
|
104
|
+
};
|
|
105
|
+
danger: {
|
|
106
|
+
root: string;
|
|
107
|
+
dot: string;
|
|
108
|
+
};
|
|
109
|
+
/** Neutral wash + fg-muted ink, like every info surface; its `Info` icon is what says "info". */
|
|
110
|
+
info: {
|
|
111
|
+
root: string;
|
|
112
|
+
dot: string;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
size: {
|
|
116
|
+
sm: {
|
|
117
|
+
root: string;
|
|
118
|
+
};
|
|
119
|
+
md: {
|
|
120
|
+
root: string;
|
|
121
|
+
};
|
|
122
|
+
};
|
|
123
|
+
}, {
|
|
124
|
+
root: string[];
|
|
125
|
+
dot: string;
|
|
126
|
+
}>>;
|
|
127
|
+
export type BadgeVariants = VariantProps<typeof badgeStyles>;
|
|
128
|
+
export interface BadgeProps extends ComponentProps<"span">, BadgeVariants {
|
|
129
|
+
/** Leading status dot. Pair with text — the dot alone never carries meaning. */
|
|
130
|
+
dot?: boolean;
|
|
131
|
+
/**
|
|
132
|
+
* Leading icon. The info variant defaults to lucide `Info`: info is neutral, not the accent, so the
|
|
133
|
+
* icon is what tells it apart from a plain neutral badge. Pass your own icon to
|
|
134
|
+
* replace it, or `false` to hide it. A `dot` takes the icon's place.
|
|
135
|
+
*/
|
|
136
|
+
icon?: ReactNode | false;
|
|
137
|
+
}
|
|
138
|
+
/** Small, calm label: subtle tint + matching foreground. Never loud. */
|
|
139
|
+
export declare function Badge({ variant, size, dot, icon, className, children, ...props }: BadgeProps): import("react").JSX.Element;
|
|
140
|
+
declare const countBadge: import("tailwind-variants").TVReturnType<{
|
|
141
|
+
tone: {
|
|
142
|
+
/** Gray pill — unread counts in sidebars. */
|
|
143
|
+
neutral: "bg-bg-muted text-fg-muted";
|
|
144
|
+
/** Ink pill — draws the eye without color. */
|
|
145
|
+
ink: "bg-primary text-primary-fg";
|
|
146
|
+
/** Something waits on the person (needs-you counts). */
|
|
147
|
+
warning: "bg-warning-subtle text-warning-fg";
|
|
148
|
+
/** Errors or blocking items only. */
|
|
149
|
+
danger: "bg-danger text-danger-on-solid";
|
|
150
|
+
};
|
|
151
|
+
}, undefined, string[], {
|
|
152
|
+
tone: {
|
|
153
|
+
/** Gray pill — unread counts in sidebars. */
|
|
154
|
+
neutral: "bg-bg-muted text-fg-muted";
|
|
155
|
+
/** Ink pill — draws the eye without color. */
|
|
156
|
+
ink: "bg-primary text-primary-fg";
|
|
157
|
+
/** Something waits on the person (needs-you counts). */
|
|
158
|
+
warning: "bg-warning-subtle text-warning-fg";
|
|
159
|
+
/** Errors or blocking items only. */
|
|
160
|
+
danger: "bg-danger text-danger-on-solid";
|
|
161
|
+
};
|
|
162
|
+
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
163
|
+
tone: {
|
|
164
|
+
/** Gray pill — unread counts in sidebars. */
|
|
165
|
+
neutral: "bg-bg-muted text-fg-muted";
|
|
166
|
+
/** Ink pill — draws the eye without color. */
|
|
167
|
+
ink: "bg-primary text-primary-fg";
|
|
168
|
+
/** Something waits on the person (needs-you counts). */
|
|
169
|
+
warning: "bg-warning-subtle text-warning-fg";
|
|
170
|
+
/** Errors or blocking items only. */
|
|
171
|
+
danger: "bg-danger text-danger-on-solid";
|
|
172
|
+
};
|
|
173
|
+
}, undefined>>;
|
|
174
|
+
export interface CountBadgeProps extends Omit<ComponentProps<"span">, "children">, VariantProps<typeof countBadge> {
|
|
175
|
+
count: number;
|
|
176
|
+
/** Values above `max` render as `max+`. */
|
|
177
|
+
max?: number;
|
|
178
|
+
/** Render a `0` instead of nothing. */
|
|
179
|
+
showZero?: boolean;
|
|
180
|
+
}
|
|
181
|
+
/** Numeric pill for unread/pending counts. The number rolls softly when it changes. */
|
|
182
|
+
export declare function CountBadge({ count, max, showZero, tone, className, ...props }: CountBadgeProps): import("react").JSX.Element | null;
|
|
183
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Info } from "lucide-react";
|
|
4
|
+
import { AnimatePresence } from "motion/react";
|
|
5
|
+
import * as m from "motion/react-m";
|
|
6
|
+
import { cn, tv } from "../lib/cn.js";
|
|
7
|
+
import { KitMotion } from "../lib/kit-motion.js";
|
|
8
|
+
import { spring } from "../lib/motion.js";
|
|
9
|
+
const badgeStyles = tv({
|
|
10
|
+
slots: {
|
|
11
|
+
root: [
|
|
12
|
+
"inline-flex shrink-0 select-none items-center whitespace-nowrap font-medium tabular",
|
|
13
|
+
"[&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-3",
|
|
14
|
+
],
|
|
15
|
+
dot: "size-1.5 shrink-0 rounded-full",
|
|
16
|
+
},
|
|
17
|
+
variants: {
|
|
18
|
+
variant: {
|
|
19
|
+
/** Quiet gray — metadata, counts, model names. */
|
|
20
|
+
neutral: { root: "bg-bg-muted text-fg-muted", dot: "bg-fg-subtle" },
|
|
21
|
+
/** Hairline only — for dense lists where fills get noisy. */
|
|
22
|
+
outline: { root: "border border-border text-fg-muted", dot: "bg-fg-subtle" },
|
|
23
|
+
success: { root: "bg-success-subtle text-success-fg", dot: "bg-success" },
|
|
24
|
+
warning: { root: "bg-warning-subtle text-warning-fg", dot: "bg-warning" },
|
|
25
|
+
danger: { root: "bg-danger-subtle text-danger-fg", dot: "bg-danger" },
|
|
26
|
+
/** Neutral wash + fg-muted ink, like every info surface; its `Info` icon is what says "info". */
|
|
27
|
+
info: { root: "bg-info-subtle text-info-fg", dot: "bg-info" },
|
|
28
|
+
},
|
|
29
|
+
size: {
|
|
30
|
+
// Text keeps 8 px from a filled edge at every size (system.md §2.3).
|
|
31
|
+
sm: { root: "h-4.5 gap-1 rounded-sm px-2 text-xs" },
|
|
32
|
+
md: { root: "h-5.5 gap-1.5 rounded-md px-2 text-xs" },
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
defaultVariants: { variant: "neutral", size: "md" },
|
|
36
|
+
});
|
|
37
|
+
/** Small, calm label: subtle tint + matching foreground. Never loud. */
|
|
38
|
+
export function Badge({ variant, size, dot = false, icon, className, children, ...props }) {
|
|
39
|
+
const styles = badgeStyles({ variant, size });
|
|
40
|
+
const lead = dot || icon === false ? null : (icon ?? (variant === "info" ? _jsx(Info, { "aria-hidden": true }) : null));
|
|
41
|
+
return (_jsxs("span", { "data-slot": "badge", className: styles.root({ className }), ...props, children: [dot && _jsx("span", { "aria-hidden": true, className: styles.dot() }), lead, children] }));
|
|
42
|
+
}
|
|
43
|
+
const countBadge = tv({
|
|
44
|
+
base: [
|
|
45
|
+
"relative inline-flex h-4 min-w-4 shrink-0 select-none items-center justify-center overflow-hidden rounded-full px-1",
|
|
46
|
+
"text-xs font-semibold leading-none tabular",
|
|
47
|
+
],
|
|
48
|
+
variants: {
|
|
49
|
+
tone: {
|
|
50
|
+
/** Gray pill — unread counts in sidebars. */
|
|
51
|
+
neutral: "bg-bg-muted text-fg-muted",
|
|
52
|
+
/** Ink pill — draws the eye without color. */
|
|
53
|
+
ink: "bg-primary text-primary-fg",
|
|
54
|
+
/** Something waits on the person (needs-you counts). */
|
|
55
|
+
warning: "bg-warning-subtle text-warning-fg",
|
|
56
|
+
/** Errors or blocking items only. */
|
|
57
|
+
danger: "bg-danger text-danger-on-solid",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
defaultVariants: { tone: "neutral" },
|
|
61
|
+
});
|
|
62
|
+
/** Numeric pill for unread/pending counts. The number rolls softly when it changes. */
|
|
63
|
+
export function CountBadge({ count, max = 99, showZero = false, tone, className, ...props }) {
|
|
64
|
+
if (count <= 0 && !showZero)
|
|
65
|
+
return null;
|
|
66
|
+
const label = count > max ? `${max}+` : String(Math.max(0, count));
|
|
67
|
+
return (_jsx("span", { "data-slot": "count-badge", className: cn(countBadge({ tone }), className), ...props, children: _jsx(KitMotion, { children: _jsx(AnimatePresence, { initial: false, mode: "popLayout", children: _jsx(m.span, { initial: { y: 6, opacity: 0 }, animate: { y: 0, opacity: 1 }, exit: { y: -6, opacity: 0 }, transition: spring.snappy, children: label }, label) }) }) }));
|
|
68
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
+
/**
|
|
3
|
+
* A panel of blocks: every block has the same header — its title with
|
|
4
|
+
* a caret, then a grip — every block collapses, and blocks reorder by dragging the grip or with the
|
|
5
|
+
* arrow keys on it. Collapse and reorder are instant: nothing animates a size or a position.
|
|
6
|
+
* Order and collapsed state live in a `BlockLayout` the host keeps and remembers.
|
|
7
|
+
*/
|
|
8
|
+
export interface BlockLayout {
|
|
9
|
+
/** Block ids, top to bottom. */
|
|
10
|
+
readonly order: readonly string[];
|
|
11
|
+
/** Ids of collapsed blocks. */
|
|
12
|
+
readonly collapsed: readonly string[];
|
|
13
|
+
}
|
|
14
|
+
export interface BlockStackProps extends Omit<ComponentProps<"div">, "children"> {
|
|
15
|
+
readonly layout: BlockLayout;
|
|
16
|
+
readonly onLayoutChange: (layout: BlockLayout) => void;
|
|
17
|
+
/** `<Block>`s; their order on screen is `layout.order` (blocks it doesn't list follow, as given). */
|
|
18
|
+
readonly children: ReactNode;
|
|
19
|
+
}
|
|
20
|
+
export declare function BlockStack({ layout, onLayoutChange, className, children, ...props }: BlockStackProps): import("react").JSX.Element;
|
|
21
|
+
export interface BlockProps extends Omit<ComponentProps<"section">, "title" | "id"> {
|
|
22
|
+
/** Stable id: the key in `BlockLayout`. */
|
|
23
|
+
readonly id: string;
|
|
24
|
+
/** Heading text ("Context"). Names the region and the grip. */
|
|
25
|
+
readonly title: string;
|
|
26
|
+
/**
|
|
27
|
+
* Quiet trailing summary in the header ("48 % used", "2 running"), shown collapsed too. Text only:
|
|
28
|
+
* a block header holds no buttons besides its own; actions live in the body.
|
|
29
|
+
*/
|
|
30
|
+
readonly meta?: ReactNode;
|
|
31
|
+
}
|
|
32
|
+
/** One block of a `BlockStack`: the shared header (title and caret, meta, grip), then its body. */
|
|
33
|
+
export declare function Block({ id, title, meta, className, children, ...props }: BlockProps): import("react").JSX.Element;
|