@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,498 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Check } from "lucide-react"
|
|
5
|
+
import { motion } from "motion/react"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
import { Card, type CardProps } from "./card"
|
|
8
|
+
import { useReducedMotion } from "../hooks/use-reduced-motion"
|
|
9
|
+
import { transitions, variants } from "../lib/motion"
|
|
10
|
+
|
|
11
|
+
export type HeroLayout = "centered" | "split" | "capsule" | "magazine" | "cinematic" | "editorial" | "mosaic" | "search"
|
|
12
|
+
|
|
13
|
+
export interface HeroStat {
|
|
14
|
+
value: React.ReactNode
|
|
15
|
+
label: React.ReactNode
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface HeroProps extends CardProps {
|
|
19
|
+
/** Optional pre-headline label / eyebrow. In magazine layout, appears in the footer-left strip. */
|
|
20
|
+
eyebrow?: React.ReactNode
|
|
21
|
+
/** Required: the marketing headline. Accepts JSX for styled spans. */
|
|
22
|
+
headline: React.ReactNode
|
|
23
|
+
/** Optional supporting paragraph. */
|
|
24
|
+
description?: React.ReactNode
|
|
25
|
+
/** CTA action slots - typically primary + secondary buttons. */
|
|
26
|
+
actions?: React.ReactNode
|
|
27
|
+
/** Main image / media slot. */
|
|
28
|
+
media?: React.ReactNode
|
|
29
|
+
/** Secondary image slot - magazine right column. */
|
|
30
|
+
secondaryMedia?: React.ReactNode
|
|
31
|
+
/** Visual template. Defaults to "split" when media is set, "centered" otherwise. */
|
|
32
|
+
layout?: HeroLayout
|
|
33
|
+
/** Metric pairs rendered below content (capsule/split) or in a bottom grid (cinematic). */
|
|
34
|
+
stats?: HeroStat[]
|
|
35
|
+
/** Brand name list for the magazine footer strip. */
|
|
36
|
+
brands?: React.ReactNode[]
|
|
37
|
+
/** Pill / live-indicator badge. split: above headline. cinematic: top-right. */
|
|
38
|
+
badge?: React.ReactNode
|
|
39
|
+
/** Brand identity element - cinematic top-left logo. */
|
|
40
|
+
brand?: React.ReactNode
|
|
41
|
+
/** Content overlaid on the media column - split floating cards, badges, etc. */
|
|
42
|
+
overlay?: React.ReactNode
|
|
43
|
+
/** Search widget slot - rendered below description in the "search" layout. */
|
|
44
|
+
search?: React.ReactNode
|
|
45
|
+
/** Trust-point strings shown below the search widget. A checkmark is prepended to each. */
|
|
46
|
+
badges?: string[]
|
|
47
|
+
/** "dark" applies a deep neutral background - intended for the cinematic layout. */
|
|
48
|
+
theme?: "light" | "dark"
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const Hero = React.forwardRef<HTMLDivElement, HeroProps>(
|
|
52
|
+
(
|
|
53
|
+
{
|
|
54
|
+
className,
|
|
55
|
+
style,
|
|
56
|
+
eyebrow,
|
|
57
|
+
headline,
|
|
58
|
+
description,
|
|
59
|
+
actions,
|
|
60
|
+
media,
|
|
61
|
+
secondaryMedia,
|
|
62
|
+
layout: layoutProp,
|
|
63
|
+
stats,
|
|
64
|
+
brands,
|
|
65
|
+
badge,
|
|
66
|
+
brand,
|
|
67
|
+
overlay,
|
|
68
|
+
search,
|
|
69
|
+
badges,
|
|
70
|
+
theme,
|
|
71
|
+
padding = "none",
|
|
72
|
+
variant,
|
|
73
|
+
...props
|
|
74
|
+
},
|
|
75
|
+
ref
|
|
76
|
+
) => {
|
|
77
|
+
const reduce = useReducedMotion()
|
|
78
|
+
const layout = layoutProp ?? (media ? "split" : "centered")
|
|
79
|
+
|
|
80
|
+
// cinematic manages its own background: use ghost to avoid bg-card conflict
|
|
81
|
+
const cardVariant = variant ?? (layout === "cinematic" ? "ghost" : "default")
|
|
82
|
+
|
|
83
|
+
const mergedStyle: React.CSSProperties = {
|
|
84
|
+
...(theme === "dark" ? { background: "hsl(var(--neutral-950))" } : {}),
|
|
85
|
+
...style,
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const enter = reduce
|
|
89
|
+
? {}
|
|
90
|
+
: {
|
|
91
|
+
variants: variants.fadeUp,
|
|
92
|
+
initial: "hidden" as const,
|
|
93
|
+
animate: "visible" as const,
|
|
94
|
+
transition: transitions.slow,
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const cardRoot = (children: React.ReactNode) => (
|
|
98
|
+
<Card
|
|
99
|
+
ref={ref}
|
|
100
|
+
variant={cardVariant}
|
|
101
|
+
padding={padding}
|
|
102
|
+
style={mergedStyle}
|
|
103
|
+
className={cn("ax:relative ax:w-full", className)}
|
|
104
|
+
{...props}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</Card>
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
// ── Centered ─────────────────────────────────────────────────────────
|
|
111
|
+
if (layout === "centered") {
|
|
112
|
+
return cardRoot(
|
|
113
|
+
<div className="ax:px-xl ax:md:px-3xl ax:py-5xl ax:md:py-6xl ax:mx-auto ax:max-w-7xl">
|
|
114
|
+
<div className="ax:max-w-3xl ax:text-center ax:mx-auto">
|
|
115
|
+
<motion.div {...enter} className="ax:flex ax:flex-col ax:gap-lg ax:items-center">
|
|
116
|
+
{eyebrow && (
|
|
117
|
+
<span className="ax:inline-flex ax:items-center ax:self-start ax:t-overline ax:text-primary ax:px-2.5 ax:py-1 ax:rounded-full ax:bg-primary/10">
|
|
118
|
+
{eyebrow}
|
|
119
|
+
</span>
|
|
120
|
+
)}
|
|
121
|
+
<h1 className="ax:t-display-lg ax:md:t-display-xl ax:lg:t-display-2xl ax:text-foreground ax:[text-wrap:balance]">
|
|
122
|
+
{headline}
|
|
123
|
+
</h1>
|
|
124
|
+
{description && (
|
|
125
|
+
<p className="ax:t-body-md ax:md:t-body-lg ax:text-muted-foreground ax:[text-wrap:pretty] ax:max-w-prose ax:mx-auto">
|
|
126
|
+
{description}
|
|
127
|
+
</p>
|
|
128
|
+
)}
|
|
129
|
+
{actions && <div className="ax:flex ax:flex-wrap ax:gap-sm ax:pt-xs">{actions}</div>}
|
|
130
|
+
</motion.div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Split ─────────────────────────────────────────────────────────────
|
|
137
|
+
if (layout === "split") {
|
|
138
|
+
return cardRoot(
|
|
139
|
+
<div className="ax:grid ax:md:grid-cols-2 ax:min-h-[480px]">
|
|
140
|
+
<motion.div
|
|
141
|
+
{...enter}
|
|
142
|
+
className="ax:flex ax:flex-col ax:justify-center ax:gap-6 ax:px-10 ax:py-14 ax:bg-background"
|
|
143
|
+
>
|
|
144
|
+
{badge && <div className="ax:w-fit">{badge}</div>}
|
|
145
|
+
<div>
|
|
146
|
+
<h1 className="ax:t-display-lg ax:md:t-display-xl ax:text-foreground ax:leading-[1.1] ax:[text-wrap:balance]">
|
|
147
|
+
{headline}
|
|
148
|
+
</h1>
|
|
149
|
+
{description && (
|
|
150
|
+
<p className="ax:mt-4 ax:t-body-md ax:text-muted-foreground ax:max-w-sm ax:[text-wrap:pretty]">
|
|
151
|
+
{description}
|
|
152
|
+
</p>
|
|
153
|
+
)}
|
|
154
|
+
</div>
|
|
155
|
+
{actions && (
|
|
156
|
+
<div className="ax:flex ax:items-center ax:gap-3 ax:flex-wrap">{actions}</div>
|
|
157
|
+
)}
|
|
158
|
+
{stats && stats.length > 0 && (
|
|
159
|
+
<div className="ax:flex ax:items-center ax:gap-5 ax:pt-2 ax:flex-wrap">
|
|
160
|
+
{stats.map((stat, i) => (
|
|
161
|
+
<div key={i}>
|
|
162
|
+
<p className="ax:t-h3 ax:font-bold ax:text-foreground ax:tabular-nums">{stat.value}</p>
|
|
163
|
+
<p className="ax:t-caption ax:text-muted-foreground">{stat.label}</p>
|
|
164
|
+
</div>
|
|
165
|
+
))}
|
|
166
|
+
</div>
|
|
167
|
+
)}
|
|
168
|
+
</motion.div>
|
|
169
|
+
{media && (
|
|
170
|
+
<motion.div
|
|
171
|
+
{...(reduce
|
|
172
|
+
? {}
|
|
173
|
+
: {
|
|
174
|
+
variants: variants.scaleIn,
|
|
175
|
+
initial: "hidden" as const,
|
|
176
|
+
animate: "visible" as const,
|
|
177
|
+
transition: { ...transitions.slow, delay: 0.1 },
|
|
178
|
+
})}
|
|
179
|
+
className="ax:relative ax:min-h-[320px] ax:overflow-hidden"
|
|
180
|
+
>
|
|
181
|
+
{media}
|
|
182
|
+
{overlay}
|
|
183
|
+
</motion.div>
|
|
184
|
+
)}
|
|
185
|
+
</div>
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ── Capsule ───────────────────────────────────────────────────────────
|
|
190
|
+
if (layout === "capsule") {
|
|
191
|
+
return cardRoot(
|
|
192
|
+
<motion.div {...enter} className="ax:px-8 ax:pt-10 ax:pb-8 ax:md:px-12 ax:md:pt-12 ax:md:pb-10">
|
|
193
|
+
{/* Row 1 - Headline + description */}
|
|
194
|
+
<div className="ax:flex ax:items-start ax:justify-between ax:gap-8 ax:mb-10">
|
|
195
|
+
<h1 className="ax:t-display-lg ax:md:t-display-xl ax:font-semibold ax:leading-[1.06] ax:text-foreground ax:[text-wrap:balance]">
|
|
196
|
+
{headline}
|
|
197
|
+
</h1>
|
|
198
|
+
{description && (
|
|
199
|
+
<p className="ax:hidden ax:md:block ax:t-body-sm ax:text-muted-foreground ax:leading-relaxed ax:max-w-[260px] ax:shrink-0">
|
|
200
|
+
{description}
|
|
201
|
+
</p>
|
|
202
|
+
)}
|
|
203
|
+
</div>
|
|
204
|
+
{/* Row 2 - Capsule image + featured stat */}
|
|
205
|
+
{(media || stats?.[0]) && (
|
|
206
|
+
<div className="ax:flex ax:items-center ax:gap-8 ax:mb-8">
|
|
207
|
+
{media && (
|
|
208
|
+
<div className="ax:flex-1 ax:rounded-full ax:overflow-hidden ax:h-[200px] ax:md:h-[240px] ax:lg:h-[260px]">
|
|
209
|
+
{media}
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
{stats?.[0] && (
|
|
213
|
+
<div className="ax:hidden ax:md:block ax:shrink-0 ax:text-right">
|
|
214
|
+
<p className="ax:t-display-sm ax:lg:t-display-xl ax:font-bold ax:text-foreground ax:tabular-nums ax:leading-none">
|
|
215
|
+
{stats[0].value}
|
|
216
|
+
</p>
|
|
217
|
+
<p className="ax:t-body-sm ax:text-muted-foreground ax:mt-2">{stats[0].label}</p>
|
|
218
|
+
</div>
|
|
219
|
+
)}
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
{/* Row 3 - Bottom stats */}
|
|
223
|
+
{stats && stats.length > 1 && (
|
|
224
|
+
<div className="ax:flex ax:items-center ax:gap-10 ax:md:gap-16">
|
|
225
|
+
{stats.slice(1).map((stat, i) => (
|
|
226
|
+
<div key={i}>
|
|
227
|
+
<p className="ax:t-h1 ax:md:t-display-md ax:font-bold ax:text-foreground ax:tabular-nums">{stat.value}</p>
|
|
228
|
+
<p className="ax:t-caption ax:text-muted-foreground ax:mt-0.5">{stat.label}</p>
|
|
229
|
+
</div>
|
|
230
|
+
))}
|
|
231
|
+
</div>
|
|
232
|
+
)}
|
|
233
|
+
{actions && <div className="ax:flex ax:flex-wrap ax:gap-sm ax:mt-lg">{actions}</div>}
|
|
234
|
+
</motion.div>
|
|
235
|
+
)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ── Magazine ──────────────────────────────────────────────────────────
|
|
239
|
+
if (layout === "magazine") {
|
|
240
|
+
return cardRoot(
|
|
241
|
+
<motion.div {...enter} className="ax:p-8 ax:md:p-10 ax:lg:p-12">
|
|
242
|
+
{/* Row 1 - Headline + description */}
|
|
243
|
+
<div className="ax:flex ax:items-start ax:justify-between ax:gap-6 ax:mb-8 ax:md:mb-10">
|
|
244
|
+
<h1 className="ax:t-display-sm ax:md:t-display-xl ax:lg:t-display-2xl ax:leading-[0.92] ax:text-foreground ax:[text-wrap:balance]">
|
|
245
|
+
{headline}
|
|
246
|
+
</h1>
|
|
247
|
+
{description && (
|
|
248
|
+
<p className="ax:hidden ax:md:block ax:t-body-sm ax:text-muted-foreground ax:leading-relaxed ax:max-w-[190px] ax:shrink-0 ax:pt-1">
|
|
249
|
+
{description}
|
|
250
|
+
</p>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
{/* Row 2 - Main image + right column */}
|
|
254
|
+
{(media || secondaryMedia || stats?.[0]) && (
|
|
255
|
+
<div className="ax:flex ax:gap-3 ax:items-stretch">
|
|
256
|
+
{media && (
|
|
257
|
+
<div className="ax:relative ax:flex-1 ax:rounded-xl ax:overflow-hidden ax:min-h-[260px] ax:md:min-h-[340px] ax:lg:min-h-[380px]">
|
|
258
|
+
{media}
|
|
259
|
+
</div>
|
|
260
|
+
)}
|
|
261
|
+
{(stats?.[0] || secondaryMedia) && (
|
|
262
|
+
<div className="ax:hidden ax:md:flex ax:flex-col ax:gap-3 ax:shrink-0 ax:w-36 ax:lg:w-44">
|
|
263
|
+
{stats?.[0] && (
|
|
264
|
+
<div className="ax:rounded-xl ax:bg-muted ax:px-4 ax:py-5">
|
|
265
|
+
<p className="ax:t-display-md ax:lg:t-display-lg ax:font-black ax:text-foreground ax:tabular-nums ax:leading-none">
|
|
266
|
+
{stats[0].value}
|
|
267
|
+
</p>
|
|
268
|
+
<p className="ax:t-overline ax:text-muted-foreground ax:mt-2">{stats[0].label}</p>
|
|
269
|
+
</div>
|
|
270
|
+
)}
|
|
271
|
+
{secondaryMedia && (
|
|
272
|
+
<div className="ax:relative ax:flex-1 ax:rounded-xl ax:overflow-hidden">{secondaryMedia}</div>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
)}
|
|
278
|
+
{/* Row 3 - Footer strip */}
|
|
279
|
+
{(eyebrow || brands || stats?.[1]) && (
|
|
280
|
+
<div className="ax:flex ax:items-center ax:justify-between ax:gap-4 ax:mt-6 ax:pt-5 ax:border-t ax:border-border">
|
|
281
|
+
{eyebrow && (
|
|
282
|
+
<p className="ax:t-overline ax:font-black ax:text-muted-foreground ax:leading-snug ax:shrink-0">
|
|
283
|
+
{eyebrow}
|
|
284
|
+
</p>
|
|
285
|
+
)}
|
|
286
|
+
{brands && brands.length > 0 && (
|
|
287
|
+
<div className="ax:hidden ax:sm:flex ax:items-center ax:gap-5 ax:t-label-sm ax:text-muted-foreground/50">
|
|
288
|
+
{brands.map((b, i) => (
|
|
289
|
+
<span key={i}>{b}</span>
|
|
290
|
+
))}
|
|
291
|
+
</div>
|
|
292
|
+
)}
|
|
293
|
+
{stats?.[1] && (
|
|
294
|
+
<div className="ax:shrink-0 ax:text-right">
|
|
295
|
+
<p className="ax:t-h1 ax:lg:t-display-md ax:font-black ax:text-foreground ax:tabular-nums ax:leading-none">
|
|
296
|
+
{stats[1].value}
|
|
297
|
+
</p>
|
|
298
|
+
<p className="ax:t-overline ax:text-muted-foreground ax:mt-1">{stats[1].label}</p>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
301
|
+
</div>
|
|
302
|
+
)}
|
|
303
|
+
</motion.div>
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// ── Editorial ─────────────────────────────────────────────────────────
|
|
308
|
+
if (layout === "editorial") {
|
|
309
|
+
return cardRoot(
|
|
310
|
+
<>
|
|
311
|
+
{/* Top accent rule */}
|
|
312
|
+
<div className="ax:h-1 ax:bg-primary" />
|
|
313
|
+
<div className="ax:px-10 ax:py-14">
|
|
314
|
+
{/* Eyebrow row - dateline + issue badge */}
|
|
315
|
+
<div className="ax:flex ax:items-center ax:justify-between ax:mb-8">
|
|
316
|
+
<div className="ax:flex ax:items-center ax:gap-3">
|
|
317
|
+
<span className="ax:h-px ax:w-8 ax:inline-block ax:bg-primary" />
|
|
318
|
+
<p className="ax:t-code ax:uppercase ax:tracking-[0.25em] ax:text-muted-foreground">{eyebrow}</p>
|
|
319
|
+
</div>
|
|
320
|
+
{badge}
|
|
321
|
+
</div>
|
|
322
|
+
<motion.div {...enter}>
|
|
323
|
+
{/* Oversized headline */}
|
|
324
|
+
<h1 className="ax:t-display-sm ax:md:t-display-2xl ax:lg:t-display-3xl ax:text-foreground ax:[text-wrap:balance]">
|
|
325
|
+
{headline}
|
|
326
|
+
</h1>
|
|
327
|
+
{/* Below-headline grid - description left, stats + actions right */}
|
|
328
|
+
<div className="ax:mt-8 ax:pt-8 ax:border-t ax:border-border ax:grid ax:md:grid-cols-[2fr_1fr] ax:gap-8">
|
|
329
|
+
{description && (
|
|
330
|
+
<p className="ax:t-body-md ax:text-muted-foreground ax:leading-relaxed ax:max-w-prose">
|
|
331
|
+
{description}
|
|
332
|
+
</p>
|
|
333
|
+
)}
|
|
334
|
+
<div className="ax:flex ax:flex-col ax:gap-4 ax:justify-between">
|
|
335
|
+
{stats && stats.length > 0 && (
|
|
336
|
+
<div className="ax:grid ax:grid-cols-2 ax:gap-4">
|
|
337
|
+
{stats.map((stat, i) => (
|
|
338
|
+
<div key={i} className="ax:border-l-2 ax:border-primary ax:pl-3">
|
|
339
|
+
<p className="ax:t-h2">{stat.value}</p>
|
|
340
|
+
<p className="ax:t-caption ax:text-muted-foreground ax:mt-0.5">{stat.label}</p>
|
|
341
|
+
</div>
|
|
342
|
+
))}
|
|
343
|
+
</div>
|
|
344
|
+
)}
|
|
345
|
+
{actions && <div className="ax:flex ax:gap-2 ax:mt-auto">{actions}</div>}
|
|
346
|
+
</div>
|
|
347
|
+
</div>
|
|
348
|
+
</motion.div>
|
|
349
|
+
</div>
|
|
350
|
+
{/* Bottom rule */}
|
|
351
|
+
<div className="ax:h-px ax:bg-border" />
|
|
352
|
+
</>
|
|
353
|
+
)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// ── Mosaic ────────────────────────────────────────────────────────────
|
|
357
|
+
if (layout === "mosaic") {
|
|
358
|
+
return cardRoot(
|
|
359
|
+
<div className="ax:grid ax:md:grid-cols-[1fr_1.2fr] ax:min-h-[480px]">
|
|
360
|
+
<motion.div {...enter} className="ax:flex ax:flex-col ax:justify-center ax:gap-6 ax:px-10 ax:py-14">
|
|
361
|
+
{eyebrow && <p className="ax:t-overline ax:text-primary">{eyebrow}</p>}
|
|
362
|
+
<h1 className="ax:t-display-lg ax:md:t-display-xl ax:text-foreground ax:leading-[1.1] ax:[text-wrap:balance]">
|
|
363
|
+
{headline}
|
|
364
|
+
</h1>
|
|
365
|
+
{description && (
|
|
366
|
+
<p className="ax:t-body-sm ax:text-muted-foreground ax:leading-relaxed ax:max-w-xs">{description}</p>
|
|
367
|
+
)}
|
|
368
|
+
{actions && <div className="ax:flex ax:flex-col ax:gap-3">{actions}</div>}
|
|
369
|
+
</motion.div>
|
|
370
|
+
{media && (
|
|
371
|
+
<motion.div
|
|
372
|
+
{...(reduce
|
|
373
|
+
? {}
|
|
374
|
+
: {
|
|
375
|
+
variants: variants.scaleIn,
|
|
376
|
+
initial: "hidden" as const,
|
|
377
|
+
animate: "visible" as const,
|
|
378
|
+
transition: { ...transitions.slow, delay: 0.1 },
|
|
379
|
+
})}
|
|
380
|
+
className="ax:p-4"
|
|
381
|
+
>
|
|
382
|
+
{media}
|
|
383
|
+
</motion.div>
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// ── Search-first ──────────────────────────────────────────────────────
|
|
390
|
+
if (layout === "search") {
|
|
391
|
+
return cardRoot(
|
|
392
|
+
<div className="ax:px-6 ax:py-16 ax:text-center">
|
|
393
|
+
<motion.div {...enter} className="ax:flex ax:flex-col ax:items-center ax:gap-4">
|
|
394
|
+
{eyebrow && <p className="ax:t-overline ax:text-primary">{eyebrow}</p>}
|
|
395
|
+
<h1 className="ax:t-display-lg ax:md:t-display-xl ax:text-foreground ax:leading-tight ax:[text-wrap:balance]">
|
|
396
|
+
{headline}
|
|
397
|
+
</h1>
|
|
398
|
+
{description && (
|
|
399
|
+
<p className="ax:t-body-md ax:text-muted-foreground ax:max-w-lg ax:mx-auto ax:[text-wrap:pretty]">
|
|
400
|
+
{description}
|
|
401
|
+
</p>
|
|
402
|
+
)}
|
|
403
|
+
{search && <div className="ax:mt-6 ax:w-full ax:max-w-2xl ax:mx-auto">{search}</div>}
|
|
404
|
+
{badges && badges.length > 0 && (
|
|
405
|
+
<div className="ax:mt-4 ax:flex ax:items-center ax:justify-center ax:gap-6 ax:flex-wrap">
|
|
406
|
+
{badges.map((b, i) => (
|
|
407
|
+
<div key={i} className="ax:flex ax:items-center ax:gap-1.5 ax:t-caption ax:text-muted-foreground">
|
|
408
|
+
<Check className="ax:size-3.5 ax:text-primary ax:shrink-0" />
|
|
409
|
+
{b}
|
|
410
|
+
</div>
|
|
411
|
+
))}
|
|
412
|
+
</div>
|
|
413
|
+
)}
|
|
414
|
+
</motion.div>
|
|
415
|
+
</div>
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// ── Cinematic ─────────────────────────────────────────────────────────
|
|
420
|
+
const isDark = theme === "dark"
|
|
421
|
+
const col = {
|
|
422
|
+
text: isDark ? "hsl(var(--neutral-50))" : undefined,
|
|
423
|
+
muted: isDark ? "hsl(var(--neutral-400))" : undefined,
|
|
424
|
+
dim: isDark ? "hsl(var(--neutral-500))" : undefined,
|
|
425
|
+
bg: isDark ? "hsl(var(--neutral-900))" : undefined,
|
|
426
|
+
border: isDark ? "hsl(var(--neutral-800))" : undefined,
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return cardRoot(
|
|
430
|
+
<div className="ax:relative ax:min-h-[520px] ax:flex ax:flex-col ax:justify-between ax:px-10 ax:py-14 ax:overflow-hidden">
|
|
431
|
+
{/* Atmospheric glow decorations */}
|
|
432
|
+
<div
|
|
433
|
+
className="ax:absolute ax:-top-32 ax:left-1/2 ax:-translate-x-1/2 ax:w-[600px] ax:h-[400px] ax:rounded-full ax:opacity-20 ax:blur-3xl ax:pointer-events-none"
|
|
434
|
+
style={{ background: "hsl(var(--primary))" }}
|
|
435
|
+
/>
|
|
436
|
+
<div
|
|
437
|
+
className="ax:absolute ax:-bottom-20 ax:right-0 ax:w-72 ax:h-72 ax:rounded-full ax:opacity-10 ax:blur-3xl ax:pointer-events-none"
|
|
438
|
+
style={{ background: "hsl(var(--accent))" }}
|
|
439
|
+
/>
|
|
440
|
+
{/* Top bar - brand identity + live badge */}
|
|
441
|
+
{(brand || badge) && (
|
|
442
|
+
<div className="ax:relative ax:flex ax:items-center ax:justify-between">{brand}{badge}</div>
|
|
443
|
+
)}
|
|
444
|
+
{/* Center - eyebrow + headline + description + actions */}
|
|
445
|
+
<motion.div {...enter} className="ax:relative ax:text-center ax:py-8">
|
|
446
|
+
{eyebrow && (
|
|
447
|
+
<p
|
|
448
|
+
className="ax:t-code ax:uppercase ax:tracking-[0.3em] ax:mb-6"
|
|
449
|
+
style={{ color: col.dim }}
|
|
450
|
+
>
|
|
451
|
+
{eyebrow}
|
|
452
|
+
</p>
|
|
453
|
+
)}
|
|
454
|
+
<h1
|
|
455
|
+
className="ax:t-display-xl ax:md:t-display-3xl ax:[text-wrap:balance]"
|
|
456
|
+
style={{ color: col.text }}
|
|
457
|
+
>
|
|
458
|
+
{headline}
|
|
459
|
+
</h1>
|
|
460
|
+
{description && (
|
|
461
|
+
<p className="ax:mt-6 ax:t-body-sm ax:max-w-md ax:mx-auto" style={{ color: col.muted }}>
|
|
462
|
+
{description}
|
|
463
|
+
</p>
|
|
464
|
+
)}
|
|
465
|
+
{actions && (
|
|
466
|
+
<div className="ax:mt-8 ax:flex ax:items-center ax:justify-center ax:gap-3">{actions}</div>
|
|
467
|
+
)}
|
|
468
|
+
</motion.div>
|
|
469
|
+
{/* Bottom stats grid */}
|
|
470
|
+
{stats && stats.length > 0 && (
|
|
471
|
+
<div
|
|
472
|
+
className="ax:relative ax:grid ax:gap-px ax:rounded-lg ax:overflow-hidden"
|
|
473
|
+
style={{
|
|
474
|
+
gridTemplateColumns: `repeat(${stats.length}, 1fr)`,
|
|
475
|
+
border: col.border ? `1px solid ${col.border}` : undefined,
|
|
476
|
+
}}
|
|
477
|
+
>
|
|
478
|
+
{stats.map((stat, i) => (
|
|
479
|
+
<div
|
|
480
|
+
key={i}
|
|
481
|
+
className="ax:flex ax:flex-col ax:items-center ax:gap-1 ax:px-4 ax:py-3"
|
|
482
|
+
style={{ background: col.bg }}
|
|
483
|
+
>
|
|
484
|
+
<p className="ax:t-h5 ax:font-bold ax:tabular-nums" style={{ color: col.text }}>
|
|
485
|
+
{stat.value}
|
|
486
|
+
</p>
|
|
487
|
+
<p className="ax:text-[10px]" style={{ color: col.dim }}>
|
|
488
|
+
{stat.label}
|
|
489
|
+
</p>
|
|
490
|
+
</div>
|
|
491
|
+
))}
|
|
492
|
+
</div>
|
|
493
|
+
)}
|
|
494
|
+
</div>
|
|
495
|
+
)
|
|
496
|
+
}
|
|
497
|
+
)
|
|
498
|
+
Hero.displayName = "Hero"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { InputButton } from "./input-button"
|
|
4
|
+
|
|
5
|
+
const meta: Meta<typeof InputButton> = {
|
|
6
|
+
title: "Components/InputButton",
|
|
7
|
+
component: InputButton,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
args: {
|
|
10
|
+
placeholder: "Enter your email",
|
|
11
|
+
buttonLabel: "Subscribe",
|
|
12
|
+
onButtonClick: () => {},
|
|
13
|
+
},
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default meta
|
|
17
|
+
type Story = StoryObj<typeof InputButton>
|
|
18
|
+
|
|
19
|
+
export const Default: Story = {}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import { Button, type ButtonProps } from "./button"
|
|
5
|
+
import { Input, type InputProps, type InputSize } from "./input"
|
|
6
|
+
import { cn } from "../lib/cn"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* InputButton - a text Input + Button compactly joined into a single inline group.
|
|
10
|
+
*
|
|
11
|
+
* The sibling of SelectButton: where that pairs a Select with a commit action,
|
|
12
|
+
* this pairs a free-text Input with one (search, submit, apply). Both halves
|
|
13
|
+
* share the same height ladder (sm/md/lg align with Button, Input, Select, and
|
|
14
|
+
* Dropdown) so they line up when placed side-by-side, and they overlap by 1px so
|
|
15
|
+
* the seam reads as a single squared pill.
|
|
16
|
+
*
|
|
17
|
+
* <InputButton
|
|
18
|
+
* size="md"
|
|
19
|
+
* placeholder="e.g. AUH-100482"
|
|
20
|
+
* value={value}
|
|
21
|
+
* onChange={(e) => setValue(e.target.value)}
|
|
22
|
+
* buttonType="submit"
|
|
23
|
+
* buttonLabel="Search"
|
|
24
|
+
* />
|
|
25
|
+
*
|
|
26
|
+
* All standard input attributes (value, onChange, placeholder, disabled, name,
|
|
27
|
+
* aria-*, …) are forwarded to the inner Input. `disabled` disables BOTH halves;
|
|
28
|
+
* use `buttonDisabled` to disable only the button.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export type InputButtonSize = InputSize
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Variant whitelist for the right-hand action button - mirrors SelectButton.
|
|
35
|
+
* Excludes Button variants that don't fit the joined-pill composite:
|
|
36
|
+
* ghost - transparent background reads as "no button"
|
|
37
|
+
* accent - wrong semantics for a commit action
|
|
38
|
+
* destructive - wrong semantics for a search/submit toolbar
|
|
39
|
+
* link - underline-on-hover text breaks the joined-pill shape
|
|
40
|
+
*/
|
|
41
|
+
export type InputButtonVariant = Exclude<NonNullable<ButtonProps["variant"]>, "ghost" | "accent" | "destructive">
|
|
42
|
+
|
|
43
|
+
export interface InputButtonProps extends Omit<InputProps, "size"> {
|
|
44
|
+
/** Shared height/typography ladder - matches Button, Input, Select. */
|
|
45
|
+
size?: InputButtonSize
|
|
46
|
+
/** Visual treatment of the right-hand action button. */
|
|
47
|
+
variant?: InputButtonVariant
|
|
48
|
+
|
|
49
|
+
// ─── Button half ──────────────────────────────────────────────────────────────
|
|
50
|
+
buttonLabel: React.ReactNode
|
|
51
|
+
onButtonClick?: React.MouseEventHandler<HTMLButtonElement>
|
|
52
|
+
buttonDisabled?: boolean
|
|
53
|
+
buttonType?: "button" | "submit" | "reset"
|
|
54
|
+
/** Extra className appended to the inner Button. */
|
|
55
|
+
buttonClassName?: string
|
|
56
|
+
|
|
57
|
+
/** Extra className appended to the inner Input. */
|
|
58
|
+
inputClassName?: string
|
|
59
|
+
|
|
60
|
+
/** className for the wrapping group (the input fills the remaining width). */
|
|
61
|
+
className?: string
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const InputButton = React.forwardRef<HTMLInputElement, InputButtonProps>(
|
|
65
|
+
(
|
|
66
|
+
{
|
|
67
|
+
size = "md",
|
|
68
|
+
variant = "primary",
|
|
69
|
+
buttonLabel,
|
|
70
|
+
onButtonClick,
|
|
71
|
+
buttonDisabled,
|
|
72
|
+
buttonType = "button",
|
|
73
|
+
buttonClassName,
|
|
74
|
+
inputClassName,
|
|
75
|
+
className,
|
|
76
|
+
disabled,
|
|
77
|
+
...inputProps
|
|
78
|
+
},
|
|
79
|
+
ref
|
|
80
|
+
) => (
|
|
81
|
+
<div data-slot="input-button" className={cn("ax:isolate ax:flex ax:w-full ax:items-stretch", className)}>
|
|
82
|
+
<Input
|
|
83
|
+
ref={ref}
|
|
84
|
+
size={size}
|
|
85
|
+
disabled={disabled}
|
|
86
|
+
className={cn(
|
|
87
|
+
// Join: square the right corners, overlap the button by 1px, and lift
|
|
88
|
+
// on focus so the focus ring sits above the adjoining button.
|
|
89
|
+
"ax:relative ax:min-w-0 ax:flex-1 ax:-mr-px ax:rounded-r-none ax:focus-visible:z-10",
|
|
90
|
+
inputClassName
|
|
91
|
+
)}
|
|
92
|
+
{...inputProps}
|
|
93
|
+
/>
|
|
94
|
+
<Button
|
|
95
|
+
type={buttonType}
|
|
96
|
+
size={size}
|
|
97
|
+
variant={variant}
|
|
98
|
+
onClick={onButtonClick}
|
|
99
|
+
disabled={buttonDisabled || disabled}
|
|
100
|
+
className={cn(
|
|
101
|
+
// Use --input-radius (not --radius) on the right corner so the
|
|
102
|
+
// composite stays squared in pill brands, matches SelectButton.
|
|
103
|
+
"ax:shrink-0 ax:rounded-l-none ax:rounded-r-[var(--input-radius)] ax:focus-visible:z-10",
|
|
104
|
+
buttonClassName
|
|
105
|
+
)}
|
|
106
|
+
>
|
|
107
|
+
{buttonLabel}
|
|
108
|
+
</Button>
|
|
109
|
+
</div>
|
|
110
|
+
)
|
|
111
|
+
)
|
|
112
|
+
InputButton.displayName = "InputButton"
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react"
|
|
3
|
+
import { InputGroup } from "./input-group"
|
|
4
|
+
import { SelectItem } from "./select"
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof InputGroup> = {
|
|
7
|
+
title: "Components/InputGroup",
|
|
8
|
+
component: InputGroup,
|
|
9
|
+
tags: ["autodocs"],
|
|
10
|
+
args: {
|
|
11
|
+
selectDefaultValue: "mr",
|
|
12
|
+
selectPlaceholder: "Title",
|
|
13
|
+
placeholder: "Full name",
|
|
14
|
+
children: (
|
|
15
|
+
<>
|
|
16
|
+
<SelectItem value="mr">Mr.</SelectItem>
|
|
17
|
+
<SelectItem value="ms">Ms.</SelectItem>
|
|
18
|
+
<SelectItem value="dr">Dr.</SelectItem>
|
|
19
|
+
<SelectItem value="prof">Prof.</SelectItem>
|
|
20
|
+
</>
|
|
21
|
+
),
|
|
22
|
+
},
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default meta
|
|
26
|
+
type Story = StoryObj<typeof InputGroup>
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {}
|
|
29
|
+
|
|
30
|
+
export const Sizes: Story = {
|
|
31
|
+
render: (args) => (
|
|
32
|
+
<div className="ax:flex ax:flex-col ax:gap-md ax:max-w-sm">
|
|
33
|
+
<InputGroup {...args} size="sm" />
|
|
34
|
+
<InputGroup {...args} size="md" />
|
|
35
|
+
<InputGroup {...args} size="lg" />
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
}
|