@twoux/ui 0.0.0-stage → 0.1.1
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.md +61 -0
- package/LICENSES/Apache-2.0.txt +202 -0
- package/NOTICE.md +64 -0
- package/README.md +211 -2
- package/SHADCN-DIFF.md +228 -0
- package/agent/AGENTS.md +358 -0
- package/agent/docs-md/accessibility.md +49 -0
- package/agent/docs-md/action-catalogue.md +81 -0
- package/agent/docs-md/actions.md +74 -0
- package/agent/docs-md/agent-activity-log.md +70 -0
- package/agent/docs-md/agent-permissions.md +82 -0
- package/agent/docs-md/agent-run.md +90 -0
- package/agent/docs-md/agents-on-behalf.md +74 -0
- package/agent/docs-md/ai-column.md +85 -0
- package/agent/docs-md/ai-origin-label.md +70 -0
- package/agent/docs-md/announcing-features.md +73 -0
- package/agent/docs-md/answer-actions.md +81 -0
- package/agent/docs-md/app-shell.md +114 -0
- package/agent/docs-md/artifact-card.md +89 -0
- package/agent/docs-md/ask-on-screen.md +72 -0
- package/agent/docs-md/assistant-page.md +100 -0
- package/agent/docs-md/assistant-panel.md +142 -0
- package/agent/docs-md/avatar.md +82 -0
- package/agent/docs-md/banner.md +106 -0
- package/agent/docs-md/board.md +163 -0
- package/agent/docs-md/breadcrumb.md +89 -0
- package/agent/docs-md/breakdown-table.md +97 -0
- package/agent/docs-md/bulk-action-bar.md +112 -0
- package/agent/docs-md/button.md +293 -0
- package/agent/docs-md/card.md +115 -0
- package/agent/docs-md/change-list.md +99 -0
- package/agent/docs-md/change-request.md +166 -0
- package/agent/docs-md/chart-area.md +87 -0
- package/agent/docs-md/chart-bar.md +100 -0
- package/agent/docs-md/chart-box-plot.md +88 -0
- package/agent/docs-md/chart-bullet.md +89 -0
- package/agent/docs-md/chart-candlestick.md +112 -0
- package/agent/docs-md/chart-card-line.md +120 -0
- package/agent/docs-md/chart-donut.md +90 -0
- package/agent/docs-md/chart-drill-down.md +72 -0
- package/agent/docs-md/chart-funnel.md +71 -0
- package/agent/docs-md/chart-heatmap.md +76 -0
- package/agent/docs-md/chart-histogram.md +81 -0
- package/agent/docs-md/chart-map.md +165 -0
- package/agent/docs-md/chart-mixed.md +88 -0
- package/agent/docs-md/chart-price.md +92 -0
- package/agent/docs-md/chart-radial-bars.md +109 -0
- package/agent/docs-md/chart-radial-lines.md +111 -0
- package/agent/docs-md/chart-sankey.md +104 -0
- package/agent/docs-md/chart-scatter.md +87 -0
- package/agent/docs-md/chart-share-bar.md +89 -0
- package/agent/docs-md/chart-treemap.md +98 -0
- package/agent/docs-md/chart-waterfall.md +93 -0
- package/agent/docs-md/checkbox.md +131 -0
- package/agent/docs-md/choosing-selection-control.md +71 -0
- package/agent/docs-md/citations-sources.md +79 -0
- package/agent/docs-md/citing-sources.md +63 -0
- package/agent/docs-md/clarify-before-acting.md +69 -0
- package/agent/docs-md/clarifying-question.md +92 -0
- package/agent/docs-md/code-block.md +174 -0
- package/agent/docs-md/colours.md +87 -0
- package/agent/docs-md/columns-menu.md +140 -0
- package/agent/docs-md/combobox.md +161 -0
- package/agent/docs-md/command-palette.md +126 -0
- package/agent/docs-md/confidence-note.md +87 -0
- package/agent/docs-md/configurable-dashboard.md +76 -0
- package/agent/docs-md/confirmation-dialog.md +93 -0
- package/agent/docs-md/connected-tools.md +80 -0
- package/agent/docs-md/connector-picker.md +127 -0
- package/agent/docs-md/consistency.md +53 -0
- package/agent/docs-md/content-density.md +61 -0
- package/agent/docs-md/conversation-history.md +78 -0
- package/agent/docs-md/create-record.md +80 -0
- package/agent/docs-md/csv-import.md +88 -0
- package/agent/docs-md/dashboard-items.md +78 -0
- package/agent/docs-md/data-export.md +61 -0
- package/agent/docs-md/data-grid.md +137 -0
- package/agent/docs-md/data-vis-colours.md +76 -0
- package/agent/docs-md/data-visualisation.md +76 -0
- package/agent/docs-md/date-picker.md +178 -0
- package/agent/docs-md/delete-record.md +75 -0
- package/agent/docs-md/dense-table.md +495 -0
- package/agent/docs-md/design-tokens.md +53 -0
- package/agent/docs-md/disabled-read-only.md +75 -0
- package/agent/docs-md/drag-and-drop.md +74 -0
- package/agent/docs-md/dropdown-menu.md +137 -0
- package/agent/docs-md/edit-record.md +73 -0
- package/agent/docs-md/empty-loading-error.md +78 -0
- package/agent/docs-md/empty-state.md +128 -0
- package/agent/docs-md/error-messages.md +75 -0
- package/agent/docs-md/error-state.md +123 -0
- package/agent/docs-md/export-menu.md +86 -0
- package/agent/docs-md/feedback-prompt.md +88 -0
- package/agent/docs-md/field-mapping.md +78 -0
- package/agent/docs-md/file-drop.md +110 -0
- package/agent/docs-md/filter-bar.md +138 -0
- package/agent/docs-md/filter-panel.md +103 -0
- package/agent/docs-md/filtering-search.md +79 -0
- package/agent/docs-md/follow-ups-starters.md +79 -0
- package/agent/docs-md/form-validation.md +95 -0
- package/agent/docs-md/generated-columns.md +65 -0
- package/agent/docs-md/generated-drafts-files.md +71 -0
- package/agent/docs-md/grouped-list.md +89 -0
- package/agent/docs-md/guide-cue.md +170 -0
- package/agent/docs-md/help-panel.md +103 -0
- package/agent/docs-md/help-system.md +74 -0
- package/agent/docs-md/iconography.md +49 -0
- package/agent/docs-md/inbox.md +108 -0
- package/agent/docs-md/inline-editing-validation.md +80 -0
- package/agent/docs-md/inline-input-in-answer.md +70 -0
- package/agent/docs-md/items-palette.md +116 -0
- package/agent/docs-md/keyboard-model.md +47 -0
- package/agent/docs-md/keyboard-navigation.md +68 -0
- package/agent/docs-md/keyboard-shortcut-hint.md +87 -0
- package/agent/docs-md/keyboard-shortcut-sheet.md +90 -0
- package/agent/docs-md/labelling-ai-output.md +66 -0
- package/agent/docs-md/layout.md +47 -0
- package/agent/docs-md/list-table.md +254 -0
- package/agent/docs-md/loading-skeleton.md +91 -0
- package/agent/docs-md/managing-agents.md +71 -0
- package/agent/docs-md/message-composer.md +105 -0
- package/agent/docs-md/migrating.md +49 -0
- package/agent/docs-md/motion.md +93 -0
- package/agent/docs-md/navigation.md +76 -0
- package/agent/docs-md/notification-channels.md +65 -0
- package/agent/docs-md/notifications-decision.md +100 -0
- package/agent/docs-md/onboarding-step.md +114 -0
- package/agent/docs-md/order-book.md +108 -0
- package/agent/docs-md/page-header.md +96 -0
- package/agent/docs-md/pagination.md +88 -0
- package/agent/docs-md/pane-layout.md +105 -0
- package/agent/docs-md/period-comparison.md +66 -0
- package/agent/docs-md/plan-card.md +120 -0
- package/agent/docs-md/popover-rules.md +88 -0
- package/agent/docs-md/prompt-editor.md +90 -0
- package/agent/docs-md/propose-apply.md +65 -0
- package/agent/docs-md/provider-logo.md +97 -0
- package/agent/docs-md/radio.md +83 -0
- package/agent/docs-md/ranked-list.md +102 -0
- package/agent/docs-md/reasoning-progress.md +72 -0
- package/agent/docs-md/record-detail.md +71 -0
- package/agent/docs-md/regenerating-answers.md +70 -0
- package/agent/docs-md/run-status-bar.md +94 -0
- package/agent/docs-md/saved-tasks-schedules.md +63 -0
- package/agent/docs-md/saved-views.md +149 -0
- package/agent/docs-md/saving-views.md +68 -0
- package/agent/docs-md/scheduler.md +258 -0
- package/agent/docs-md/secondary-panels.md +72 -0
- package/agent/docs-md/segmented-control.md +88 -0
- package/agent/docs-md/selection-bulk-actions.md +70 -0
- package/agent/docs-md/separator.md +75 -0
- package/agent/docs-md/setup-checklist.md +92 -0
- package/agent/docs-md/shape-elevation.md +55 -0
- package/agent/docs-md/sheet-grid.md +211 -0
- package/agent/docs-md/side-panel.md +134 -0
- package/agent/docs-md/sign-in.md +114 -0
- package/agent/docs-md/spacing.md +55 -0
- package/agent/docs-md/split-button.md +100 -0
- package/agent/docs-md/stat-compare.md +82 -0
- package/agent/docs-md/static-dashboard.md +75 -0
- package/agent/docs-md/status-badge.md +140 -0
- package/agent/docs-md/step-card.md +75 -0
- package/agent/docs-md/stepper.md +92 -0
- package/agent/docs-md/suggested-changes.md +91 -0
- package/agent/docs-md/switch.md +78 -0
- package/agent/docs-md/table-settings.md +81 -0
- package/agent/docs-md/tabs.md +85 -0
- package/agent/docs-md/tag-input.md +108 -0
- package/agent/docs-md/tag.md +79 -0
- package/agent/docs-md/task-builder.md +87 -0
- package/agent/docs-md/team-instructions.md +72 -0
- package/agent/docs-md/templates-gallery.md +85 -0
- package/agent/docs-md/text-input.md +160 -0
- package/agent/docs-md/thinking-steps.md +89 -0
- package/agent/docs-md/timeline.md +189 -0
- package/agent/docs-md/timestamp.md +84 -0
- package/agent/docs-md/timestamps.md +70 -0
- package/agent/docs-md/toast.md +117 -0
- package/agent/docs-md/tooltip.md +90 -0
- package/agent/docs-md/typography.md +62 -0
- package/agent/docs-md/undo-destructive.md +66 -0
- package/agent/docs-md/unsaved-changes.md +71 -0
- package/agent/docs-md/usage-meter.md +80 -0
- package/agent/docs-md/user-feedback.md +71 -0
- package/agent/docs-md/voice-and-tone.md +60 -0
- package/agent/docs-md/why-choice.md +93 -0
- package/agent/docs-md/workspace-onboarding.md +74 -0
- package/agent/docs-md/writing.md +48 -0
- package/agent/llms-full.txt +18369 -0
- package/agent/llms.txt +198 -0
- package/agent/skills/twoux-audit/SKILL.md +49 -0
- package/agent/skills/twoux-build/SKILL.md +39 -0
- package/agent/skills/twoux-design/SKILL.md +54 -0
- package/agent/skills/twoux-write/SKILL.md +32 -0
- package/bin/check.mjs +123 -0
- package/bin/docs.mjs +24 -0
- package/bin/guard.mjs +27 -0
- package/bin/init.mjs +113 -0
- package/bin/knowledge.mjs +87 -0
- package/bin/mcp.mjs +116 -0
- package/bin/theme.mjs +168 -0
- package/bin/twoux.mjs +25 -0
- package/package.json +80 -3
- package/registry/jobs.json +1028 -0
- package/registry/palette.json +770 -0
- package/registry/rules.json +913 -0
- package/src/components/agent-review.tsx +364 -0
- package/src/components/ai-basics.tsx +212 -0
- package/src/components/ai-work.tsx +304 -0
- package/src/components/ai.tsx +227 -0
- package/src/components/alert-dialog.tsx +203 -0
- package/src/components/alert.tsx +86 -0
- package/src/components/avatar.tsx +114 -0
- package/src/components/badge.tsx +59 -0
- package/src/components/board.tsx +397 -0
- package/src/components/breadcrumb.tsx +114 -0
- package/src/components/button-group.tsx +90 -0
- package/src/components/button.tsx +109 -0
- package/src/components/calendar.tsx +247 -0
- package/src/components/card.tsx +102 -0
- package/src/components/chart-map.tsx +336 -0
- package/src/components/charts-more.tsx +1331 -0
- package/src/components/charts-radial.tsx +328 -0
- package/src/components/charts.tsx +390 -0
- package/src/components/checkbox.tsx +40 -0
- package/src/components/code.tsx +139 -0
- package/src/components/collapsible.tsx +37 -0
- package/src/components/combobox.tsx +339 -0
- package/src/components/command-extras.tsx +11 -0
- package/src/components/command.tsx +196 -0
- package/src/components/data-grid.tsx +213 -0
- package/src/components/data-table.tsx +334 -0
- package/src/components/data-views.tsx +740 -0
- package/src/components/date-picker.tsx +686 -0
- package/src/components/dense-table.tsx +207 -0
- package/src/components/dialog.tsx +162 -0
- package/src/components/dropdown-menu.tsx +264 -0
- package/src/components/empty.tsx +114 -0
- package/src/components/feedback-prompt.tsx +80 -0
- package/src/components/field.tsx +254 -0
- package/src/components/fields.tsx +112 -0
- package/src/components/flow.tsx +840 -0
- package/src/components/guide-cue.tsx +273 -0
- package/src/components/inbox.tsx +113 -0
- package/src/components/input-group.tsx +176 -0
- package/src/components/input.tsx +26 -0
- package/src/components/kbd.tsx +34 -0
- package/src/components/label.tsx +27 -0
- package/src/components/labels.tsx +176 -0
- package/src/components/motion.tsx +136 -0
- package/src/components/onboarding.tsx +454 -0
- package/src/components/order-book.tsx +185 -0
- package/src/components/pane-layout.tsx +417 -0
- package/src/components/popover.tsx +96 -0
- package/src/components/provider-logos.data.ts +49 -0
- package/src/components/radio-group.tsx +52 -0
- package/src/components/scheduler.tsx +997 -0
- package/src/components/segmented-control.tsx +28 -0
- package/src/components/select.tsx +195 -0
- package/src/components/separator.tsx +31 -0
- package/src/components/sheet.tsx +148 -0
- package/src/components/shell.tsx +426 -0
- package/src/components/shortcut-sheet.tsx +83 -0
- package/src/components/skeleton.tsx +18 -0
- package/src/components/sonner.tsx +107 -0
- package/src/components/spinner.tsx +13 -0
- package/src/components/split-button.tsx +58 -0
- package/src/components/stepper.tsx +78 -0
- package/src/components/switch.tsx +43 -0
- package/src/components/table.tsx +126 -0
- package/src/components/tabs.tsx +118 -0
- package/src/components/tag-input.tsx +253 -0
- package/src/components/textarea.tsx +21 -0
- package/src/components/theme.tsx +80 -0
- package/src/components/timeline.tsx +408 -0
- package/src/components/toggle-group.tsx +87 -0
- package/src/components/toggle.tsx +54 -0
- package/src/components/tooltip.tsx +65 -0
- package/src/components/workspace-actions.tsx +294 -0
- package/src/components/workspace-import.tsx +465 -0
- package/src/components/workspace.tsx +1884 -0
- package/src/index.ts +80 -0
- package/src/lib/active-indicator.tsx +53 -0
- package/src/lib/chart-kit.tsx +72 -0
- package/src/lib/cn.ts +6 -0
- package/src/lib/shortcuts.tsx +61 -0
- package/src/lib/workspace.tsx +10 -0
- package/src/shadcn/alert-dialog.tsx +196 -0
- package/src/shadcn/alert.tsx +65 -0
- package/src/shadcn/avatar.tsx +108 -0
- package/src/shadcn/badge.tsx +47 -0
- package/src/shadcn/breadcrumb.tsx +108 -0
- package/src/shadcn/button-group.tsx +83 -0
- package/src/shadcn/button.tsx +63 -0
- package/src/shadcn/calendar.tsx +220 -0
- package/src/shadcn/card.tsx +91 -0
- package/src/shadcn/checkbox.tsx +31 -0
- package/src/shadcn/collapsible.tsx +33 -0
- package/src/shadcn/command.tsx +184 -0
- package/src/shadcn/dialog.tsx +158 -0
- package/src/shadcn/dropdown-menu.tsx +256 -0
- package/src/shadcn/empty.tsx +103 -0
- package/src/shadcn/field.tsx +248 -0
- package/src/shadcn/input-group.tsx +170 -0
- package/src/shadcn/input.tsx +20 -0
- package/src/shadcn/kbd.tsx +28 -0
- package/src/shadcn/label.tsx +23 -0
- package/src/shadcn/popover.tsx +88 -0
- package/src/shadcn/radio-group.tsx +44 -0
- package/src/shadcn/select.tsx +189 -0
- package/src/shadcn/separator.tsx +27 -0
- package/src/shadcn/sheet.tsx +142 -0
- package/src/shadcn/skeleton.tsx +13 -0
- package/src/shadcn/sonner.tsx +40 -0
- package/src/shadcn/switch.tsx +34 -0
- package/src/shadcn/table.tsx +115 -0
- package/src/shadcn/tabs.tsx +90 -0
- package/src/shadcn/textarea.tsx +17 -0
- package/src/shadcn/toggle-group.tsx +83 -0
- package/src/shadcn/toggle.tsx +46 -0
- package/src/shadcn/tooltip.tsx +56 -0
- package/src/styles/palette.css +127 -0
- package/src/styles/theme.css +35 -0
- package/src/styles/twoux.css +236 -0
- package/theme.json +14 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export { cn } from "./lib/cn";
|
|
2
|
+
export { ShortcutsProvider, useShortcut, useShortcutsEnabled } from "./lib/shortcuts";
|
|
3
|
+
// Generated from shadcn/ui (scripts/tailor-shadcn.mjs). See SHADCN-DIFF.md for every TwoUX change.
|
|
4
|
+
export * from "./components/alert";
|
|
5
|
+
export * from "./components/alert-dialog";
|
|
6
|
+
export * from "./components/avatar";
|
|
7
|
+
export * from "./components/badge";
|
|
8
|
+
export * from "./components/breadcrumb";
|
|
9
|
+
export * from "./components/button";
|
|
10
|
+
export * from "./components/button-group";
|
|
11
|
+
export * from "./components/card";
|
|
12
|
+
export * from "./components/checkbox";
|
|
13
|
+
export * from "./components/collapsible";
|
|
14
|
+
export * from "./components/command";
|
|
15
|
+
export * from "./components/dialog";
|
|
16
|
+
export * from "./components/dropdown-menu";
|
|
17
|
+
export * from "./components/empty";
|
|
18
|
+
export * from "./components/field";
|
|
19
|
+
export * from "./components/input";
|
|
20
|
+
export * from "./components/input-group";
|
|
21
|
+
export * from "./components/kbd";
|
|
22
|
+
export * from "./components/label";
|
|
23
|
+
export * from "./components/popover";
|
|
24
|
+
export * from "./components/select";
|
|
25
|
+
export * from "./components/radio-group";
|
|
26
|
+
export * from "./components/separator";
|
|
27
|
+
export * from "./components/sheet";
|
|
28
|
+
export * from "./components/skeleton";
|
|
29
|
+
export * from "./components/sonner";
|
|
30
|
+
export { toast } from "sonner";
|
|
31
|
+
export * from "./components/switch";
|
|
32
|
+
export * from "./components/table";
|
|
33
|
+
export * from "./components/tabs";
|
|
34
|
+
export * from "./components/textarea";
|
|
35
|
+
export * from "./components/toggle";
|
|
36
|
+
export * from "./components/toggle-group";
|
|
37
|
+
export * from "./components/tooltip";
|
|
38
|
+
// TwoUX compositions built on the above
|
|
39
|
+
export * from "./components/ai";
|
|
40
|
+
export * from "./components/ai-basics";
|
|
41
|
+
export * from "./components/ai-work";
|
|
42
|
+
export * from "./components/board";
|
|
43
|
+
export * from "./components/charts";
|
|
44
|
+
export * from "./components/code";
|
|
45
|
+
export * from "./components/command-extras";
|
|
46
|
+
export * from "./components/data-grid";
|
|
47
|
+
export * from "./components/data-table";
|
|
48
|
+
export * from "./components/dense-table";
|
|
49
|
+
export * from "./components/data-views";
|
|
50
|
+
export * from "./components/feedback-prompt";
|
|
51
|
+
export * from "./components/fields";
|
|
52
|
+
export * from "./components/inbox";
|
|
53
|
+
export * from "./components/labels";
|
|
54
|
+
export * from "./components/segmented-control";
|
|
55
|
+
export * from "./components/split-button";
|
|
56
|
+
export * from "./components/shell";
|
|
57
|
+
export * from "./components/shortcut-sheet";
|
|
58
|
+
export * from "./components/stepper";
|
|
59
|
+
export * from "./components/spinner";
|
|
60
|
+
export * from "./components/theme";
|
|
61
|
+
export * from "./components/motion";
|
|
62
|
+
export * from "./components/agent-review";
|
|
63
|
+
export * from "./components/onboarding";
|
|
64
|
+
export * from "./components/charts-more";
|
|
65
|
+
export * from "./components/workspace";
|
|
66
|
+
export * from "./components/workspace-import";
|
|
67
|
+
export * from "./components/workspace-actions";
|
|
68
|
+
export * from "./components/timeline";
|
|
69
|
+
export * from "./components/scheduler";
|
|
70
|
+
export * from "./components/charts-radial";
|
|
71
|
+
export * from "./components/chart-map";
|
|
72
|
+
export * from "./components/guide-cue";
|
|
73
|
+
export * from "./components/calendar";
|
|
74
|
+
export * from "./components/date-picker";
|
|
75
|
+
export * from "./components/combobox";
|
|
76
|
+
export * from "./components/tag-input";
|
|
77
|
+
export * from "./components/order-book";
|
|
78
|
+
export * from "./components/pane-layout";
|
|
79
|
+
export * from "./components/flow";
|
|
80
|
+
export { useWorkspace } from "./lib/workspace";
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TwoUX: one indicator that slides to the active item (tabs underline, segment pill),
|
|
3
|
+
* instead of each item painting its own and the highlight jumping.
|
|
4
|
+
* Place it as the first child of a `relative` list. It follows the child matching `selector`.
|
|
5
|
+
*/
|
|
6
|
+
import * as React from "react"
|
|
7
|
+
import { cn } from "./cn"
|
|
8
|
+
|
|
9
|
+
type Box = { left: number; top?: number; width: number; height?: number }
|
|
10
|
+
|
|
11
|
+
export function ActiveIndicator({ selector, fit = "box", className }: { selector: string; fit?: "x" | "box"; className?: string }) {
|
|
12
|
+
const ref = React.useRef<HTMLSpanElement>(null)
|
|
13
|
+
const [box, setBox] = React.useState<Box | null>(null)
|
|
14
|
+
const [animate, setAnimate] = React.useState(false)
|
|
15
|
+
|
|
16
|
+
React.useLayoutEffect(() => {
|
|
17
|
+
const list = ref.current?.parentElement
|
|
18
|
+
if (!list) return
|
|
19
|
+
const measure = () => {
|
|
20
|
+
const el = list.querySelector<HTMLElement>(`:scope > ${selector}`)
|
|
21
|
+
if (!el || !el.offsetWidth) return setBox(null)
|
|
22
|
+
setBox(fit === "x" ? { left: el.offsetLeft, width: el.offsetWidth } : { left: el.offsetLeft, top: el.offsetTop, width: el.offsetWidth, height: el.offsetHeight })
|
|
23
|
+
}
|
|
24
|
+
measure()
|
|
25
|
+
const mo = new MutationObserver(measure)
|
|
26
|
+
mo.observe(list, { subtree: true, childList: true, attributes: true, attributeFilter: ["data-state"] })
|
|
27
|
+
const ro = new ResizeObserver(measure)
|
|
28
|
+
ro.observe(list)
|
|
29
|
+
for (const c of Array.from(list.children)) ro.observe(c)
|
|
30
|
+
// First position is set without motion; only later changes slide.
|
|
31
|
+
const raf = requestAnimationFrame(() => setAnimate(true))
|
|
32
|
+
return () => {
|
|
33
|
+
mo.disconnect()
|
|
34
|
+
ro.disconnect()
|
|
35
|
+
cancelAnimationFrame(raf)
|
|
36
|
+
}
|
|
37
|
+
}, [selector, fit])
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<span
|
|
41
|
+
ref={ref}
|
|
42
|
+
aria-hidden="true"
|
|
43
|
+
data-slot="active-indicator"
|
|
44
|
+
className={cn(
|
|
45
|
+
"pointer-events-none absolute",
|
|
46
|
+
animate && "transition-[left,top,width,height] duration-(--duration-base) ease-(--ease-out)",
|
|
47
|
+
!box && "invisible",
|
|
48
|
+
className
|
|
49
|
+
)}
|
|
50
|
+
style={box ?? undefined}
|
|
51
|
+
/>
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared chart internals for TwoUX's own-SVG charts (charts-more, charts-radial, chart-map, timeline).
|
|
3
|
+
* Not exported from the package index: components use these, apps never do.
|
|
4
|
+
*/
|
|
5
|
+
import * as React from "react"
|
|
6
|
+
|
|
7
|
+
export const fmt = (n: number) => n.toLocaleString("en-GB")
|
|
8
|
+
export const MINUS = "−"
|
|
9
|
+
export const pct = (part: number, whole: number, digits = 1) => `${whole ? ((part / whole) * 100).toFixed(digits) : "0"}%`
|
|
10
|
+
/** Neutral mark for totals and Other: never a series or status colour. */
|
|
11
|
+
export const NEUTRAL = "var(--color-ink-3)"
|
|
12
|
+
/** Rough width of 12px text, to decide whether a label fits before drawing it. */
|
|
13
|
+
export const textW = (s: string) => s.length * 6.6
|
|
14
|
+
export const svgCls = "block overflow-visible rounded-xs focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
15
|
+
export const halo = "stroke-surface [paint-order:stroke]"
|
|
16
|
+
|
|
17
|
+
export function useWidth<T extends HTMLElement>() {
|
|
18
|
+
const ref = React.useRef<T>(null)
|
|
19
|
+
const [w, setW] = React.useState(0)
|
|
20
|
+
React.useLayoutEffect(() => {
|
|
21
|
+
const el = ref.current
|
|
22
|
+
if (!el) return
|
|
23
|
+
const ro = new ResizeObserver(([e]) => setW(Math.round(e.contentRect.width)))
|
|
24
|
+
ro.observe(el)
|
|
25
|
+
setW(el.clientWidth)
|
|
26
|
+
return () => ro.disconnect()
|
|
27
|
+
}, [])
|
|
28
|
+
return [ref, w] as const
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One active mark, moved with the arrow keys, Home and End; Esc clears. */
|
|
32
|
+
export function useActive(n: number) {
|
|
33
|
+
const [active, setActive] = React.useState<number | null>(null)
|
|
34
|
+
const onKey = (e: React.KeyboardEvent) => {
|
|
35
|
+
if (!n) return
|
|
36
|
+
if (e.key === "ArrowRight" || e.key === "ArrowDown") { e.preventDefault(); setActive((a) => (a === null ? 0 : Math.min(n - 1, a + 1))) }
|
|
37
|
+
else if (e.key === "ArrowLeft" || e.key === "ArrowUp") { e.preventDefault(); setActive((a) => (a === null ? n - 1 : Math.max(0, a - 1))) }
|
|
38
|
+
else if (e.key === "Home") { e.preventDefault(); setActive(0) }
|
|
39
|
+
else if (e.key === "End") { e.preventDefault(); setActive(n - 1) }
|
|
40
|
+
else if (e.key === "Escape") setActive(null)
|
|
41
|
+
}
|
|
42
|
+
return { active, setActive, onKey }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function niceStep(raw: number) {
|
|
46
|
+
if (raw <= 0) return 1
|
|
47
|
+
const mag = 10 ** Math.floor(Math.log10(raw))
|
|
48
|
+
return [1, 2, 2.5, 5, 10].map((m) => m * mag).find((s) => s >= raw) ?? raw
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type TipRow = { label: string; value: string; color?: string }
|
|
52
|
+
/** Tooltip: value strong, label secondary, a line key for the series. Never the only way to a value. */
|
|
53
|
+
export function Tip({ x, y, width, title, rows }: { x: number; y: number; width: number; title: string; rows: TipRow[] }) {
|
|
54
|
+
const left = x + 12 + 184 > width ? Math.max(0, x - 12 - 184) : x + 12
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
role="status"
|
|
58
|
+
className="pointer-events-none absolute z-10 grid min-w-44 gap-1 rounded-md border border-line bg-surface px-2.5 py-2 text-sm shadow-pop left-(--x) top-(--y)"
|
|
59
|
+
style={{ "--x": `${left}px`, "--y": `${Math.max(0, y)}px` } as React.CSSProperties}
|
|
60
|
+
>
|
|
61
|
+
<span className="font-medium text-ink">{title}</span>
|
|
62
|
+
{rows.map((r) => (
|
|
63
|
+
<span key={r.label} className="flex items-center gap-2">
|
|
64
|
+
{r.color && <span aria-hidden="true" className="h-0.5 w-3 shrink-0 rounded-full bg-(--c)" style={{ "--c": r.color } as React.CSSProperties} />}
|
|
65
|
+
<span className="text-ink-2">{r.label}</span>
|
|
66
|
+
<span className="tabular ml-auto pl-3 font-medium text-ink">{r.value}</span>
|
|
67
|
+
</span>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
package/src/lib/cn.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TwoUX: keyboard shortcuts that match what the button shows.
|
|
3
|
+
* <ShortcutsProvider enabled={setting}> wraps the app once. Button's `shortcut` then both shows the key
|
|
4
|
+
* and presses the button. Single-key shortcuts obey `enabled`, so people can turn them off (WCAG 2.1.4);
|
|
5
|
+
* shortcuts with ⌘/Ctrl/⌥ always work.
|
|
6
|
+
* A shortcut fires only when its button is visible and enabled, focus is not in a text field,
|
|
7
|
+
* and no dialog is open (unless the button is inside that dialog).
|
|
8
|
+
*/
|
|
9
|
+
import * as React from "react"
|
|
10
|
+
|
|
11
|
+
type Ctx = { enabled: boolean }
|
|
12
|
+
const ShortcutsContext = React.createContext<Ctx | null>(null)
|
|
13
|
+
|
|
14
|
+
export function ShortcutsProvider({ enabled = true, children }: { enabled?: boolean; children: React.ReactNode }) {
|
|
15
|
+
const value = React.useMemo(() => ({ enabled }), [enabled])
|
|
16
|
+
return <ShortcutsContext.Provider value={value}>{children}</ShortcutsContext.Provider>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Parses "C", "⇧C", "⌘↵", "⌘K", "Esc" into a matcher. */
|
|
20
|
+
function parse(shortcut: string) {
|
|
21
|
+
const mods = { meta: false, shift: false, alt: false }
|
|
22
|
+
let key = shortcut
|
|
23
|
+
for (;;) {
|
|
24
|
+
if (key.startsWith("⌘")) { mods.meta = true; key = key.slice(1) }
|
|
25
|
+
else if (key.startsWith("⇧")) { mods.shift = true; key = key.slice(1) }
|
|
26
|
+
else if (key.startsWith("⌥")) { mods.alt = true; key = key.slice(1) }
|
|
27
|
+
else break
|
|
28
|
+
}
|
|
29
|
+
const named: Record<string, string> = { "↵": "Enter", Esc: "Escape", "⌫": "Backspace", "←": "ArrowLeft", "→": "ArrowRight", "↑": "ArrowUp", "↓": "ArrowDown" }
|
|
30
|
+
return { ...mods, key: (named[key] ?? key).toLowerCase(), single: !mods.meta && !mods.alt }
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const isTyping = (el: Element | null) => !!el?.closest("input:not([type=checkbox]):not([type=radio]), textarea, select, [contenteditable=true]")
|
|
34
|
+
|
|
35
|
+
/** Presses `ref` when `shortcut` is typed. Used by Button; usable for any element. */
|
|
36
|
+
/** Whether single-key shortcuts are on. Components with their own single-key keys (zoom, etc.) check this. */
|
|
37
|
+
export function useShortcutsEnabled() {
|
|
38
|
+
return React.useContext(ShortcutsContext)?.enabled ?? true
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function useShortcut(shortcut: string | undefined, ref: React.RefObject<HTMLElement | null>, disabled = false) {
|
|
42
|
+
const ctx = React.useContext(ShortcutsContext)
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (!shortcut || !ctx || disabled) return
|
|
45
|
+
const want = parse(shortcut)
|
|
46
|
+
if (want.single && !ctx.enabled) return
|
|
47
|
+
const onKey = (e: KeyboardEvent) => {
|
|
48
|
+
if (e.defaultPrevented || e.repeat) return
|
|
49
|
+
if (e.key.toLowerCase() !== want.key || (e.metaKey || e.ctrlKey) !== want.meta || e.shiftKey !== want.shift || e.altKey !== want.alt) return
|
|
50
|
+
if (want.single && isTyping(document.activeElement)) return
|
|
51
|
+
const el = ref.current
|
|
52
|
+
if (!el || !el.isConnected || el.offsetParent === null || el.closest("[inert]") || (el as HTMLButtonElement).disabled || el.getAttribute("aria-disabled") === "true") return
|
|
53
|
+
const dialog = document.querySelector("[role=dialog][data-state=open], [role=alertdialog][data-state=open]")
|
|
54
|
+
if (dialog && !dialog.contains(el)) return
|
|
55
|
+
e.preventDefault()
|
|
56
|
+
el.click()
|
|
57
|
+
}
|
|
58
|
+
document.addEventListener("keydown", onKey)
|
|
59
|
+
return () => document.removeEventListener("keydown", onKey)
|
|
60
|
+
}, [shortcut, ctx, disabled, ref])
|
|
61
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workspace density (see Content density): set by AppShell frame="flush" for tool pages that keep several regions in view.
|
|
5
|
+
* Components read it to drop their own chrome and gaps, never their sizes: ChartFrame goes flush, PageHeader tightens
|
|
6
|
+
* its padding. Text, controls, rows and targets stay the same.
|
|
7
|
+
*/
|
|
8
|
+
export const WorkspaceContext = React.createContext(false)
|
|
9
|
+
/** True inside AppShell frame="flush". */
|
|
10
|
+
export const useWorkspace = () => React.useContext(WorkspaceContext)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { AlertDialog as AlertDialogPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
import { Button } from "@/registry/new-york-v4/ui/button"
|
|
8
|
+
|
|
9
|
+
function AlertDialog({
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Root>) {
|
|
12
|
+
return <AlertDialogPrimitive.Root data-slot="alert-dialog" {...props} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function AlertDialogTrigger({
|
|
16
|
+
...props
|
|
17
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Trigger>) {
|
|
18
|
+
return (
|
|
19
|
+
<AlertDialogPrimitive.Trigger data-slot="alert-dialog-trigger" {...props} />
|
|
20
|
+
)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function AlertDialogPortal({
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Portal>) {
|
|
26
|
+
return (
|
|
27
|
+
<AlertDialogPrimitive.Portal data-slot="alert-dialog-portal" {...props} />
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function AlertDialogOverlay({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Overlay>) {
|
|
35
|
+
return (
|
|
36
|
+
<AlertDialogPrimitive.Overlay
|
|
37
|
+
data-slot="alert-dialog-overlay"
|
|
38
|
+
className={cn(
|
|
39
|
+
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
40
|
+
className
|
|
41
|
+
)}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function AlertDialogContent({
|
|
48
|
+
className,
|
|
49
|
+
size = "default",
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Content> & {
|
|
52
|
+
size?: "default" | "sm"
|
|
53
|
+
}) {
|
|
54
|
+
return (
|
|
55
|
+
<AlertDialogPortal>
|
|
56
|
+
<AlertDialogOverlay />
|
|
57
|
+
<AlertDialogPrimitive.Content
|
|
58
|
+
data-slot="alert-dialog-content"
|
|
59
|
+
data-size={size}
|
|
60
|
+
className={cn(
|
|
61
|
+
"group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-lg",
|
|
62
|
+
className
|
|
63
|
+
)}
|
|
64
|
+
{...props}
|
|
65
|
+
/>
|
|
66
|
+
</AlertDialogPortal>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function AlertDialogHeader({
|
|
71
|
+
className,
|
|
72
|
+
...props
|
|
73
|
+
}: React.ComponentProps<"div">) {
|
|
74
|
+
return (
|
|
75
|
+
<div
|
|
76
|
+
data-slot="alert-dialog-header"
|
|
77
|
+
className={cn(
|
|
78
|
+
"grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
|
|
79
|
+
className
|
|
80
|
+
)}
|
|
81
|
+
{...props}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function AlertDialogFooter({
|
|
87
|
+
className,
|
|
88
|
+
...props
|
|
89
|
+
}: React.ComponentProps<"div">) {
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
data-slot="alert-dialog-footer"
|
|
93
|
+
className={cn(
|
|
94
|
+
"flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
|
|
95
|
+
className
|
|
96
|
+
)}
|
|
97
|
+
{...props}
|
|
98
|
+
/>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function AlertDialogTitle({
|
|
103
|
+
className,
|
|
104
|
+
...props
|
|
105
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
106
|
+
return (
|
|
107
|
+
<AlertDialogPrimitive.Title
|
|
108
|
+
data-slot="alert-dialog-title"
|
|
109
|
+
className={cn(
|
|
110
|
+
"text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
|
|
111
|
+
className
|
|
112
|
+
)}
|
|
113
|
+
{...props}
|
|
114
|
+
/>
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function AlertDialogDescription({
|
|
119
|
+
className,
|
|
120
|
+
...props
|
|
121
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
122
|
+
return (
|
|
123
|
+
<AlertDialogPrimitive.Description
|
|
124
|
+
data-slot="alert-dialog-description"
|
|
125
|
+
className={cn("text-sm text-muted-foreground", className)}
|
|
126
|
+
{...props}
|
|
127
|
+
/>
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function AlertDialogMedia({
|
|
132
|
+
className,
|
|
133
|
+
...props
|
|
134
|
+
}: React.ComponentProps<"div">) {
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
data-slot="alert-dialog-media"
|
|
138
|
+
className={cn(
|
|
139
|
+
"mb-2 inline-flex size-16 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-8",
|
|
140
|
+
className
|
|
141
|
+
)}
|
|
142
|
+
{...props}
|
|
143
|
+
/>
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function AlertDialogAction({
|
|
148
|
+
className,
|
|
149
|
+
variant = "default",
|
|
150
|
+
size = "default",
|
|
151
|
+
...props
|
|
152
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Action> &
|
|
153
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
154
|
+
return (
|
|
155
|
+
<Button variant={variant} size={size} asChild>
|
|
156
|
+
<AlertDialogPrimitive.Action
|
|
157
|
+
data-slot="alert-dialog-action"
|
|
158
|
+
className={cn(className)}
|
|
159
|
+
{...props}
|
|
160
|
+
/>
|
|
161
|
+
</Button>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function AlertDialogCancel({
|
|
166
|
+
className,
|
|
167
|
+
variant = "outline",
|
|
168
|
+
size = "default",
|
|
169
|
+
...props
|
|
170
|
+
}: React.ComponentProps<typeof AlertDialogPrimitive.Cancel> &
|
|
171
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
172
|
+
return (
|
|
173
|
+
<Button variant={variant} size={size} asChild>
|
|
174
|
+
<AlertDialogPrimitive.Cancel
|
|
175
|
+
data-slot="alert-dialog-cancel"
|
|
176
|
+
className={cn(className)}
|
|
177
|
+
{...props}
|
|
178
|
+
/>
|
|
179
|
+
</Button>
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export {
|
|
184
|
+
AlertDialog,
|
|
185
|
+
AlertDialogAction,
|
|
186
|
+
AlertDialogCancel,
|
|
187
|
+
AlertDialogContent,
|
|
188
|
+
AlertDialogDescription,
|
|
189
|
+
AlertDialogFooter,
|
|
190
|
+
AlertDialogHeader,
|
|
191
|
+
AlertDialogMedia,
|
|
192
|
+
AlertDialogOverlay,
|
|
193
|
+
AlertDialogPortal,
|
|
194
|
+
AlertDialogTitle,
|
|
195
|
+
AlertDialogTrigger,
|
|
196
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cva, type VariantProps } from "class-variance-authority"
|
|
3
|
+
import { cn } from "cn"
|
|
4
|
+
|
|
5
|
+
const alertVariants = cva(
|
|
6
|
+
"relative grid w-full grid-cols-[0_1fr] items-start gap-y-0.5 rounded-lg border px-4 py-3 text-sm has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] has-[>svg]:gap-x-3 [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current",
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: "bg-card text-card-foreground",
|
|
11
|
+
destructive:
|
|
12
|
+
"bg-card text-destructive *:data-[slot=alert-description]:text-destructive/90 [&>svg]:text-current",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultVariants: {
|
|
16
|
+
variant: "default",
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
function Alert({
|
|
22
|
+
className,
|
|
23
|
+
variant,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof alertVariants>) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
data-slot="alert"
|
|
29
|
+
role="alert"
|
|
30
|
+
className={cn(alertVariants({ variant }), className)}
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function AlertTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
37
|
+
return (
|
|
38
|
+
<div
|
|
39
|
+
data-slot="alert-title"
|
|
40
|
+
className={cn(
|
|
41
|
+
"col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight",
|
|
42
|
+
className
|
|
43
|
+
)}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function AlertDescription({
|
|
50
|
+
className,
|
|
51
|
+
...props
|
|
52
|
+
}: React.ComponentProps<"div">) {
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
data-slot="alert-description"
|
|
56
|
+
className={cn(
|
|
57
|
+
"col-start-2 grid justify-items-start gap-1 text-sm text-muted-foreground [&_p]:leading-relaxed",
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
{...props}
|
|
61
|
+
/>
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { Alert, AlertTitle, AlertDescription }
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "cn"
|
|
5
|
+
import { Avatar as AvatarPrimitive } from "radix-ui"
|
|
6
|
+
|
|
7
|
+
function Avatar({
|
|
8
|
+
className,
|
|
9
|
+
size = "default",
|
|
10
|
+
...props
|
|
11
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Root> & {
|
|
12
|
+
size?: "default" | "sm" | "lg"
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<AvatarPrimitive.Root
|
|
16
|
+
data-slot="avatar"
|
|
17
|
+
data-size={size}
|
|
18
|
+
className={cn(
|
|
19
|
+
"group/avatar relative flex size-8 shrink-0 overflow-hidden rounded-full select-none data-[size=lg]:size-10 data-[size=sm]:size-6",
|
|
20
|
+
className
|
|
21
|
+
)}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function AvatarImage({
|
|
28
|
+
className,
|
|
29
|
+
...props
|
|
30
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
|
31
|
+
return (
|
|
32
|
+
<AvatarPrimitive.Image
|
|
33
|
+
data-slot="avatar-image"
|
|
34
|
+
className={cn("aspect-square size-full", className)}
|
|
35
|
+
{...props}
|
|
36
|
+
/>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function AvatarFallback({
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
|
44
|
+
return (
|
|
45
|
+
<AvatarPrimitive.Fallback
|
|
46
|
+
data-slot="avatar-fallback"
|
|
47
|
+
className={cn(
|
|
48
|
+
"flex size-full items-center justify-center rounded-full bg-muted text-sm text-muted-foreground group-data-[size=sm]/avatar:text-xs",
|
|
49
|
+
className
|
|
50
|
+
)}
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
|
57
|
+
return (
|
|
58
|
+
<span
|
|
59
|
+
data-slot="avatar-badge"
|
|
60
|
+
className={cn(
|
|
61
|
+
"absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full bg-primary text-primary-foreground ring-2 ring-background select-none",
|
|
62
|
+
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
|
63
|
+
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
|
64
|
+
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function AvatarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="avatar-group"
|
|
76
|
+
className={cn(
|
|
77
|
+
"group/avatar-group flex -space-x-2 *:data-[slot=avatar]:ring-2 *:data-[slot=avatar]:ring-background",
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function AvatarGroupCount({
|
|
86
|
+
className,
|
|
87
|
+
...props
|
|
88
|
+
}: React.ComponentProps<"div">) {
|
|
89
|
+
return (
|
|
90
|
+
<div
|
|
91
|
+
data-slot="avatar-group-count"
|
|
92
|
+
className={cn(
|
|
93
|
+
"relative flex size-8 shrink-0 items-center justify-center rounded-full bg-muted text-sm text-muted-foreground ring-2 ring-background group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3",
|
|
94
|
+
className
|
|
95
|
+
)}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
Avatar,
|
|
103
|
+
AvatarImage,
|
|
104
|
+
AvatarFallback,
|
|
105
|
+
AvatarBadge,
|
|
106
|
+
AvatarGroup,
|
|
107
|
+
AvatarGroupCount,
|
|
108
|
+
}
|