rechta-ds 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/.changeset/config.json +11 -0
  2. package/.github/workflows/release.yml +53 -0
  3. package/.github/workflows/storybook.yml +34 -0
  4. package/.storybook/main.ts +17 -0
  5. package/.storybook/preview.ts +35 -0
  6. package/CHANGELOG.md +65 -0
  7. package/CONTRIBUTING.md +106 -0
  8. package/README.md +206 -0
  9. package/package.json +30 -0
  10. package/packages/tokens/build.js +357 -0
  11. package/packages/tokens/package.json +44 -0
  12. package/packages/tokens/src/tokens.json +1538 -0
  13. package/packages/ui/.storybook/main.ts +17 -0
  14. package/packages/ui/.storybook/preview.tsx +37 -0
  15. package/packages/ui/package.json +109 -0
  16. package/packages/ui/postcss.config.js +6 -0
  17. package/packages/ui/src/components/atoms/Avatar.tsx +139 -0
  18. package/packages/ui/src/components/atoms/Badge.tsx +62 -0
  19. package/packages/ui/src/components/atoms/Button.tsx +125 -0
  20. package/packages/ui/src/components/atoms/Input.tsx +116 -0
  21. package/packages/ui/src/components/atoms/Misc.tsx +128 -0
  22. package/packages/ui/src/components/atoms/Toggle.tsx +191 -0
  23. package/packages/ui/src/components/atoms/Typography.tsx +178 -0
  24. package/packages/ui/src/components/atoms/index.ts +7 -0
  25. package/packages/ui/src/components/charts/Charts.tsx +380 -0
  26. package/packages/ui/src/components/charts/DataTable.tsx +222 -0
  27. package/packages/ui/src/components/charts/index.ts +19 -0
  28. package/packages/ui/src/components/molecules/Accordion.tsx +93 -0
  29. package/packages/ui/src/components/molecules/Card.tsx +100 -0
  30. package/packages/ui/src/components/molecules/PricingCard.tsx +196 -0
  31. package/packages/ui/src/components/molecules/TestimonialCard.tsx +85 -0
  32. package/packages/ui/src/components/molecules/Tooltip.tsx +71 -0
  33. package/packages/ui/src/components/molecules/index.ts +5 -0
  34. package/packages/ui/src/components/organisms/FeatureTabs.tsx +196 -0
  35. package/packages/ui/src/components/organisms/LogoMarquee.tsx +119 -0
  36. package/packages/ui/src/components/organisms/Navbar.tsx +194 -0
  37. package/packages/ui/src/components/organisms/index.ts +3 -0
  38. package/packages/ui/src/index.ts +15 -0
  39. package/packages/ui/src/lib/utils.ts +12 -0
  40. package/packages/ui/src/stories/atoms/Avatar.stories.tsx +49 -0
  41. package/packages/ui/src/stories/atoms/Badge.stories.tsx +68 -0
  42. package/packages/ui/src/stories/atoms/Button.stories.tsx +98 -0
  43. package/packages/ui/src/stories/atoms/Input.stories.tsx +66 -0
  44. package/packages/ui/src/stories/atoms/Toggle.stories.tsx +36 -0
  45. package/packages/ui/src/stories/molecules/Accordion.stories.tsx +47 -0
  46. package/packages/ui/src/stories/molecules/Card.stories.tsx +84 -0
  47. package/packages/ui/src/stories/molecules/PricingCard.stories.tsx +62 -0
  48. package/packages/ui/src/stories/molecules/TestimonialCard.stories.tsx +52 -0
  49. package/packages/ui/src/stories/molecules/Tooltip.stories.tsx +66 -0
  50. package/packages/ui/src/stories/organisms/LogoMarquee.stories.tsx +33 -0
  51. package/packages/ui/src/stories/organisms/Navbar.stories.tsx +37 -0
  52. package/packages/ui/src/styles/globals.css +220 -0
  53. package/packages/ui/tailwind.config.ts +68 -0
  54. package/packages/ui/tsconfig.json +23 -0
  55. package/packages/ui/tsup.config.ts +24 -0
  56. package/packages/ui/vite.config.ts +17 -0
  57. package/pnpm-workspace.yaml +2 -0
  58. package/turbo.json +33 -0
@@ -0,0 +1,196 @@
1
+ import * as React from 'react';
2
+ import * as TabsPrimitive from '@radix-ui/react-tabs';
3
+ import { cn } from '../../lib/utils';
4
+ import { Button } from '../atoms/Button';
5
+ import { ArrowRight } from 'lucide-react';
6
+
7
+ // ─── Tabs primitives ──────────────────────────────────────────────────────────
8
+ const TabsRoot = TabsPrimitive.Root;
9
+
10
+ const TabsList = React.forwardRef<
11
+ React.ElementRef<typeof TabsPrimitive.List>,
12
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
13
+ >(({ className, ...props }, ref) => (
14
+ <TabsPrimitive.List
15
+ ref={ref}
16
+ className={cn(
17
+ 'inline-flex items-center gap-1 rounded-xl bg-surface border border-border p-1',
18
+ className
19
+ )}
20
+ {...props}
21
+ />
22
+ ));
23
+ TabsList.displayName = 'TabsList';
24
+
25
+ const TabsTrigger = React.forwardRef<
26
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
27
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
28
+ >(({ className, ...props }, ref) => (
29
+ <TabsPrimitive.Trigger
30
+ ref={ref}
31
+ className={cn(
32
+ 'inline-flex items-center justify-center whitespace-nowrap rounded-lg',
33
+ 'px-4 py-2 text-sm font-medium font-body',
34
+ 'text-text-tertiary transition-all duration-200',
35
+ 'hover:text-text-secondary',
36
+ 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-500',
37
+ 'disabled:pointer-events-none disabled:opacity-50',
38
+ 'data-[state=active]:bg-surface-raised data-[state=active]:text-text-primary data-[state=active]:shadow-sm',
39
+ 'data-[state=active]:border data-[state=active]:border-border',
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ));
45
+ TabsTrigger.displayName = 'TabsTrigger';
46
+
47
+ const TabsContent = React.forwardRef<
48
+ React.ElementRef<typeof TabsPrimitive.Content>,
49
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
50
+ >(({ className, ...props }, ref) => (
51
+ <TabsPrimitive.Content
52
+ ref={ref}
53
+ className={cn(
54
+ 'focus-visible:outline-none',
55
+ 'data-[state=active]:animate-fade-up',
56
+ className
57
+ )}
58
+ {...props}
59
+ />
60
+ ));
61
+ TabsContent.displayName = 'TabsContent';
62
+
63
+ // ─── Feature Tabs organism ────────────────────────────────────────────────────
64
+ export interface FeatureTab {
65
+ id: string;
66
+ label: string;
67
+ overline: string;
68
+ heading: string;
69
+ description: string;
70
+ ctaLabel: string;
71
+ ctaHref?: string;
72
+ onCtaClick?: () => void;
73
+ media?: string; // video URL or GIF
74
+ mediaType?: 'video' | 'image';
75
+ mediaPoster?: string;
76
+ }
77
+
78
+ export interface FeatureTabsProps {
79
+ tabs: FeatureTab[];
80
+ eyebrow?: string;
81
+ heading?: string;
82
+ productTourLabel?: string;
83
+ onProductTourClick?: () => void;
84
+ className?: string;
85
+ }
86
+
87
+ const FeatureTabs: React.FC<FeatureTabsProps> = ({
88
+ tabs,
89
+ eyebrow,
90
+ heading,
91
+ productTourLabel = 'Take product tour',
92
+ onProductTourClick,
93
+ className,
94
+ }) => {
95
+ const [active, setActive] = React.useState(tabs[0]?.id ?? '');
96
+ const activeTab = tabs.find((t) => t.id === active) ?? tabs[0];
97
+
98
+ return (
99
+ <section className={cn('w-full', className)}>
100
+ {/* Section header */}
101
+ {(eyebrow || heading) && (
102
+ <div className="text-center mb-12 max-w-2xl mx-auto">
103
+ {eyebrow && (
104
+ <p className="text-xs font-mono text-text-tertiary tracking-widest uppercase mb-3">{eyebrow}</p>
105
+ )}
106
+ {heading && (
107
+ <h2 className="font-display text-4xl font-bold text-text-primary tracking-tight">{heading}</h2>
108
+ )}
109
+ </div>
110
+ )}
111
+
112
+ <TabsRoot value={active} onValueChange={setActive}>
113
+ {/* Tab list */}
114
+ <div className="flex justify-center mb-10">
115
+ <TabsList>
116
+ {tabs.map((tab) => (
117
+ <TabsTrigger key={tab.id} value={tab.id}>
118
+ {tab.label}
119
+ </TabsTrigger>
120
+ ))}
121
+ </TabsList>
122
+ </div>
123
+
124
+ {/* Tab panels */}
125
+ {tabs.map((tab) => (
126
+ <TabsContent key={tab.id} value={tab.id}>
127
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-12 items-center">
128
+ {/* Text side */}
129
+ <div className="flex flex-col gap-6">
130
+ <div>
131
+ <p className="text-xs font-mono text-brand-400 tracking-widest uppercase mb-3">
132
+ {tab.overline}
133
+ </p>
134
+ <h3 className="font-display text-3xl font-bold text-text-primary tracking-tight mb-4 leading-tight">
135
+ {tab.heading}
136
+ </h3>
137
+ <p className="text-text-secondary leading-relaxed">
138
+ {tab.description}
139
+ </p>
140
+ </div>
141
+ <Button
142
+ variant="ghost"
143
+ size="md"
144
+ rightIcon={<ArrowRight className="size-4" />}
145
+ onClick={tab.onCtaClick}
146
+ className="self-start"
147
+ >
148
+ {tab.ctaLabel}
149
+ </Button>
150
+ </div>
151
+
152
+ {/* Media side */}
153
+ {tab.media && (
154
+ <div className="relative rounded-2xl overflow-hidden border border-border bg-surface-sunken aspect-square">
155
+ {tab.mediaType === 'video' ? (
156
+ <video
157
+ src={tab.media}
158
+ poster={tab.mediaPoster}
159
+ autoPlay
160
+ loop
161
+ muted
162
+ playsInline
163
+ className="w-full h-full object-cover"
164
+ />
165
+ ) : (
166
+ <img
167
+ src={tab.media}
168
+ alt={tab.heading}
169
+ className="w-full h-full object-cover"
170
+ />
171
+ )}
172
+ </div>
173
+ )}
174
+ </div>
175
+ </TabsContent>
176
+ ))}
177
+ </TabsRoot>
178
+
179
+ {/* Product tour CTA */}
180
+ {productTourLabel && (
181
+ <div className="text-center mt-10">
182
+ <Button
183
+ variant="outline"
184
+ size="sm"
185
+ rightIcon={<ArrowRight className="size-3.5" />}
186
+ onClick={onProductTourClick}
187
+ >
188
+ {productTourLabel}
189
+ </Button>
190
+ </div>
191
+ )}
192
+ </section>
193
+ );
194
+ };
195
+
196
+ export { TabsRoot, TabsList, TabsTrigger, TabsContent, FeatureTabs };
@@ -0,0 +1,119 @@
1
+ import * as React from 'react';
2
+ import { cn } from '../../lib/utils';
3
+
4
+ export interface LogoItem {
5
+ src: string;
6
+ alt: string;
7
+ name?: string;
8
+ }
9
+
10
+ export interface LogoMarqueeProps {
11
+ logos: LogoItem[];
12
+ title?: string;
13
+ rows?: 1 | 2;
14
+ speed?: 'slow' | 'normal' | 'fast';
15
+ direction?: 'left' | 'right';
16
+ pauseOnHover?: boolean;
17
+ className?: string;
18
+ }
19
+
20
+ const speedMap = {
21
+ slow: 'animate-[marquee_50s_linear_infinite]',
22
+ normal: 'animate-[marquee_30s_linear_infinite]',
23
+ fast: 'animate-[marquee_15s_linear_infinite]',
24
+ };
25
+
26
+ const LogoTile: React.FC<{ logo: LogoItem }> = ({ logo }) => (
27
+ <div className="flex items-center justify-center px-6 shrink-0">
28
+ <div className="flex flex-col items-center gap-2">
29
+ <div className="h-10 w-24 flex items-center justify-center">
30
+ <img
31
+ src={logo.src}
32
+ alt={logo.alt}
33
+ className="max-h-8 max-w-[80px] object-contain opacity-40 hover:opacity-70 transition-opacity duration-300 filter brightness-200"
34
+ />
35
+ </div>
36
+ {logo.name && (
37
+ <span className="text-[10px] font-mono text-text-disabled tracking-wider uppercase whitespace-nowrap">
38
+ {logo.name}
39
+ </span>
40
+ )}
41
+ </div>
42
+ </div>
43
+ );
44
+
45
+ const MarqueeRow: React.FC<{
46
+ logos: LogoItem[];
47
+ speed: keyof typeof speedMap;
48
+ reverse?: boolean;
49
+ pauseOnHover?: boolean;
50
+ }> = ({ logos, speed, reverse, pauseOnHover }) => {
51
+ // Duplicate for seamless loop
52
+ const duplicated = [...logos, ...logos, ...logos, ...logos];
53
+
54
+ return (
55
+ <div className={cn('flex overflow-hidden', pauseOnHover && 'hover:[&>div]:pause')}>
56
+ <div
57
+ className={cn(
58
+ 'flex shrink-0',
59
+ speedMap[speed],
60
+ reverse && '[animation-direction:reverse]'
61
+ )}
62
+ style={{ animationPlayState: 'running' }}
63
+ >
64
+ {duplicated.map((logo, i) => (
65
+ <LogoTile key={i} logo={logo} />
66
+ ))}
67
+ </div>
68
+ </div>
69
+ );
70
+ };
71
+
72
+ const LogoMarquee: React.FC<LogoMarqueeProps> = ({
73
+ logos,
74
+ title,
75
+ rows = 1,
76
+ speed = 'normal',
77
+ direction = 'left',
78
+ pauseOnHover = true,
79
+ className,
80
+ }) => {
81
+ const half = Math.ceil(logos.length / 2);
82
+ const row1 = logos.slice(0, half);
83
+ const row2 = logos.slice(half);
84
+
85
+ return (
86
+ <div className={cn('w-full overflow-hidden', className)}>
87
+ {title && (
88
+ <p className="text-xs font-mono text-text-disabled tracking-widest uppercase text-center mb-6">
89
+ {title}
90
+ </p>
91
+ )}
92
+
93
+ {/* Fade masks */}
94
+ <div className="relative">
95
+ <div className="absolute left-0 top-0 bottom-0 w-24 bg-gradient-to-r from-bg-base to-transparent z-10 pointer-events-none" />
96
+ <div className="absolute right-0 top-0 bottom-0 w-24 bg-gradient-to-l from-bg-base to-transparent z-10 pointer-events-none" />
97
+
98
+ <div className="flex flex-col gap-4">
99
+ <MarqueeRow
100
+ logos={rows === 1 ? logos : row1}
101
+ speed={speed}
102
+ reverse={direction === 'right'}
103
+ pauseOnHover={pauseOnHover}
104
+ />
105
+ {rows === 2 && (
106
+ <MarqueeRow
107
+ logos={row2}
108
+ speed={speed}
109
+ reverse={direction === 'left'}
110
+ pauseOnHover={pauseOnHover}
111
+ />
112
+ )}
113
+ </div>
114
+ </div>
115
+ </div>
116
+ );
117
+ };
118
+
119
+ export { LogoMarquee, LogoTile };
@@ -0,0 +1,194 @@
1
+ import * as React from 'react';
2
+
3
+ export interface NavDropdownItem {
4
+ label: string;
5
+ description?: string;
6
+ href: string;
7
+ icon?: React.ReactNode;
8
+ badge?: string;
9
+ }
10
+ export interface NavDropdownGroup {
11
+ label?: string;
12
+ items: NavDropdownItem[];
13
+ }
14
+ export interface NavItem {
15
+ label: string;
16
+ href?: string;
17
+ groups?: NavDropdownGroup[];
18
+ }
19
+ export interface NavbarProps {
20
+ logo: React.ReactNode;
21
+ items: NavItem[];
22
+ announcementBar?: { message: React.ReactNode; href?: string };
23
+ ctaPrimary?: { label: string; href?: string; onClick?: () => void };
24
+ ctaSecondary?: { label: string; href?: string; onClick?: () => void };
25
+ loginHref?: string;
26
+ }
27
+
28
+ const NAV: React.CSSProperties & Record<string, string> = {
29
+ fontFamily: "var(--font-sans)",
30
+ };
31
+
32
+ const Navbar: React.FC<NavbarProps> = ({
33
+ logo, items, announcementBar, ctaPrimary, ctaSecondary, loginHref,
34
+ }) => {
35
+ const [mobileOpen, setMobileOpen] = React.useState(false);
36
+ const [openDropdown, setOpenDropdown] = React.useState<string | null>(null);
37
+
38
+ return (
39
+ <div style={{ width: '100%', fontFamily: 'var(--font-sans)' }}>
40
+ {/* Announcement bar */}
41
+ {announcementBar && (
42
+ <div style={{
43
+ width: '100%', borderBottom: '1px solid var(--c-border-accent)',
44
+ background: 'var(--c-brand-subtle)', padding: '8px 24px',
45
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
46
+ }}>
47
+ <a href={announcementBar.href} style={{
48
+ display: 'flex', alignItems: 'center', gap: 8,
49
+ fontSize: 12, color: 'var(--c-text-accent)',
50
+ fontFamily: 'var(--font-mono)', letterSpacing: '.02em', textDecoration: 'none',
51
+ }}>
52
+ {announcementBar.message}
53
+ <span style={{ fontSize: 10 }}>→</span>
54
+ </a>
55
+ </div>
56
+ )}
57
+
58
+ {/* Main nav — frosted glass */}
59
+ <nav style={{
60
+ width: '100%',
61
+ borderBottom: '1px solid var(--c-border)',
62
+ background: 'rgba(9,9,11,0.82)',
63
+ backdropFilter: 'blur(12px)',
64
+ WebkitBackdropFilter: 'blur(12px)',
65
+ position: 'sticky', top: 0, zIndex: 50,
66
+ }}>
67
+ <div style={{
68
+ maxWidth: 1200, margin: '0 auto', padding: '0 24px',
69
+ height: 60, display: 'flex', alignItems: 'center', gap: 32,
70
+ }}>
71
+ {/* Logo */}
72
+ <div style={{ flexShrink: 0 }}>{logo}</div>
73
+
74
+ {/* Desktop nav */}
75
+ <div style={{ display: 'flex', alignItems: 'center', gap: 2, flex: 1 }}>
76
+ {items.map(item => (
77
+ <div
78
+ key={item.label}
79
+ style={{ position: 'relative' }}
80
+ onMouseEnter={() => item.groups && setOpenDropdown(item.label)}
81
+ onMouseLeave={() => setOpenDropdown(null)}
82
+ >
83
+ {item.groups ? (
84
+ <button style={{
85
+ display: 'flex', alignItems: 'center', gap: 4,
86
+ padding: '6px 12px', borderRadius: 8, border: 'none', cursor: 'pointer',
87
+ fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 500,
88
+ color: openDropdown === item.label ? 'var(--c-text)' : 'var(--c-text-2)',
89
+ background: openDropdown === item.label ? 'var(--c-bg-surface-hover)' : 'transparent',
90
+ transition: 'all .15s',
91
+ }}>
92
+ {item.label}
93
+ <span style={{ fontSize: 9, color: 'var(--c-text-muted)', transform: openDropdown === item.label ? 'rotate(180deg)' : 'none', transition: 'transform .2s', display: 'inline-block' }}>▼</span>
94
+ </button>
95
+ ) : (
96
+ <a href={item.href} style={{
97
+ display: 'flex', alignItems: 'center', padding: '6px 12px',
98
+ borderRadius: 8, fontFamily: 'var(--font-sans)', fontSize: 13, fontWeight: 500,
99
+ color: 'var(--c-text-2)', textDecoration: 'none', transition: 'all .15s',
100
+ }}>
101
+ {item.label}
102
+ </a>
103
+ )}
104
+
105
+ {/* Dropdown */}
106
+ {item.groups && openDropdown === item.label && (
107
+ <div style={{ position: 'absolute', top: '100%', left: 0, paddingTop: 8, zIndex: 100, minWidth: 440 }}>
108
+ <div style={{
109
+ border: '1px solid var(--c-border)',
110
+ background: 'var(--c-bg-elevated)',
111
+ borderRadius: 14, padding: 16,
112
+ boxShadow: 'var(--shadow-xl)',
113
+ }}>
114
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 4 }}>
115
+ {item.groups.map((group, gi) => (
116
+ <div key={gi}>
117
+ {group.label && (
118
+ <p style={{
119
+ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700,
120
+ color: 'var(--c-text-muted)', letterSpacing: '.08em',
121
+ textTransform: 'uppercase', padding: '4px 8px 6px',
122
+ }}>{group.label}</p>
123
+ )}
124
+ {group.items.map((navItem, ni) => (
125
+ <a key={ni} href={navItem.href} style={{
126
+ display: 'flex', alignItems: 'flex-start', gap: 12,
127
+ padding: '10px 10px', borderRadius: 10, textDecoration: 'none',
128
+ transition: 'background .12s',
129
+ }}
130
+ onMouseEnter={e => (e.currentTarget.style.background = 'var(--c-bg-surface-hover)')}
131
+ onMouseLeave={e => (e.currentTarget.style.background = 'transparent')}
132
+ >
133
+ {navItem.icon && (
134
+ <div style={{
135
+ width: 32, height: 32, borderRadius: 8, flexShrink: 0,
136
+ background: 'var(--c-bg-surface)', border: '1px solid var(--c-border)',
137
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
138
+ }}>{navItem.icon}</div>
139
+ )}
140
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
141
+ <span style={{ fontSize: 13, fontWeight: 500, color: 'var(--c-text)' }}>{navItem.label}</span>
142
+ {navItem.description && (
143
+ <span style={{ fontSize: 12, color: 'var(--c-text-3)', lineHeight: 1.4 }}>{navItem.description}</span>
144
+ )}
145
+ </div>
146
+ </a>
147
+ ))}
148
+ </div>
149
+ ))}
150
+ </div>
151
+ </div>
152
+ </div>
153
+ )}
154
+ </div>
155
+ ))}
156
+ </div>
157
+
158
+ {/* Right CTAs */}
159
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 'auto' }}>
160
+ {loginHref && (
161
+ <a href={loginHref} style={{
162
+ fontSize: 13, fontWeight: 500, color: 'var(--c-text-2)',
163
+ padding: '6px 12px', borderRadius: 8, textDecoration: 'none', transition: 'color .15s',
164
+ }}>Log in</a>
165
+ )}
166
+ {ctaSecondary && (
167
+ <button onClick={ctaSecondary.onClick} style={{
168
+ height: 34, padding: '0 14px', borderRadius: 8, border: '1px solid var(--c-border)',
169
+ background: 'transparent', color: 'var(--c-text)', fontSize: 13, fontWeight: 500,
170
+ fontFamily: 'var(--font-sans)', cursor: 'pointer', transition: 'all .15s',
171
+ }}>{ctaSecondary.label}</button>
172
+ )}
173
+ {ctaPrimary && (
174
+ <button onClick={ctaPrimary.onClick} style={{
175
+ height: 34, padding: '0 16px', borderRadius: 8, border: 'none',
176
+ background: 'var(--c-brand)', color: '#fff', fontSize: 13, fontWeight: 500,
177
+ fontFamily: 'var(--font-sans)', cursor: 'pointer', transition: 'all .15s',
178
+ }}>{ctaPrimary.label}</button>
179
+ )}
180
+ </div>
181
+
182
+ {/* Mobile toggle */}
183
+ <button
184
+ style={{ display: 'none', padding: 8, background: 'none', border: 'none', cursor: 'pointer', color: 'var(--c-text-2)' }}
185
+ onClick={() => setMobileOpen(!mobileOpen)}
186
+ aria-label="Toggle menu"
187
+ >☰</button>
188
+ </div>
189
+ </nav>
190
+ </div>
191
+ );
192
+ };
193
+
194
+ export { Navbar };
@@ -0,0 +1,3 @@
1
+ export * from './Navbar';
2
+ export * from './LogoMarquee';
3
+ export * from './FeatureTabs';
@@ -0,0 +1,15 @@
1
+ // ─── Atoms ────────────────────────────────────────────────────────────────────
2
+ export * from './components/atoms/index';
3
+
4
+ // ─── Molecules ────────────────────────────────────────────────────────────────
5
+ export * from './components/molecules/index';
6
+
7
+ // ─── Organisms ────────────────────────────────────────────────────────────────
8
+ export * from './components/organisms/index';
9
+
10
+ // ─── Charts ───────────────────────────────────────────────────────────────────
11
+ export * from './components/charts/index';
12
+
13
+ // ─── Utils ────────────────────────────────────────────────────────────────────
14
+ export { cn } from './lib/utils';
15
+ export type { WithClassName, AsChild, Size, Variant, Status } from './lib/utils';
@@ -0,0 +1,12 @@
1
+ import { type ClassValue, clsx } from 'clsx';
2
+ import { twMerge } from 'tailwind-merge';
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ export type WithClassName = { className?: string };
9
+ export type AsChild = { asChild?: boolean };
10
+ export type Size = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
11
+ export type Variant = 'default' | 'secondary' | 'ghost' | 'outline' | 'destructive' | 'link';
12
+ export type Status = 'success' | 'warning' | 'error' | 'info' | 'neutral';
@@ -0,0 +1,49 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Avatar } from '../../components/atoms/Avatar';
3
+
4
+ const meta: Meta<typeof Avatar> = {
5
+ title: 'Atoms/Avatar',
6
+ component: Avatar,
7
+ tags: ['autodocs'],
8
+ parameters: { layout: 'centered' },
9
+ };
10
+
11
+ export default meta;
12
+ type Story = StoryObj<typeof Avatar>;
13
+
14
+ export const WithImage: Story = {
15
+ args: {
16
+ src: 'https://avatars.githubusercontent.com/u/28986134',
17
+ alt: 'Steven Tey',
18
+ fallback: 'ST',
19
+ },
20
+ };
21
+
22
+ export const WithFallback: Story = {
23
+ args: { fallback: 'JH' },
24
+ };
25
+
26
+ export const Sizes: Story = {
27
+ render: () => (
28
+ <div className="flex items-end gap-4">
29
+ {(['xs','sm','md','lg','xl'] as const).map((size) => (
30
+ <Avatar key={size} size={size} fallback="RT" />
31
+ ))}
32
+ </div>
33
+ ),
34
+ };
35
+
36
+ export const AvatarGroup: Story = {
37
+ render: () => (
38
+ <div className="flex -space-x-2">
39
+ {['ST','KK','GR','JH'].map((f, i) => (
40
+ <div key={i} className="ring-2 ring-[--c-bg] rounded-full">
41
+ <Avatar fallback={f} size="sm" />
42
+ </div>
43
+ ))}
44
+ <div className="ring-2 ring-[--c-bg] rounded-full size-8 rounded-full bg-[--c-bg-surface] border border-[--c-border] text-[11px] font-medium text-[--c-text-3] flex items-center justify-center">
45
+ +8
46
+ </div>
47
+ </div>
48
+ ),
49
+ };
@@ -0,0 +1,68 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Badge } from '../../components/atoms/Badge';
3
+
4
+ const meta: Meta<typeof Badge> = {
5
+ title: 'Atoms/Badge',
6
+ component: Badge,
7
+ tags: ['autodocs'],
8
+ parameters: { layout: 'centered' },
9
+ argTypes: {
10
+ variant: {
11
+ control: 'select',
12
+ options: ['default','brand','emerald','blue','success','warning','error','info','outline','solid','solid-blue','porcelain'],
13
+ },
14
+ dot: { control: 'boolean' },
15
+ },
16
+ };
17
+
18
+ export default meta;
19
+ type Story = StoryObj<typeof Badge>;
20
+
21
+ export const Default: Story = {
22
+ args: { children: 'Badge' },
23
+ };
24
+
25
+ export const AllVariants: Story = {
26
+ render: () => (
27
+ <div className="flex flex-wrap gap-2 items-center">
28
+ <Badge variant="default">Default</Badge>
29
+ <Badge variant="brand">Brand</Badge>
30
+ <Badge variant="emerald">Emerald</Badge>
31
+ <Badge variant="blue">Blue</Badge>
32
+ <Badge variant="success">Success</Badge>
33
+ <Badge variant="warning">Warning</Badge>
34
+ <Badge variant="error">Error</Badge>
35
+ <Badge variant="info">Info</Badge>
36
+ <Badge variant="outline">Outline</Badge>
37
+ <Badge variant="solid">Solid</Badge>
38
+ <Badge variant="solid-blue">Solid Blue</Badge>
39
+ <Badge variant="porcelain">Porcelain</Badge>
40
+ </div>
41
+ ),
42
+ };
43
+
44
+ export const WithDot: Story = {
45
+ render: () => (
46
+ <div className="flex flex-wrap gap-2 items-center">
47
+ <Badge variant="success" dot>Online</Badge>
48
+ <Badge variant="error" dot>Offline</Badge>
49
+ <Badge variant="warning" dot>Degraded</Badge>
50
+ <Badge variant="info" dot>Syncing</Badge>
51
+ <Badge variant="brand" dot>Active</Badge>
52
+ </div>
53
+ ),
54
+ };
55
+
56
+ export const UsageExamples: Story = {
57
+ render: () => (
58
+ <div className="flex flex-wrap gap-2 items-center">
59
+ <Badge variant="brand">v2.1.0</Badge>
60
+ <Badge variant="solid">Popular</Badge>
61
+ <Badge variant="blue">New</Badge>
62
+ <Badge variant="default">Pro</Badge>
63
+ <Badge variant="outline">Enterprise</Badge>
64
+ <Badge variant="success" dot>Verified</Badge>
65
+ <Badge variant="warning" dot>Pending</Badge>
66
+ </div>
67
+ ),
68
+ };