@adres/mgda-ui 0.0.0-stage → 0.0.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/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,157 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import * as SelectPrimitive from "@radix-ui/react-select"
|
|
5
|
+
import { ChevronDown } from "lucide-react"
|
|
6
|
+
import { tv } from "tailwind-variants"
|
|
7
|
+
import { SelectContent } from "./select"
|
|
8
|
+
import { Input, type InputProps } from "./input"
|
|
9
|
+
import { cn } from "../lib/cn"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* InputGroup - a Select + Input compactly joined into a single inline group.
|
|
13
|
+
*
|
|
14
|
+
* The third member of the joined-pill composite family alongside InputButton
|
|
15
|
+
* (Input+Button) and SelectButton (Select+Button): the Select trigger occupies
|
|
16
|
+
* the left half, the Input occupies the right half, both at the same height
|
|
17
|
+
* ladder so they line up with the other composites at a matching size.
|
|
18
|
+
*
|
|
19
|
+
* <InputGroup
|
|
20
|
+
* size="md"
|
|
21
|
+
* selectPlaceholder="Title"
|
|
22
|
+
* selectValue={title}
|
|
23
|
+
* onSelectValueChange={setTitle}
|
|
24
|
+
* placeholder="Full name"
|
|
25
|
+
* value={name}
|
|
26
|
+
* onChange={(e) => setName(e.target.value)}
|
|
27
|
+
* >
|
|
28
|
+
* <SelectItem value="mr">Mr.</SelectItem>
|
|
29
|
+
* <SelectItem value="ms">Ms.</SelectItem>
|
|
30
|
+
* </InputGroup>
|
|
31
|
+
*
|
|
32
|
+
* Select-half props are prefixed (selectValue, selectPlaceholder, ...) so they
|
|
33
|
+
* don't collide with the native input attributes (value, placeholder, ...)
|
|
34
|
+
* inherited from InputProps. `disabled` disables BOTH halves; use
|
|
35
|
+
* `selectDisabled` to disable only the select.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
export type InputGroupSize = "sm" | "md" | "lg"
|
|
39
|
+
|
|
40
|
+
// Two-slot composite: the select trigger's height/padding/type ramp scales
|
|
41
|
+
// with `size` (mirrors the standalone Select / SelectButton trigger); the
|
|
42
|
+
// input slot only carries the static join classes since Input derives its
|
|
43
|
+
// own size-driven height/padding/type from its own `size` prop.
|
|
44
|
+
const inputGroupVariants = tv(
|
|
45
|
+
{
|
|
46
|
+
slots: {
|
|
47
|
+
trigger: [
|
|
48
|
+
"ax:inline-flex ax:shrink-0 ax:items-center ax:justify-between ax:gap-xs ax:whitespace-nowrap ax:cursor-pointer ax:select-none",
|
|
49
|
+
"ax:bg-transparent ax:text-foreground",
|
|
50
|
+
"ax:border ax:border-input ax:rounded-l-(--input-radius) ax:rounded-r-none ax:-mr-px",
|
|
51
|
+
"ax:transition-[border-color,box-shadow] ax:transition-snappy",
|
|
52
|
+
// ring-inset (not ring-offset) keeps the focus ring inside the trigger's
|
|
53
|
+
// own box: an outward halo would bleed across the seam onto the Input.
|
|
54
|
+
"ax:focus:outline-none ax:focus:z-10 ax:focus:ring-2 ax:focus:ring-inset ax:focus:ring-ring ax:focus:border-transparent",
|
|
55
|
+
"ax:disabled:cursor-not-allowed ax:disabled:opacity-50",
|
|
56
|
+
"ax:data-placeholder:text-muted-foreground",
|
|
57
|
+
// leading-snug on the value span: t-label-sm is leading-none, and line-clamp-1
|
|
58
|
+
// sets overflow-hidden: a 1.0 line-height would shave descenders (g, y, p).
|
|
59
|
+
"ax:[&>span]:line-clamp-1 ax:[&>span]:leading-snug",
|
|
60
|
+
],
|
|
61
|
+
// Join: square the left corner, overlap the trigger by 1px, and lift on
|
|
62
|
+
// focus so the focus ring sits above the adjoining trigger. ring-inset +
|
|
63
|
+
// ring-offset-0 override Input's own standalone ring-offset styling so
|
|
64
|
+
// its focus ring doesn't bleed left onto the trigger either.
|
|
65
|
+
input: "ax:relative ax:min-w-0 ax:flex-1 ax:-ml-px ax:rounded-l-none ax:focus-visible:z-10 ax:focus-visible:ring-inset ax:focus-visible:ring-offset-0",
|
|
66
|
+
},
|
|
67
|
+
variants: {
|
|
68
|
+
size: {
|
|
69
|
+
sm: { trigger: "ax:h-2xl ax:px-sm ax:t-label-sm" },
|
|
70
|
+
md: { trigger: "ax:h-3xl ax:px-md ax:t-label-sm" },
|
|
71
|
+
lg: { trigger: "ax:h-4xl ax:px-xl ax:text-base" },
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
defaultVariants: {
|
|
75
|
+
size: "md",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
{ twMerge: false }
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
export interface InputGroupProps extends Omit<InputProps, "size"> {
|
|
82
|
+
/** Shared height/typography ladder - matches Button, Input, Select, InputButton, SelectButton. */
|
|
83
|
+
size?: InputGroupSize
|
|
84
|
+
|
|
85
|
+
// ─── Select half ──────────────────────────────────────────────────────────────
|
|
86
|
+
selectValue?: string
|
|
87
|
+
selectDefaultValue?: string
|
|
88
|
+
onSelectValueChange?: (value: string) => void
|
|
89
|
+
selectPlaceholder?: string
|
|
90
|
+
selectName?: string
|
|
91
|
+
/** Disables only the select half. `disabled` (inherited from InputProps) disables both. */
|
|
92
|
+
selectDisabled?: boolean
|
|
93
|
+
/** Extra className appended to the select trigger. */
|
|
94
|
+
selectClassName?: string
|
|
95
|
+
/** SelectItem (and SelectLabel/SelectSeparator) children for the popover. */
|
|
96
|
+
children: React.ReactNode
|
|
97
|
+
|
|
98
|
+
// ─── Input half ───────────────────────────────────────────────────────────────
|
|
99
|
+
/** Extra className appended to the inner Input. */
|
|
100
|
+
inputClassName?: string
|
|
101
|
+
|
|
102
|
+
/** className for the wrapping group (the input fills the remaining width). */
|
|
103
|
+
className?: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const InputGroup = React.forwardRef<HTMLInputElement, InputGroupProps>(
|
|
107
|
+
(
|
|
108
|
+
{
|
|
109
|
+
size = "md",
|
|
110
|
+
selectValue,
|
|
111
|
+
selectDefaultValue,
|
|
112
|
+
onSelectValueChange,
|
|
113
|
+
selectPlaceholder,
|
|
114
|
+
selectName,
|
|
115
|
+
selectDisabled,
|
|
116
|
+
selectClassName,
|
|
117
|
+
children,
|
|
118
|
+
inputClassName,
|
|
119
|
+
className,
|
|
120
|
+
disabled,
|
|
121
|
+
...inputProps
|
|
122
|
+
},
|
|
123
|
+
ref
|
|
124
|
+
) => {
|
|
125
|
+
const { trigger, input } = inputGroupVariants({ size })
|
|
126
|
+
return (
|
|
127
|
+
<div
|
|
128
|
+
data-slot="input-group"
|
|
129
|
+
className={cn("ax:isolate ax:flex ax:w-full ax:items-stretch", disabled && "ax:opacity-50 ax:pointer-events-none", className)}
|
|
130
|
+
>
|
|
131
|
+
<SelectPrimitive.Root
|
|
132
|
+
value={selectValue}
|
|
133
|
+
defaultValue={selectDefaultValue}
|
|
134
|
+
onValueChange={onSelectValueChange}
|
|
135
|
+
disabled={disabled || selectDisabled}
|
|
136
|
+
name={selectName}
|
|
137
|
+
>
|
|
138
|
+
<SelectPrimitive.Trigger className={cn(trigger(), selectClassName)}>
|
|
139
|
+
<SelectPrimitive.Value placeholder={selectPlaceholder} />
|
|
140
|
+
<SelectPrimitive.Icon asChild>
|
|
141
|
+
<ChevronDown className="ax:size-4 ax:shrink-0 ax:text-muted-foreground" />
|
|
142
|
+
</SelectPrimitive.Icon>
|
|
143
|
+
</SelectPrimitive.Trigger>
|
|
144
|
+
<SelectContent>{children}</SelectContent>
|
|
145
|
+
</SelectPrimitive.Root>
|
|
146
|
+
<Input
|
|
147
|
+
ref={ref}
|
|
148
|
+
size={size}
|
|
149
|
+
disabled={disabled}
|
|
150
|
+
className={cn(input(), inputClassName)}
|
|
151
|
+
{...inputProps}
|
|
152
|
+
/>
|
|
153
|
+
</div>
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
)
|
|
157
|
+
InputGroup.displayName = "InputGroup"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Field, Input, Label, Textarea } from "./input"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof Input> = {
|
|
6
|
+
title: "Components/Input",
|
|
7
|
+
component: Input,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export default meta
|
|
12
|
+
type Story = StoryObj<typeof Input>
|
|
13
|
+
|
|
14
|
+
export const Default: Story = {
|
|
15
|
+
render: () => (
|
|
16
|
+
<Field className="ax:max-w-sm">
|
|
17
|
+
<Label htmlFor="email">Email</Label>
|
|
18
|
+
<Input id="email" type="email" placeholder="you@example.com" />
|
|
19
|
+
</Field>
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const TextareaStory: Story = {
|
|
24
|
+
name: "Textarea",
|
|
25
|
+
render: () => (
|
|
26
|
+
<Field className="ax:max-w-sm">
|
|
27
|
+
<Label htmlFor="bio">Bio</Label>
|
|
28
|
+
<Textarea id="bio" placeholder="Tell us about yourself" />
|
|
29
|
+
</Field>
|
|
30
|
+
),
|
|
31
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../lib/cn"
|
|
5
|
+
|
|
6
|
+
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
export type InputSize = "sm" | "md" | "lg"
|
|
9
|
+
|
|
10
|
+
// ─── TextField context ────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
interface TextFieldContextValue {
|
|
13
|
+
inRoot: boolean
|
|
14
|
+
size: InputSize
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const TextFieldContext = React.createContext<TextFieldContextValue>({
|
|
18
|
+
inRoot: false,
|
|
19
|
+
size: "md",
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
// ─── Size maps ────────────────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const HEIGHT: Record<InputSize, string> = {
|
|
25
|
+
sm: "ax:h-2xl", // 32px
|
|
26
|
+
md: "ax:h-3xl", // 40px
|
|
27
|
+
lg: "ax:h-4xl", // 48px
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Mirrors Button's px-scale so an Input and a Button at the same size share
|
|
31
|
+
// identical horizontal interior padding.
|
|
32
|
+
const PADDING_X: Record<InputSize, string> = {
|
|
33
|
+
sm: "ax:px-sm",
|
|
34
|
+
md: "ax:px-sm",
|
|
35
|
+
lg: "ax:px-md",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Mirrors Button's type ramp (label-md for sm/md, text-base for lg).
|
|
39
|
+
const TEXT_SIZE: Record<InputSize, string> = {
|
|
40
|
+
sm: "ax:t-label-sm",
|
|
41
|
+
md: "ax:t-label-sm",
|
|
42
|
+
lg: "ax:text-base",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ─── Input ────────────────────────────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
export interface InputProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
48
|
+
invalid?: boolean
|
|
49
|
+
size?: InputSize
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
53
|
+
({ className, type = "text", invalid, size: sizeProp, ...props }, ref) => {
|
|
54
|
+
const { inRoot, size: ctxSize } = React.useContext(TextFieldContext)
|
|
55
|
+
const size = sizeProp ?? ctxSize
|
|
56
|
+
|
|
57
|
+
return (
|
|
58
|
+
<input
|
|
59
|
+
ref={ref}
|
|
60
|
+
type={type}
|
|
61
|
+
aria-invalid={invalid || props["aria-invalid"]}
|
|
62
|
+
data-slot="input"
|
|
63
|
+
className={cn(
|
|
64
|
+
"ax:bg-card ax:text-foreground ax:placeholder:text-muted-foreground/50",
|
|
65
|
+
"ax:transition-[border-color,box-shadow] ax:transition-snappy",
|
|
66
|
+
"ax:file:border-0 ax:file:bg-transparent ax:file:text-sm ax:file:font-medium",
|
|
67
|
+
TEXT_SIZE[size],
|
|
68
|
+
inRoot
|
|
69
|
+
? [
|
|
70
|
+
// Inside TextFieldRoot: root owns the border, ring, height, and px
|
|
71
|
+
"ax:flex-1 ax:min-w-0 ax:h-full ax:rounded-none ax:border-0 ax:px-0 ax:py-0",
|
|
72
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-0 ax:focus-visible:ring-offset-0",
|
|
73
|
+
"ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-transparent",
|
|
74
|
+
]
|
|
75
|
+
: [
|
|
76
|
+
// Standalone
|
|
77
|
+
"ax:w-full ax:rounded-(--input-radius) ax:border ax:border-input",
|
|
78
|
+
HEIGHT[size],
|
|
79
|
+
PADDING_X[size],
|
|
80
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-1 ax:focus-visible:ring-offset-background ax:focus-visible:border-transparent",
|
|
81
|
+
"ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-muted",
|
|
82
|
+
"ax:aria-[invalid=true]:border-destructive ax:aria-[invalid=true]:ring-2 ax:aria-[invalid=true]:ring-destructive/30",
|
|
83
|
+
],
|
|
84
|
+
className
|
|
85
|
+
)}
|
|
86
|
+
{...props}
|
|
87
|
+
/>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
)
|
|
91
|
+
Input.displayName = "Input"
|
|
92
|
+
|
|
93
|
+
// ─── TextFieldRoot ─────────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export interface TextFieldRootProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
96
|
+
size?: InputSize
|
|
97
|
+
invalid?: boolean
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export const TextFieldRoot = React.forwardRef<HTMLDivElement, TextFieldRootProps>(
|
|
101
|
+
({ className, size: sizeProp, invalid, ...props }, ref) => {
|
|
102
|
+
const { size: ctxSize } = React.useContext(TextFieldContext)
|
|
103
|
+
const size = sizeProp ?? ctxSize
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<TextFieldContext.Provider value={{ inRoot: true, size }}>
|
|
107
|
+
<div
|
|
108
|
+
ref={ref}
|
|
109
|
+
className={cn(
|
|
110
|
+
"ax:flex ax:items-center ax:gap-xs ax:w-full ax:rounded-(--input-radius) ax:border ax:border-input ax:bg-card ax:overflow-hidden",
|
|
111
|
+
"ax:transition-[border-color,box-shadow] ax:transition-snappy",
|
|
112
|
+
"ax:focus-within:ring-2 ax:focus-within:ring-ring ax:focus-within:ring-offset-1 ax:focus-within:ring-offset-background ax:focus-within:border-transparent",
|
|
113
|
+
// CSS detects disabled/invalid state on nested input
|
|
114
|
+
"ax:has-[input:disabled]:opacity-50 ax:has-[input:disabled]:cursor-not-allowed ax:has-[input:disabled]:bg-muted",
|
|
115
|
+
"ax:has-[input[aria-invalid=true]]:border-destructive ax:has-[input[aria-invalid=true]]:focus-within:ring-destructive/30",
|
|
116
|
+
HEIGHT[size],
|
|
117
|
+
PADDING_X[size],
|
|
118
|
+
invalid && "ax:border-destructive ax:ring-2 ax:ring-destructive/30",
|
|
119
|
+
className
|
|
120
|
+
)}
|
|
121
|
+
{...props}
|
|
122
|
+
/>
|
|
123
|
+
</TextFieldContext.Provider>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
TextFieldRoot.displayName = "TextFieldRoot"
|
|
128
|
+
|
|
129
|
+
// ─── TextFieldSlot ─────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
export interface TextFieldSlotProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
132
|
+
|
|
133
|
+
export const TextFieldSlot = React.forwardRef<HTMLDivElement, TextFieldSlotProps>(
|
|
134
|
+
({ className, ...props }, ref) => (
|
|
135
|
+
<div
|
|
136
|
+
ref={ref}
|
|
137
|
+
className={cn(
|
|
138
|
+
"ax:flex ax:items-center ax:shrink-0 ax:text-muted-foreground",
|
|
139
|
+
// Slots with interactive children (buttons, links) need pointer events
|
|
140
|
+
"ax:pointer-events-none ax:[&:has(button)]:pointer-events-auto ax:[&:has(a)]:pointer-events-auto",
|
|
141
|
+
className
|
|
142
|
+
)}
|
|
143
|
+
{...props}
|
|
144
|
+
/>
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
TextFieldSlot.displayName = "TextFieldSlot"
|
|
148
|
+
|
|
149
|
+
// ─── Textarea ─────────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
151
|
+
export type TextareaResize = "none" | "vertical" | "horizontal" | "both"
|
|
152
|
+
export type TextareaSize = "sm" | "md" | "lg"
|
|
153
|
+
|
|
154
|
+
// Mirrors Button's px-scale + type ramp. py-* tuned so the single-line
|
|
155
|
+
// height of a Textarea matches the equivalent-size Button:
|
|
156
|
+
// sm 32px (py-1.5 = 6px × 2 + 20px line), md 40px, lg 48px.
|
|
157
|
+
const TEXTAREA_SIZE: Record<TextareaSize, string> = {
|
|
158
|
+
sm: "ax:p-sm ax:t-label-sm",
|
|
159
|
+
md: "ax:p-md ax:t-label-sm",
|
|
160
|
+
lg: "ax:p-md ax:text-base",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const RESIZE_CLASS: Record<TextareaResize, string> = {
|
|
164
|
+
none: "ax:resize-none",
|
|
165
|
+
vertical: "ax:resize-y",
|
|
166
|
+
horizontal: "ax:resize-x",
|
|
167
|
+
both: "resize",
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export interface TextareaProps extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
171
|
+
invalid?: boolean
|
|
172
|
+
resize?: TextareaResize
|
|
173
|
+
size?: TextareaSize
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
|
177
|
+
({ className, invalid, resize = "vertical", size: sizeProp, ...props }, ref) => {
|
|
178
|
+
const { size: ctxSize } = React.useContext(TextFieldContext)
|
|
179
|
+
const size = sizeProp ?? ctxSize
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<textarea
|
|
183
|
+
ref={ref}
|
|
184
|
+
aria-invalid={invalid || props["aria-invalid"]}
|
|
185
|
+
data-slot="textarea"
|
|
186
|
+
className={cn(
|
|
187
|
+
"ax:flex ax:min-h-20 ax:w-full ax:rounded-(--input-radius) ax:bg-card",
|
|
188
|
+
"ax:border ax:border-input",
|
|
189
|
+
"ax:text-foreground ax:placeholder:text-muted-foreground/50",
|
|
190
|
+
"ax:transition-[border-color,box-shadow] ax:transition-snappy",
|
|
191
|
+
"ax:focus-visible:outline-none ax:focus-visible:ring-2 ax:focus-visible:ring-ring ax:focus-visible:ring-offset-1 ax:focus-visible:ring-offset-background ax:focus-visible:border-transparent",
|
|
192
|
+
"ax:disabled:cursor-not-allowed ax:disabled:opacity-50 ax:disabled:bg-muted",
|
|
193
|
+
"ax:aria-[invalid=true]:border-destructive ax:aria-[invalid=true]:ring-2 ax:aria-[invalid=true]:ring-destructive/30",
|
|
194
|
+
TEXTAREA_SIZE[size],
|
|
195
|
+
RESIZE_CLASS[resize],
|
|
196
|
+
className
|
|
197
|
+
)}
|
|
198
|
+
{...props}
|
|
199
|
+
/>
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
Textarea.displayName = "Textarea"
|
|
204
|
+
|
|
205
|
+
// ─── Label ────────────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
export type LabelSize = "xs" | "sm" | "md" | "lg"
|
|
208
|
+
|
|
209
|
+
// xs/sm/md share the compact label scale; only lg steps up, matching the
|
|
210
|
+
// point where Input's own type ramp moves off t-label-sm (see TEXT_SIZE above).
|
|
211
|
+
const LABEL_TEXT_SIZE: Record<LabelSize, string> = {
|
|
212
|
+
xs: "ax:t-label-xs",
|
|
213
|
+
sm: "ax:t-label-xs",
|
|
214
|
+
md: "ax:t-label-xs",
|
|
215
|
+
lg: "ax:t-label-sm",
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export interface LabelProps extends React.LabelHTMLAttributes<HTMLLabelElement> {
|
|
219
|
+
size?: LabelSize
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export const Label = React.forwardRef<HTMLLabelElement, LabelProps>(
|
|
223
|
+
({ className, size: sizeProp, ...props }, ref) => {
|
|
224
|
+
const { size: ctxSize } = React.useContext(TextFieldContext)
|
|
225
|
+
const size = sizeProp ?? ctxSize
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
<label
|
|
229
|
+
ref={ref}
|
|
230
|
+
className={cn(
|
|
231
|
+
LABEL_TEXT_SIZE[size],
|
|
232
|
+
"ax:text-foreground",
|
|
233
|
+
"ax:peer-disabled:cursor-not-allowed ax:peer-disabled:opacity-70",
|
|
234
|
+
className
|
|
235
|
+
)}
|
|
236
|
+
{...props}
|
|
237
|
+
/>
|
|
238
|
+
)
|
|
239
|
+
}
|
|
240
|
+
)
|
|
241
|
+
Label.displayName = "Label"
|
|
242
|
+
|
|
243
|
+
// ─── Field ────────────────────────────────────────────────────────────────────
|
|
244
|
+
|
|
245
|
+
export interface FieldProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
246
|
+
help?: React.ReactNode
|
|
247
|
+
error?: React.ReactNode
|
|
248
|
+
/** Sizes the Label and any Input/Textarea/TextFieldRoot inside, unless one overrides it directly. */
|
|
249
|
+
size?: InputSize
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export const Field = React.forwardRef<HTMLDivElement, FieldProps>(
|
|
253
|
+
({ className, help, error, size = "md", children, ...props }, ref) => {
|
|
254
|
+
const id = React.useId()
|
|
255
|
+
const helpId = `${id}-help`
|
|
256
|
+
return (
|
|
257
|
+
<TextFieldContext.Provider value={{ inRoot: false, size }}>
|
|
258
|
+
<div ref={ref} className={cn("ax:flex ax:flex-col ax:gap-1.5", className)} {...props}>
|
|
259
|
+
{React.Children.map(children, (child) =>
|
|
260
|
+
React.isValidElement<{ id?: string; "aria-describedby"?: string; "aria-invalid"?: boolean }>(child)
|
|
261
|
+
? React.cloneElement(child, {
|
|
262
|
+
id: child.props.id ?? id,
|
|
263
|
+
"aria-describedby":
|
|
264
|
+
help || error
|
|
265
|
+
? `${helpId} ${child.props["aria-describedby"] ?? ""}`.trim()
|
|
266
|
+
: child.props["aria-describedby"],
|
|
267
|
+
"aria-invalid": error ? true : child.props["aria-invalid"],
|
|
268
|
+
})
|
|
269
|
+
: child
|
|
270
|
+
)}
|
|
271
|
+
{(error || help) && (
|
|
272
|
+
<p
|
|
273
|
+
id={helpId}
|
|
274
|
+
className={cn("ax:text-xs", error ? "ax:text-destructive-foreground" : "ax:text-muted-foreground")}
|
|
275
|
+
>
|
|
276
|
+
{error ?? help}
|
|
277
|
+
</p>
|
|
278
|
+
)}
|
|
279
|
+
</div>
|
|
280
|
+
</TextFieldContext.Provider>
|
|
281
|
+
)
|
|
282
|
+
}
|
|
283
|
+
)
|
|
284
|
+
Field.displayName = "Field"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Card, CardContent, CardHeader, CardTitle } from "./card"
|
|
4
|
+
import { Inset } from "./inset"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof Inset> = {
|
|
7
|
+
title: "Components/Inset",
|
|
8
|
+
component: Inset,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj<typeof Inset>
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<Card className="ax:max-w-sm">
|
|
18
|
+
<CardHeader>
|
|
19
|
+
<CardTitle>Media card</CardTitle>
|
|
20
|
+
</CardHeader>
|
|
21
|
+
<Inset side="x" clip="padding-box">
|
|
22
|
+
<div className="ax:h-32 ax:bg-muted ax:flex ax:items-center ax:justify-center ax:text-muted-foreground">Inset content</div>
|
|
23
|
+
</Inset>
|
|
24
|
+
<CardContent>
|
|
25
|
+
<p>Content below the inset.</p>
|
|
26
|
+
</CardContent>
|
|
27
|
+
</Card>
|
|
28
|
+
),
|
|
29
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { cn } from "../lib/cn"
|
|
5
|
+
|
|
6
|
+
export type InsetSide = "all" | "x" | "y" | "top" | "bottom" | "left" | "right"
|
|
7
|
+
export type InsetClip = "border-box" | "padding-box"
|
|
8
|
+
|
|
9
|
+
export interface InsetProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
/**
|
|
11
|
+
* Which edges the content bleeds to. Named by the face of the container:
|
|
12
|
+
* "top" → bleeds to top, left, and right edges.
|
|
13
|
+
* "left" → bleeds to left, top, and bottom edges.
|
|
14
|
+
*/
|
|
15
|
+
side?: InsetSide
|
|
16
|
+
/** Whether the content clips at the border or allows overflow. Default "border-box". */
|
|
17
|
+
clip?: InsetClip
|
|
18
|
+
/** Add padding equal to the container's inset padding ABOVE the element.
|
|
19
|
+
* Use with side="bottom" to gap the inset away from siblings above it. */
|
|
20
|
+
pt?: "none" | "current"
|
|
21
|
+
/** Add padding equal to the container's inset padding BELOW the element.
|
|
22
|
+
* Use with side="top" to gap the inset away from siblings below it. */
|
|
23
|
+
pb?: "none" | "current"
|
|
24
|
+
/** Add padding equal to the container's inset padding LEFT of the element.
|
|
25
|
+
* Use with side="right" to gap the inset away from siblings to its left. */
|
|
26
|
+
pl?: "none" | "current"
|
|
27
|
+
/** Add padding equal to the container's inset padding RIGHT of the element.
|
|
28
|
+
* Use with side="left" to gap the inset away from siblings to its right. */
|
|
29
|
+
pr?: "none" | "current"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const P = "var(--inset-padding, 0px)"
|
|
33
|
+
const NEG = `calc(-1 * ${P})`
|
|
34
|
+
|
|
35
|
+
function sideStyle(side: InsetSide): React.CSSProperties {
|
|
36
|
+
switch (side) {
|
|
37
|
+
case "all": return { margin: NEG }
|
|
38
|
+
case "x": return { marginLeft: NEG, marginRight: NEG }
|
|
39
|
+
case "y": return { marginTop: NEG, marginBottom: NEG }
|
|
40
|
+
case "top": return { marginTop: NEG, marginLeft: NEG, marginRight: NEG }
|
|
41
|
+
case "bottom": return { marginBottom: NEG, marginLeft: NEG, marginRight: NEG }
|
|
42
|
+
case "left": return { marginLeft: NEG, marginTop: NEG, marginBottom: NEG }
|
|
43
|
+
case "right": return { marginRight: NEG, marginTop: NEG, marginBottom: NEG }
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const Inset = React.forwardRef<HTMLDivElement, InsetProps>(
|
|
48
|
+
({ className, side = "all", clip = "border-box", pt = "none", pb = "none", pl = "none", pr = "none", style, children, ...props }, ref) => {
|
|
49
|
+
const inlineStyle: React.CSSProperties = {
|
|
50
|
+
...sideStyle(side),
|
|
51
|
+
...(pt === "current" ? { paddingTop: P } : {}),
|
|
52
|
+
...(pb === "current" ? { paddingBottom: P } : {}),
|
|
53
|
+
...(pl === "current" ? { paddingLeft: P } : {}),
|
|
54
|
+
...(pr === "current" ? { paddingRight: P } : {}),
|
|
55
|
+
...style,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
ref={ref}
|
|
61
|
+
style={inlineStyle}
|
|
62
|
+
className={cn(clip === "border-box" && "ax:overflow-hidden", className)}
|
|
63
|
+
{...props}
|
|
64
|
+
>
|
|
65
|
+
{children}
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
Inset.displayName = "Inset"
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { Users } from "lucide-react"
|
|
4
|
+
import { KpiCard, KpiCardGroup } from "./kpi-card"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof KpiCard> = {
|
|
7
|
+
title: "Components/KpiCard",
|
|
8
|
+
component: KpiCard,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default meta
|
|
13
|
+
type Story = StoryObj<typeof KpiCard>
|
|
14
|
+
|
|
15
|
+
export const Default: Story = {
|
|
16
|
+
render: () => (
|
|
17
|
+
<KpiCardGroup className="ax:max-w-xs">
|
|
18
|
+
<KpiCard title="Active users" value={1284} change={12} icon={<Users className="ax:size-4" />} description="vs last 30 days" />
|
|
19
|
+
</KpiCardGroup>
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const Loading: Story = {
|
|
24
|
+
render: () => (
|
|
25
|
+
<KpiCardGroup className="ax:max-w-xs">
|
|
26
|
+
<KpiCard title="Active users" value={0} loading />
|
|
27
|
+
</KpiCardGroup>
|
|
28
|
+
),
|
|
29
|
+
}
|