@adres/mgda-ui 0.0.0-stage → 0.0.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/brand-knowledge.json +451 -0
- package/knowledge/accordion.md +135 -0
- package/knowledge/activity-rings.md +123 -0
- package/knowledge/alert.md +184 -0
- package/knowledge/answer-actions.md +217 -0
- package/knowledge/aspect-ratio.md +82 -0
- package/knowledge/avatar.md +158 -0
- package/knowledge/badge.md +172 -0
- package/knowledge/bar-graph.md +167 -0
- package/knowledge/bento-grid.md +115 -0
- package/knowledge/blockquote.md +153 -0
- package/knowledge/box-list.md +177 -0
- package/knowledge/bullet-list.md +128 -0
- package/knowledge/button-group.md +179 -0
- package/knowledge/button.md +231 -0
- package/knowledge/calendar.md +168 -0
- package/knowledge/card.md +321 -0
- package/knowledge/cards-nav.md +170 -0
- package/knowledge/chain-of-thought.md +164 -0
- package/knowledge/chat-container.md +135 -0
- package/knowledge/chat-message.md +224 -0
- package/knowledge/checkbox-card.md +172 -0
- package/knowledge/checkbox.md +111 -0
- package/knowledge/citations.md +241 -0
- package/knowledge/combobox.md +265 -0
- package/knowledge/countdown-pill.md +153 -0
- package/knowledge/cta.md +118 -0
- package/knowledge/data-list.md +173 -0
- package/knowledge/data-table.md +220 -0
- package/knowledge/dialog.md +249 -0
- package/knowledge/drawer.md +236 -0
- package/knowledge/dropdown-menu.md +238 -0
- package/knowledge/dropzone.md +153 -0
- package/knowledge/email-chips-input.md +161 -0
- package/knowledge/empty-state.md +106 -0
- package/knowledge/entity-header.md +166 -0
- package/knowledge/feature-grid.md +120 -0
- package/knowledge/feedback-bar.md +127 -0
- package/knowledge/footer.md +116 -0
- package/knowledge/heading.md +189 -0
- package/knowledge/hero.md +361 -0
- package/knowledge/input-button.md +187 -0
- package/knowledge/input-group.md +185 -0
- package/knowledge/input.md +256 -0
- package/knowledge/inset.md +177 -0
- package/knowledge/kpi-card.md +206 -0
- package/knowledge/list-nav.md +181 -0
- package/knowledge/logo-cloud.md +102 -0
- package/knowledge/media-gallery.md +158 -0
- package/knowledge/message-actions.md +175 -0
- package/knowledge/nav-sidebar.md +225 -0
- package/knowledge/number-stepper.md +159 -0
- package/knowledge/page-header.md +109 -0
- package/knowledge/pagination.md +225 -0
- package/knowledge/popover.md +159 -0
- package/knowledge/pricing.md +132 -0
- package/knowledge/progress.md +156 -0
- package/knowledge/prompt-input.md +337 -0
- package/knowledge/prompt-suggestions.md +168 -0
- package/knowledge/quota-bar.md +160 -0
- package/knowledge/quote.md +116 -0
- package/knowledge/radio-card.md +172 -0
- package/knowledge/radio-group.md +135 -0
- package/knowledge/reasoning.md +142 -0
- package/knowledge/search.md +131 -0
- package/knowledge/section-nav.md +162 -0
- package/knowledge/segmented-control.md +181 -0
- package/knowledge/select-button.md +205 -0
- package/knowledge/select.md +210 -0
- package/knowledge/separator.md +75 -0
- package/knowledge/shimmer-text.md +140 -0
- package/knowledge/sidebar.md +227 -0
- package/knowledge/skeleton.md +142 -0
- package/knowledge/slider.md +133 -0
- package/knowledge/slideshow.md +159 -0
- package/knowledge/source.md +156 -0
- package/knowledge/spinner.md +120 -0
- package/knowledge/stat-strip.md +128 -0
- package/knowledge/stats.md +117 -0
- package/knowledge/step-indicator.md +157 -0
- package/knowledge/switch.md +88 -0
- package/knowledge/table.md +190 -0
- package/knowledge/tabs.md +169 -0
- package/knowledge/testimonials.md +115 -0
- package/knowledge/text-link.md +192 -0
- package/knowledge/text.md +194 -0
- package/knowledge/thinking-bar.md +115 -0
- package/knowledge/thinking-steps.md +140 -0
- package/knowledge/toaster.md +127 -0
- package/knowledge/toggle.md +113 -0
- package/knowledge/tool-call-card.md +241 -0
- package/knowledge/tooltip.md +120 -0
- package/knowledge/top-nav.md +138 -0
- package/knowledge.json +19854 -0
- package/package.json +94 -4
- package/src/components/accordion.stories.tsx +46 -0
- package/src/components/accordion.tsx +74 -0
- package/src/components/activity-rings.stories.tsx +73 -0
- package/src/components/activity-rings.tsx +121 -0
- package/src/components/alert.stories.tsx +43 -0
- package/src/components/alert.tsx +132 -0
- package/src/components/answer-actions.stories.tsx +54 -0
- package/src/components/answer-actions.test.tsx +37 -0
- package/src/components/answer-actions.tsx +313 -0
- package/src/components/aspect-ratio.stories.tsx +32 -0
- package/src/components/aspect-ratio.tsx +5 -0
- package/src/components/avatar.stories.tsx +40 -0
- package/src/components/avatar.tsx +154 -0
- package/src/components/badge.stories.tsx +27 -0
- package/src/components/badge.tsx +63 -0
- package/src/components/bar-graph.stories.tsx +28 -0
- package/src/components/bar-graph.tsx +204 -0
- package/src/components/bento-grid.stories.tsx +23 -0
- package/src/components/bento-grid.tsx +67 -0
- package/src/components/blockquote.stories.tsx +28 -0
- package/src/components/blockquote.tsx +88 -0
- package/src/components/box-list.stories.tsx +25 -0
- package/src/components/box-list.tsx +108 -0
- package/src/components/bullet-list.stories.tsx +22 -0
- package/src/components/bullet-list.tsx +101 -0
- package/src/components/button-group.stories.tsx +31 -0
- package/src/components/button-group.tsx +156 -0
- package/src/components/button.stories.tsx +66 -0
- package/src/components/button.test.ts +17 -0
- package/src/components/button.tsx +139 -0
- package/src/components/calendar.stories.tsx +25 -0
- package/src/components/calendar.tsx +367 -0
- package/src/components/card.stories.tsx +44 -0
- package/src/components/card.tsx +392 -0
- package/src/components/cards-nav.stories.tsx +35 -0
- package/src/components/cards-nav.tsx +115 -0
- package/src/components/chain-of-thought.stories.tsx +67 -0
- package/src/components/chain-of-thought.test.tsx +50 -0
- package/src/components/chain-of-thought.tsx +170 -0
- package/src/components/chat-container.stories.tsx +47 -0
- package/src/components/chat-container.test.tsx +49 -0
- package/src/components/chat-container.tsx +222 -0
- package/src/components/chat-message.stories.tsx +52 -0
- package/src/components/chat-message.test.tsx +36 -0
- package/src/components/chat-message.tsx +163 -0
- package/src/components/checkbox-card.stories.tsx +27 -0
- package/src/components/checkbox-card.tsx +200 -0
- package/src/components/checkbox.stories.tsx +26 -0
- package/src/components/checkbox.tsx +61 -0
- package/src/components/citations.stories.tsx +64 -0
- package/src/components/citations.test.tsx +61 -0
- package/src/components/citations.tsx +220 -0
- package/src/components/combobox.stories.tsx +184 -0
- package/src/components/combobox.test.tsx +82 -0
- package/src/components/combobox.tsx +407 -0
- package/src/components/countdown-pill.stories.tsx +24 -0
- package/src/components/countdown-pill.tsx +129 -0
- package/src/components/cta.stories.tsx +29 -0
- package/src/components/cta.tsx +52 -0
- package/src/components/data-list.stories.tsx +50 -0
- package/src/components/data-list.test.tsx +35 -0
- package/src/components/data-list.tsx +181 -0
- package/src/components/data-table.stories.tsx +29 -0
- package/src/components/data-table.tsx +200 -0
- package/src/components/dialog.stories.tsx +42 -0
- package/src/components/dialog.tsx +200 -0
- package/src/components/drawer.stories.tsx +48 -0
- package/src/components/drawer.tsx +385 -0
- package/src/components/dropdown-menu.stories.tsx +41 -0
- package/src/components/dropdown-menu.tsx +252 -0
- package/src/components/dropzone.stories.tsx +19 -0
- package/src/components/dropzone.tsx +250 -0
- package/src/components/email-chips-input.stories.tsx +135 -0
- package/src/components/email-chips-input.tsx +210 -0
- package/src/components/empty-state.stories.tsx +22 -0
- package/src/components/empty-state.tsx +35 -0
- package/src/components/entity-header.stories.tsx +43 -0
- package/src/components/entity-header.test.tsx +35 -0
- package/src/components/entity-header.tsx +92 -0
- package/src/components/feature-grid.stories.tsx +25 -0
- package/src/components/feature-grid.tsx +87 -0
- package/src/components/feedback-bar.stories.tsx +25 -0
- package/src/components/feedback-bar.test.tsx +24 -0
- package/src/components/feedback-bar.tsx +62 -0
- package/src/components/footer.stories.tsx +23 -0
- package/src/components/footer.tsx +78 -0
- package/src/components/heading.stories.tsx +45 -0
- package/src/components/heading.test.tsx +22 -0
- package/src/components/heading.tsx +77 -0
- package/src/components/hero.stories.tsx +38 -0
- package/src/components/hero.tsx +498 -0
- package/src/components/input-button.stories.tsx +19 -0
- package/src/components/input-button.tsx +112 -0
- package/src/components/input-group.stories.tsx +38 -0
- package/src/components/input-group.tsx +157 -0
- package/src/components/input.stories.tsx +31 -0
- package/src/components/input.tsx +284 -0
- package/src/components/inset.stories.tsx +29 -0
- package/src/components/inset.tsx +70 -0
- package/src/components/kpi-card.stories.tsx +29 -0
- package/src/components/kpi-card.tsx +284 -0
- package/src/components/list-nav.stories.tsx +22 -0
- package/src/components/list-nav.tsx +145 -0
- package/src/components/logo-cloud.stories.tsx +23 -0
- package/src/components/logo-cloud.tsx +42 -0
- package/src/components/media-gallery.stories.tsx +25 -0
- package/src/components/media-gallery.tsx +347 -0
- package/src/components/message-actions.stories.tsx +45 -0
- package/src/components/message-actions.test.tsx +32 -0
- package/src/components/message-actions.tsx +119 -0
- package/src/components/nav-sidebar.stories.tsx +58 -0
- package/src/components/nav-sidebar.test.tsx +54 -0
- package/src/components/nav-sidebar.tsx +462 -0
- package/src/components/number-stepper.stories.tsx +19 -0
- package/src/components/number-stepper.tsx +158 -0
- package/src/components/page-header.stories.tsx +25 -0
- package/src/components/page-header.tsx +64 -0
- package/src/components/pagination.stories.tsx +110 -0
- package/src/components/pagination.test.tsx +46 -0
- package/src/components/pagination.tsx +144 -0
- package/src/components/popover.stories.tsx +26 -0
- package/src/components/popover.tsx +62 -0
- package/src/components/pricing.stories.tsx +42 -0
- package/src/components/pricing.tsx +130 -0
- package/src/components/progress.stories.tsx +22 -0
- package/src/components/progress.tsx +189 -0
- package/src/components/prompt-input.stories.tsx +62 -0
- package/src/components/prompt-input.test.tsx +106 -0
- package/src/components/prompt-input.tsx +343 -0
- package/src/components/prompt-suggestions.stories.tsx +53 -0
- package/src/components/prompt-suggestions.test.tsx +37 -0
- package/src/components/prompt-suggestions.tsx +122 -0
- package/src/components/quota-bar.stories.tsx +19 -0
- package/src/components/quota-bar.tsx +191 -0
- package/src/components/quote.stories.tsx +18 -0
- package/src/components/quote.tsx +43 -0
- package/src/components/radio-card.stories.tsx +27 -0
- package/src/components/radio-card.tsx +225 -0
- package/src/components/radio-group.stories.tsx +32 -0
- package/src/components/radio-group.tsx +68 -0
- package/src/components/reasoning.stories.tsx +50 -0
- package/src/components/reasoning.test.tsx +48 -0
- package/src/components/reasoning.tsx +143 -0
- package/src/components/search.stories.tsx +18 -0
- package/src/components/search.tsx +133 -0
- package/src/components/section-nav.stories.tsx +59 -0
- package/src/components/section-nav.test.tsx +36 -0
- package/src/components/section-nav.tsx +146 -0
- package/src/components/segmented-control.stories.tsx +22 -0
- package/src/components/segmented-control.tsx +301 -0
- package/src/components/select-button.stories.tsx +27 -0
- package/src/components/select-button.tsx +174 -0
- package/src/components/select.stories.tsx +27 -0
- package/src/components/select.tsx +198 -0
- package/src/components/separator.stories.tsx +22 -0
- package/src/components/separator.tsx +23 -0
- package/src/components/shimmer-text.stories.tsx +23 -0
- package/src/components/shimmer-text.test.tsx +35 -0
- package/src/components/shimmer-text.tsx +43 -0
- package/src/components/sidebar.stories.tsx +25 -0
- package/src/components/sidebar.tsx +234 -0
- package/src/components/skeleton.stories.tsx +22 -0
- package/src/components/skeleton.tsx +96 -0
- package/src/components/slider.stories.tsx +20 -0
- package/src/components/slider.tsx +176 -0
- package/src/components/slideshow.stories.tsx +30 -0
- package/src/components/slideshow.tsx +291 -0
- package/src/components/source.stories.tsx +32 -0
- package/src/components/source.test.tsx +40 -0
- package/src/components/source.tsx +161 -0
- package/src/components/spinner.stories.tsx +19 -0
- package/src/components/spinner.tsx +76 -0
- package/src/components/stat-strip.stories.tsx +36 -0
- package/src/components/stat-strip.test.tsx +25 -0
- package/src/components/stat-strip.tsx +85 -0
- package/src/components/stats.stories.tsx +24 -0
- package/src/components/stats.tsx +83 -0
- package/src/components/step-indicator.stories.tsx +18 -0
- package/src/components/step-indicator.test.tsx +18 -0
- package/src/components/step-indicator.tsx +286 -0
- package/src/components/switch.stories.tsx +26 -0
- package/src/components/switch.tsx +32 -0
- package/src/components/table.stories.tsx +38 -0
- package/src/components/table.tsx +220 -0
- package/src/components/tabs.stories.tsx +25 -0
- package/src/components/tabs.tsx +276 -0
- package/src/components/testimonials.stories.tsx +22 -0
- package/src/components/testimonials.tsx +109 -0
- package/src/components/text-link.stories.tsx +47 -0
- package/src/components/text-link.test.tsx +18 -0
- package/src/components/text-link.tsx +88 -0
- package/src/components/text.stories.tsx +49 -0
- package/src/components/text.tsx +105 -0
- package/src/components/thinking-bar.stories.tsx +19 -0
- package/src/components/thinking-bar.test.tsx +27 -0
- package/src/components/thinking-bar.tsx +64 -0
- package/src/components/thinking-steps.stories.tsx +44 -0
- package/src/components/thinking-steps.test.tsx +42 -0
- package/src/components/thinking-steps.tsx +92 -0
- package/src/components/toaster.stories.tsx +80 -0
- package/src/components/toaster.tsx +36 -0
- package/src/components/toggle.stories.tsx +21 -0
- package/src/components/toggle.tsx +103 -0
- package/src/components/tool-call-card.stories.tsx +97 -0
- package/src/components/tool-call-card.test.tsx +88 -0
- package/src/components/tool-call-card.tsx +231 -0
- package/src/components/tooltip.stories.tsx +25 -0
- package/src/components/tooltip.tsx +43 -0
- package/src/components/top-nav.stories.tsx +20 -0
- package/src/components/top-nav.tsx +76 -0
- package/src/docs/Colors.stories.tsx +192 -0
- package/src/docs/GettingStarted.mdx +72 -0
- package/src/docs/Mcp.mdx +52 -0
- package/src/docs/Typography.stories.tsx +114 -0
- package/src/hooks/use-in-view.ts +32 -0
- package/src/hooks/use-reduced-motion.ts +26 -0
- package/src/index.ts +343 -0
- package/src/lib/cn.test.ts +67 -0
- package/src/lib/cn.ts +81 -0
- package/src/lib/motion.ts +109 -0
- package/src/lib/prefix-utilities.test.ts +56 -0
- package/src/lib/prefix-utilities.ts +50 -0
- package/src/styles/base.css +489 -0
- package/src/styles/motion.css +69 -0
- package/src/styles/overrides.css +44 -0
- package/src/styles/typography.css +173 -0
- package/src/theme/theme.css +391 -0
- package/src/theme/tokens.ts +29 -0
- package/src/theme/types.ts +26 -0
- package/README.md +0 -3
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
|
5
|
+
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
export const Select = SelectPrimitive.Root
|
|
9
|
+
export const SelectGroup = SelectPrimitive.Group
|
|
10
|
+
export const SelectValue = SelectPrimitive.Value
|
|
11
|
+
|
|
12
|
+
// Sizes mirror Button so a Select sits at the same height + interior padding
|
|
13
|
+
// as a Button at the matching size.
|
|
14
|
+
export type SelectTriggerSize = "sm" | "md" | "lg"
|
|
15
|
+
|
|
16
|
+
const TRIGGER_HEIGHT: Record<SelectTriggerSize, string> = {
|
|
17
|
+
sm: "ax:h-2xl", // 32px
|
|
18
|
+
md: "ax:h-3xl", // 40px
|
|
19
|
+
lg: "ax:h-4xl", // 48px
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const TRIGGER_PADDING_X: Record<SelectTriggerSize, string> = {
|
|
23
|
+
sm: "ax:px-sm",
|
|
24
|
+
md: "ax:px-md",
|
|
25
|
+
lg: "ax:px-xl",
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const TRIGGER_TEXT: Record<SelectTriggerSize, string> = {
|
|
29
|
+
sm: "ax:t-label-sm",
|
|
30
|
+
md: "ax:t-label-sm",
|
|
31
|
+
lg: "ax:text-base",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface SelectTriggerProps
|
|
35
|
+
extends React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> {
|
|
36
|
+
size?: SelectTriggerSize
|
|
37
|
+
/** Drop the border - for inline / sidebar triggers that shouldn't read as a field. */
|
|
38
|
+
borderless?: boolean
|
|
39
|
+
/** Drop the solid background so the trigger blends into its surface. */
|
|
40
|
+
transparent?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const SelectTrigger = React.forwardRef<
|
|
44
|
+
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
45
|
+
SelectTriggerProps
|
|
46
|
+
>(({ className, size = "md", borderless = false, transparent = false, children, ...props }, ref) => (
|
|
47
|
+
<SelectPrimitive.Trigger
|
|
48
|
+
ref={ref}
|
|
49
|
+
className={cn(
|
|
50
|
+
"ax:flex ax:w-full ax:items-center ax:justify-between ax:gap-xs ax:rounded-(--input-radius) ax:py-xs",
|
|
51
|
+
"ax:text-foreground ax:placeholder:text-muted-foreground",
|
|
52
|
+
"ax:transition-[border-color,box-shadow] ax:transition-snappy",
|
|
53
|
+
"ax:focus:outline-none ax:focus:ring-2 ax:focus:ring-ring ax:focus:ring-offset-1 ax:focus:ring-offset-background ax:focus:border-transparent",
|
|
54
|
+
"ax:cursor-pointer ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
|
|
55
|
+
"ax:data-[placeholder]:text-muted-foreground",
|
|
56
|
+
// leading-snug on the value span: t-label-sm is leading-none, and line-clamp-1
|
|
57
|
+
// sets overflow-hidden: a 1.0 line-height would shave descenders (g, y, p).
|
|
58
|
+
// flex-1 + text-start keep the value filling the trigger and left-aligned so
|
|
59
|
+
// a truncated value (…) doesn't drift to centre against the chevron.
|
|
60
|
+
"ax:[&>span]:line-clamp-1 ax:[&>span]:leading-snug ax:[&>span]:min-w-0 ax:[&>span]:flex-1 ax:[&>span]:text-start",
|
|
61
|
+
borderless ? "ax:border-0" : "ax:border ax:border-input",
|
|
62
|
+
transparent ? "ax:bg-transparent" : "ax:bg-background",
|
|
63
|
+
TRIGGER_HEIGHT[size],
|
|
64
|
+
TRIGGER_PADDING_X[size],
|
|
65
|
+
TRIGGER_TEXT[size],
|
|
66
|
+
className
|
|
67
|
+
)}
|
|
68
|
+
{...props}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
<SelectPrimitive.Icon asChild>
|
|
72
|
+
<ChevronDown className="ax:size-4 ax:shrink-0 ax:text-muted-foreground" />
|
|
73
|
+
</SelectPrimitive.Icon>
|
|
74
|
+
</SelectPrimitive.Trigger>
|
|
75
|
+
))
|
|
76
|
+
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
77
|
+
|
|
78
|
+
export const SelectScrollUpButton = React.forwardRef<
|
|
79
|
+
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
80
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
81
|
+
>(({ className, ...props }, ref) => (
|
|
82
|
+
<SelectPrimitive.ScrollUpButton
|
|
83
|
+
ref={ref}
|
|
84
|
+
className={cn("ax:flex ax:cursor-default ax:items-center ax:justify-center ax:py-1", className)}
|
|
85
|
+
{...props}
|
|
86
|
+
>
|
|
87
|
+
<ChevronUp className="ax:size-4" />
|
|
88
|
+
</SelectPrimitive.ScrollUpButton>
|
|
89
|
+
))
|
|
90
|
+
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
91
|
+
|
|
92
|
+
export const SelectScrollDownButton = React.forwardRef<
|
|
93
|
+
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
94
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
95
|
+
>(({ className, ...props }, ref) => (
|
|
96
|
+
<SelectPrimitive.ScrollDownButton
|
|
97
|
+
ref={ref}
|
|
98
|
+
className={cn("ax:flex ax:cursor-default ax:items-center ax:justify-center ax:py-1", className)}
|
|
99
|
+
{...props}
|
|
100
|
+
>
|
|
101
|
+
<ChevronDown className="ax:size-4" />
|
|
102
|
+
</SelectPrimitive.ScrollDownButton>
|
|
103
|
+
))
|
|
104
|
+
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
|
105
|
+
|
|
106
|
+
export const SelectContent = React.forwardRef<
|
|
107
|
+
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
108
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
109
|
+
>(({ className, children, position = "popper", ...props }, ref) => (
|
|
110
|
+
<SelectPrimitive.Portal>
|
|
111
|
+
<SelectPrimitive.Content
|
|
112
|
+
ref={ref}
|
|
113
|
+
className={cn(
|
|
114
|
+
"ax:relative ax:z-[1100] ax:max-h-96 ax:min-w-[8rem] ax:overflow-hidden",
|
|
115
|
+
"ax:rounded-(--input-radius) ax:border ax:border-border ax:bg-popover ax:text-popover-foreground ax:shadow-md",
|
|
116
|
+
"ax:data-[state=open]:animate-in ax:data-[state=closed]:animate-out",
|
|
117
|
+
"ax:data-[state=closed]:fade-out-0 ax:data-[state=open]:fade-in-0",
|
|
118
|
+
"ax:data-[state=closed]:zoom-out-[0.96] ax:data-[state=open]:zoom-in-[0.96]",
|
|
119
|
+
"ax:data-[side=bottom]:slide-in-from-top-1",
|
|
120
|
+
"ax:data-[side=left]:slide-in-from-right-1",
|
|
121
|
+
"ax:data-[side=right]:slide-in-from-left-1",
|
|
122
|
+
"ax:data-[side=top]:slide-in-from-bottom-1",
|
|
123
|
+
"ax:[animation-duration:var(--motion-fast)] ax:[animation-timing-function:var(--motion-ease-out)]",
|
|
124
|
+
"ax:motion-reduce:animate-none",
|
|
125
|
+
position === "popper" && [
|
|
126
|
+
"ax:data-[side=bottom]:translate-y-1",
|
|
127
|
+
"ax:data-[side=left]:-translate-x-1",
|
|
128
|
+
"ax:data-[side=right]:translate-x-1",
|
|
129
|
+
"ax:data-[side=top]:-translate-y-1",
|
|
130
|
+
],
|
|
131
|
+
className
|
|
132
|
+
)}
|
|
133
|
+
position={position}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
<SelectScrollUpButton />
|
|
137
|
+
<SelectPrimitive.Viewport
|
|
138
|
+
className={cn(
|
|
139
|
+
"ax:p-2xs",
|
|
140
|
+
position === "popper" && "ax:h-[var(--radix-select-trigger-height)] ax:w-full ax:min-w-[var(--radix-select-trigger-width)]"
|
|
141
|
+
)}
|
|
142
|
+
>
|
|
143
|
+
{children}
|
|
144
|
+
</SelectPrimitive.Viewport>
|
|
145
|
+
<SelectScrollDownButton />
|
|
146
|
+
</SelectPrimitive.Content>
|
|
147
|
+
</SelectPrimitive.Portal>
|
|
148
|
+
))
|
|
149
|
+
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
150
|
+
|
|
151
|
+
export const SelectLabel = React.forwardRef<
|
|
152
|
+
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
153
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
154
|
+
>(({ className, ...props }, ref) => (
|
|
155
|
+
<SelectPrimitive.Label
|
|
156
|
+
ref={ref}
|
|
157
|
+
className={cn("ax:px-2 ax:py-1.5 ax:t-overline ax:text-muted-foreground", className)}
|
|
158
|
+
{...props}
|
|
159
|
+
/>
|
|
160
|
+
))
|
|
161
|
+
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
162
|
+
|
|
163
|
+
export const SelectItem = React.forwardRef<
|
|
164
|
+
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
165
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
166
|
+
>(({ className, children, ...props }, ref) => (
|
|
167
|
+
<SelectPrimitive.Item
|
|
168
|
+
ref={ref}
|
|
169
|
+
className={cn(
|
|
170
|
+
"ax:relative ax:flex ax:w-full ax:cursor-pointer ax:select-none ax:items-center ax:rounded-(--input-radius) ax:py-1.5 ax:pl-2 ax:pr-8",
|
|
171
|
+
"ax:t-body-sm ax:text-foreground ax:outline-none",
|
|
172
|
+
"ax:focus:bg-muted ax:focus:text-foreground",
|
|
173
|
+
"ax:data-[disabled]:pointer-events-none ax:data-[disabled]:opacity-50",
|
|
174
|
+
className
|
|
175
|
+
)}
|
|
176
|
+
{...props}
|
|
177
|
+
>
|
|
178
|
+
<span className="ax:absolute ax:right-2 ax:flex ax:size-3.5 ax:items-center ax:justify-center">
|
|
179
|
+
<SelectPrimitive.ItemIndicator>
|
|
180
|
+
<Check className="ax:size-4" />
|
|
181
|
+
</SelectPrimitive.ItemIndicator>
|
|
182
|
+
</span>
|
|
183
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
184
|
+
</SelectPrimitive.Item>
|
|
185
|
+
))
|
|
186
|
+
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
187
|
+
|
|
188
|
+
export const SelectSeparator = React.forwardRef<
|
|
189
|
+
React.ElementRef<typeof SelectPrimitive.Separator>,
|
|
190
|
+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
|
191
|
+
>(({ className, ...props }, ref) => (
|
|
192
|
+
<SelectPrimitive.Separator
|
|
193
|
+
ref={ref}
|
|
194
|
+
className={cn("ax:-mx-1 ax:my-1 ax:h-px ax:bg-border", className)}
|
|
195
|
+
{...props}
|
|
196
|
+
/>
|
|
197
|
+
))
|
|
198
|
+
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Separator } from "./separator"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Separator> = {
|
|
6
|
+
title: "Components/Separator",
|
|
7
|
+
component: Separator,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof Separator>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="ax:w-64 ax:space-y-md">
|
|
17
|
+
<p className="ax:t-body-sm">Section one</p>
|
|
18
|
+
<Separator />
|
|
19
|
+
<p className="ax:t-body-sm">Section two</p>
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
|
5
|
+
import { cn } from "../lib/cn"
|
|
6
|
+
|
|
7
|
+
export const Separator = React.forwardRef<
|
|
8
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
9
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
10
|
+
>(({ className, orientation = "horizontal", decorative = true, ...props }, ref) => (
|
|
11
|
+
<SeparatorPrimitive.Root
|
|
12
|
+
ref={ref}
|
|
13
|
+
decorative={decorative}
|
|
14
|
+
orientation={orientation}
|
|
15
|
+
className={cn(
|
|
16
|
+
"ax:shrink-0 ax:bg-border",
|
|
17
|
+
orientation === "horizontal" ? "ax:h-px ax:w-full" : "ax:h-full ax:w-px",
|
|
18
|
+
className
|
|
19
|
+
)}
|
|
20
|
+
{...props}
|
|
21
|
+
/>
|
|
22
|
+
))
|
|
23
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
2
|
+
import { ShimmerText } from "./shimmer-text"
|
|
3
|
+
|
|
4
|
+
const meta: Meta<typeof ShimmerText> = {
|
|
5
|
+
title: "Components/ShimmerText",
|
|
6
|
+
component: ShimmerText,
|
|
7
|
+
tags: ["autodocs"],
|
|
8
|
+
args: { children: "Searching listings on Yas Island", duration: 2.5, spread: 20 },
|
|
9
|
+
argTypes: {
|
|
10
|
+
as: { control: "select", options: ["span", "p", "div", "strong", "h1", "h2", "h3", "h4", "h5", "h6"] },
|
|
11
|
+
duration: { control: { type: "range", min: 0.5, max: 8, step: 0.5 } },
|
|
12
|
+
spread: { control: { type: "range", min: 5, max: 45, step: 1 } },
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
type Story = StoryObj<typeof ShimmerText>
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {}
|
|
20
|
+
|
|
21
|
+
export const Heading: Story = { args: { as: "h3", className: "ax:t-h3", children: "Preparing your report" } }
|
|
22
|
+
|
|
23
|
+
export const SlowWide: Story = { args: { duration: 5, spread: 40, children: "Comparing registered sales" } }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest"
|
|
2
|
+
import * as React from "react"
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server"
|
|
4
|
+
import { ShimmerText, shimmerGradient } from "./shimmer-text"
|
|
5
|
+
|
|
6
|
+
describe("shimmerGradient", () => {
|
|
7
|
+
it("brightens the centre and spreads the highlight either side", () => {
|
|
8
|
+
expect(shimmerGradient(20)).toContain("hsl(var(--muted-foreground)) 30%, hsl(var(--foreground)) 50%, hsl(var(--muted-foreground)) 70%")
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it("clamps the spread to 5-45", () => {
|
|
12
|
+
expect(shimmerGradient(0)).toContain(" 45%, ")
|
|
13
|
+
expect(shimmerGradient(99)).toContain(" 5%, ")
|
|
14
|
+
})
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
describe("ShimmerText", () => {
|
|
18
|
+
it("renders a span with the gradient inline, so class merging cannot drop it", () => {
|
|
19
|
+
const html = renderToStaticMarkup(<ShimmerText className="ax:bg-primary">Thinking</ShimmerText>)
|
|
20
|
+
expect(html).toMatch(/^<span/)
|
|
21
|
+
expect(html).toContain("background-image:linear-gradient")
|
|
22
|
+
expect(html).toContain("background-size:200% auto")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it("takes the element, duration and caller styles", () => {
|
|
26
|
+
const html = renderToStaticMarkup(<ShimmerText as="p" duration={4} style={{ letterSpacing: "1px" }}>Searching</ShimmerText>)
|
|
27
|
+
expect(html).toMatch(/^<p/)
|
|
28
|
+
expect(html).toContain("animation-duration:4s")
|
|
29
|
+
expect(html).toContain("letter-spacing:1px")
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it("stops the sweep under reduced motion", () => {
|
|
33
|
+
expect(renderToStaticMarkup(<ShimmerText>Thinking</ShimmerText>)).toContain("ax:motion-reduce:animate-none")
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../lib/cn"
|
|
5
|
+
|
|
6
|
+
export type ShimmerTextAs = "span" | "p" | "div" | "strong" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
|
|
7
|
+
|
|
8
|
+
export interface ShimmerTextProps extends React.HTMLAttributes<HTMLElement> {
|
|
9
|
+
as?: ShimmerTextAs
|
|
10
|
+
/** Seconds for one sweep. */
|
|
11
|
+
duration?: number
|
|
12
|
+
/** Width of the highlight as a percentage either side of centre, clamped to 5-45. */
|
|
13
|
+
spread?: number
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The gradient behind the text: muted, bright at centre, muted. */
|
|
17
|
+
export function shimmerGradient(spread: number) {
|
|
18
|
+
const s = Math.min(Math.max(spread, 5), 45)
|
|
19
|
+
return `linear-gradient(to right, hsl(var(--muted-foreground)) ${50 - s}%, hsl(var(--foreground)) 50%, hsl(var(--muted-foreground)) ${50 + s}%)`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* ShimmerText - text with a highlight sweeping across it, for work in
|
|
24
|
+
* progress such as "Thinking" or "Searching listings". The sweep stops under
|
|
25
|
+
* reduced motion; the text stays readable.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* <ShimmerText>Searching listings</ShimmerText>
|
|
29
|
+
* <ShimmerText as="p" duration={4} spread={30}>Comparing registered sales</ShimmerText>
|
|
30
|
+
*/
|
|
31
|
+
export const ShimmerText = React.forwardRef<HTMLElement, ShimmerTextProps>(
|
|
32
|
+
({ as: Tag = "span", duration = 2.5, spread = 20, className, style, ...props }, ref) => (
|
|
33
|
+
<Tag
|
|
34
|
+
ref={ref as React.Ref<never>}
|
|
35
|
+
data-slot="shimmer-text"
|
|
36
|
+
// Gradient and size go inline: cn's tailwind-merge drops bg-linear-to-r beside other bg-* classes.
|
|
37
|
+
style={{ backgroundImage: shimmerGradient(spread), backgroundSize: "200% auto", animationDuration: `${duration}s`, ...style }}
|
|
38
|
+
className={cn("ax:bg-clip-text ax:text-transparent ax:animate-text-shimmer ax:motion-reduce:animate-none", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
),
|
|
42
|
+
)
|
|
43
|
+
ShimmerText.displayName = "ShimmerText"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Sidebar, SidebarItem, SidebarSection } from "./sidebar"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Sidebar> = {
|
|
6
|
+
title: "Components/Sidebar",
|
|
7
|
+
component: Sidebar,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof Sidebar>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="ax:h-[320px] ax:border ax:border-border ax:rounded-md ax:overflow-hidden">
|
|
17
|
+
<Sidebar>
|
|
18
|
+
<SidebarSection label="Main">
|
|
19
|
+
<SidebarItem href="#" active>Dashboard</SidebarItem>
|
|
20
|
+
<SidebarItem href="#">Projects</SidebarItem>
|
|
21
|
+
</SidebarSection>
|
|
22
|
+
</Sidebar>
|
|
23
|
+
</div>
|
|
24
|
+
),
|
|
25
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot"
|
|
5
|
+
import { motion, AnimatePresence } from "motion/react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip"
|
|
9
|
+
|
|
10
|
+
// ─── Sidebar ──────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
export interface SidebarProps extends React.HTMLAttributes<HTMLElement> {
|
|
13
|
+
/** Sidebar width in rem; default 16rem (256px). */
|
|
14
|
+
width?: number
|
|
15
|
+
/** Collapsed state - controlled. When undefined, sidebar is always open. */
|
|
16
|
+
collapsed?: boolean
|
|
17
|
+
/** Optional logo / brand mark slot at the top. */
|
|
18
|
+
brand?: React.ReactNode
|
|
19
|
+
/** Optional footer slot (typically a `SidebarUserCard`). */
|
|
20
|
+
footer?: React.ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sidebar - primary app-shell navigation surface.
|
|
25
|
+
*
|
|
26
|
+
* Vertical column with brand, scrollable nav body, and footer. Uses logical
|
|
27
|
+
* properties (`border-e`) so the column sits on the inline-start edge in LTR
|
|
28
|
+
* and flips to inline-end in RTL automatically. Animates collapsed/expanded
|
|
29
|
+
* state via motion (suppressed under reduced motion).
|
|
30
|
+
*/
|
|
31
|
+
export const Sidebar = React.forwardRef<HTMLElement, SidebarProps>(
|
|
32
|
+
({ className, width = 16, collapsed, brand, footer, children, ...props }, ref) => {
|
|
33
|
+
const reduce = useReducedMotion()
|
|
34
|
+
const collapsedWidth = 4
|
|
35
|
+
|
|
36
|
+
const targetWidth = collapsed ? `${collapsedWidth}rem` : `${width}rem`
|
|
37
|
+
|
|
38
|
+
const inner = (
|
|
39
|
+
<>
|
|
40
|
+
{brand && <div className="ax:h-5xl ax:px-md ax:flex ax:items-center">{brand}</div>}
|
|
41
|
+
<nav className="ax:flex-1 ax:overflow-y-auto ax:p-sm">{children}</nav>
|
|
42
|
+
{footer && <div className="ax:border-t ax:border-border ax:p-sm">{footer}</div>}
|
|
43
|
+
</>
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
const baseClasses = cn(
|
|
47
|
+
"ax:shrink-0 ax:h-full ax:overflow-hidden ax:border-e ax:border-border ax:bg-card ax:text-card-foreground",
|
|
48
|
+
"ax:flex ax:flex-col",
|
|
49
|
+
className
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
// TooltipProvider here so collapsed SidebarItems can show labels-as-tooltips
|
|
54
|
+
// without requiring the consumer to wrap their app root themselves.
|
|
55
|
+
<TooltipProvider delayDuration={200}>
|
|
56
|
+
{reduce ? (
|
|
57
|
+
<aside
|
|
58
|
+
ref={ref}
|
|
59
|
+
aria-label="Primary"
|
|
60
|
+
style={{ width: targetWidth }}
|
|
61
|
+
className={baseClasses}
|
|
62
|
+
{...props}
|
|
63
|
+
>
|
|
64
|
+
{inner}
|
|
65
|
+
</aside>
|
|
66
|
+
) : (
|
|
67
|
+
<motion.aside
|
|
68
|
+
ref={ref as React.Ref<HTMLElement>}
|
|
69
|
+
aria-label="Primary"
|
|
70
|
+
initial={false}
|
|
71
|
+
animate={{ width: targetWidth }}
|
|
72
|
+
transition={{ type: "spring", stiffness: 320, damping: 32 }}
|
|
73
|
+
className={baseClasses}
|
|
74
|
+
{...(props as Record<string, unknown>)}
|
|
75
|
+
>
|
|
76
|
+
{inner}
|
|
77
|
+
</motion.aside>
|
|
78
|
+
)}
|
|
79
|
+
</TooltipProvider>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
)
|
|
83
|
+
Sidebar.displayName = "Sidebar"
|
|
84
|
+
|
|
85
|
+
// ─── SidebarItem ──────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
export interface SidebarItemProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
88
|
+
icon?: React.ReactNode
|
|
89
|
+
active?: boolean
|
|
90
|
+
collapsed?: boolean
|
|
91
|
+
/**
|
|
92
|
+
* Render as the child element (e.g. a Next.js `<Link>`). The child receives
|
|
93
|
+
* the className, ref, and event props so it behaves as the sidebar row while
|
|
94
|
+
* keeping its own navigation behaviour.
|
|
95
|
+
*/
|
|
96
|
+
asChild?: boolean
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* SidebarItem - a row inside `Sidebar`.
|
|
101
|
+
*
|
|
102
|
+
* Renders an `<a>` by default. Use `asChild` to wrap a Next.js `<Link>` (or
|
|
103
|
+
* any custom navigation component) without losing the visual treatment.
|
|
104
|
+
*
|
|
105
|
+
* Active state: tinted background + matching text + a 2px start-edge bar
|
|
106
|
+
* (`border-s-2 border-primary`). Inactive rows reserve the same 2px so
|
|
107
|
+
* activating doesn't shift the label horizontally.
|
|
108
|
+
*
|
|
109
|
+
* When `collapsed` is true the label is hidden and the `children` are
|
|
110
|
+
* surfaced as a Tooltip on the inline-end side. Keep `children` to a
|
|
111
|
+
* short string when collapsed mode is in play.
|
|
112
|
+
*/
|
|
113
|
+
export const SidebarItem = React.forwardRef<HTMLAnchorElement, SidebarItemProps>(
|
|
114
|
+
({ className, icon, active, collapsed, asChild, children, ...props }, ref) => {
|
|
115
|
+
const Comp = asChild ? Slot : "a"
|
|
116
|
+
|
|
117
|
+
const row = (
|
|
118
|
+
<Comp
|
|
119
|
+
ref={ref as React.Ref<HTMLAnchorElement>}
|
|
120
|
+
aria-current={active ? "page" : undefined}
|
|
121
|
+
className={cn(
|
|
122
|
+
"ax:group ax:flex ax:items-center ax:gap-sm ax:rounded-md ax:ps-xs ax:pe-xs ax:py-xs ax:t-label-sm ax:cursor-pointer",
|
|
123
|
+
// Reserve the 2px start-edge bar on every row so the active state
|
|
124
|
+
// doesn't nudge content horizontally.
|
|
125
|
+
"ax:border-s-2 ax:border-transparent",
|
|
126
|
+
"ax:transition-colors ax:transition-snappy",
|
|
127
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-2 ax:focus-visible:ring-offset-card",
|
|
128
|
+
active
|
|
129
|
+
? "ax:bg-card ax:text-primary-foreground ax:border-primary"
|
|
130
|
+
: "ax:text-foreground ax:hover:bg-muted ax:hover:text-foreground-strong",
|
|
131
|
+
collapsed && "ax:justify-center ax:ps-2xs ax:pe-2xs",
|
|
132
|
+
className
|
|
133
|
+
)}
|
|
134
|
+
{...props}
|
|
135
|
+
>
|
|
136
|
+
{icon && <span className="ax:size-4 ax:shrink-0 ax:flex ax:items-center ax:justify-center ax:[&>svg]:size-4 ax:[&>svg]:shrink-0">{icon}</span>}
|
|
137
|
+
<AnimatePresence initial={false}>
|
|
138
|
+
{!collapsed && (
|
|
139
|
+
<motion.span
|
|
140
|
+
initial={{ opacity: 0, width: 0 }}
|
|
141
|
+
animate={{ opacity: 1, width: "auto" }}
|
|
142
|
+
exit={{ opacity: 0, width: 0 }}
|
|
143
|
+
transition={{ duration: 0.15 }}
|
|
144
|
+
className="ax:overflow-hidden ax:whitespace-nowrap"
|
|
145
|
+
>
|
|
146
|
+
{children}
|
|
147
|
+
</motion.span>
|
|
148
|
+
)}
|
|
149
|
+
</AnimatePresence>
|
|
150
|
+
</Comp>
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if (collapsed) {
|
|
154
|
+
return (
|
|
155
|
+
<Tooltip>
|
|
156
|
+
<TooltipTrigger asChild>{row}</TooltipTrigger>
|
|
157
|
+
{/* `side="right"` flips to the inline-end in RTL because Radix uses
|
|
158
|
+
physical sides; we mirror with a logical `rtl:` rule on the parent. */}
|
|
159
|
+
<TooltipContent side="right">{children}</TooltipContent>
|
|
160
|
+
</Tooltip>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return row
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
SidebarItem.displayName = "SidebarItem"
|
|
168
|
+
|
|
169
|
+
// ─── SidebarSection ───────────────────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
export const SidebarSection = React.forwardRef<
|
|
172
|
+
HTMLDivElement,
|
|
173
|
+
React.HTMLAttributes<HTMLDivElement> & { label?: React.ReactNode; collapsed?: boolean }
|
|
174
|
+
>(
|
|
175
|
+
({ className, label, collapsed, children, ...props }, ref) => (
|
|
176
|
+
<div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-0.5 ax:py-xs", className)} {...props}>
|
|
177
|
+
{label && !collapsed && (
|
|
178
|
+
<div className="ax:px-xs ax:pb-xs ax:t-caption ax:text-foreground/50 ax:font-medium">
|
|
179
|
+
{label}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
{children}
|
|
183
|
+
</div>
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
SidebarSection.displayName = "SidebarSection"
|
|
187
|
+
|
|
188
|
+
// ─── SidebarUserCard ──────────────────────────────────────────────────────────
|
|
189
|
+
//
|
|
190
|
+
// Conventional footer block: avatar + name + role + optional action slot.
|
|
191
|
+
// Matches the pattern called out in the TPB spec ("User avatar + name +
|
|
192
|
+
// 'Company Admin' role label, settings, logout"). Collapses to just the
|
|
193
|
+
// avatar when the sidebar is collapsed.
|
|
194
|
+
|
|
195
|
+
// `role` is our domain prop (the sub-label under the name); the HTML
|
|
196
|
+
// attribute of the same name is omitted to keep the type clean.
|
|
197
|
+
export interface SidebarUserCardProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "role"> {
|
|
198
|
+
/** Avatar element (typically `<Avatar>` from the DS). */
|
|
199
|
+
avatar: React.ReactNode
|
|
200
|
+
/** Display name. */
|
|
201
|
+
name: React.ReactNode
|
|
202
|
+
/** Role / sub-label rendered under the name (e.g. "Company Admin"). */
|
|
203
|
+
role?: React.ReactNode
|
|
204
|
+
/** Trailing slot - typically a DropdownMenu trigger for settings/logout. */
|
|
205
|
+
action?: React.ReactNode
|
|
206
|
+
/** Hides the name/role/action when the parent Sidebar is collapsed. */
|
|
207
|
+
collapsed?: boolean
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const SidebarUserCard = React.forwardRef<HTMLDivElement, SidebarUserCardProps>(
|
|
211
|
+
({ className, avatar, name, role, action, collapsed, ...props }, ref) => (
|
|
212
|
+
<div
|
|
213
|
+
ref={ref}
|
|
214
|
+
className={cn(
|
|
215
|
+
"ax:flex ax:items-center ax:gap-sm",
|
|
216
|
+
collapsed && "ax:justify-center",
|
|
217
|
+
className
|
|
218
|
+
)}
|
|
219
|
+
{...props}
|
|
220
|
+
>
|
|
221
|
+
<div className="ax:shrink-0">{avatar}</div>
|
|
222
|
+
{!collapsed && (
|
|
223
|
+
<>
|
|
224
|
+
<div className="ax:min-w-0 ax:flex-1">
|
|
225
|
+
<div className="ax:t-label-sm ax:text-foreground ax:truncate">{name}</div>
|
|
226
|
+
{role && <div className="ax:t-caption ax:text-muted-foreground ax:truncate">{role}</div>}
|
|
227
|
+
</div>
|
|
228
|
+
{action && <div className="ax:shrink-0">{action}</div>}
|
|
229
|
+
</>
|
|
230
|
+
)}
|
|
231
|
+
</div>
|
|
232
|
+
)
|
|
233
|
+
)
|
|
234
|
+
SidebarUserCard.displayName = "SidebarUserCard"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Skeleton } from "./skeleton"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Skeleton> = {
|
|
6
|
+
title: "Components/Skeleton",
|
|
7
|
+
component: Skeleton,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof Skeleton>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<div className="ax:space-y-sm ax:w-64">
|
|
17
|
+
<Skeleton className="ax:h-4 ax:w-full" />
|
|
18
|
+
<Skeleton className="ax:h-4 ax:w-3/4" />
|
|
19
|
+
<Skeleton className="ax:h-20 ax:w-full ax:rounded-md" />
|
|
20
|
+
</div>
|
|
21
|
+
),
|
|
22
|
+
}
|