@dtsports/design-system 0.2.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.
- package/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/components/accordion.d.ts +19 -0
- package/dist/components/accordion.d.ts.map +1 -0
- package/dist/components/accordion.js +26 -0
- package/dist/components/accordion.js.map +1 -0
- package/dist/components/alert.d.ts +26 -0
- package/dist/components/alert.d.ts.map +1 -0
- package/dist/components/alert.js.map +1 -0
- package/dist/components/app-shell.d.ts +21 -0
- package/dist/components/app-shell.d.ts.map +1 -0
- package/dist/components/app-shell.js +25 -0
- package/dist/components/app-shell.js.map +1 -0
- package/dist/components/aspect-ratio.d.ts +16 -0
- package/dist/components/aspect-ratio.d.ts.map +1 -0
- package/dist/components/aspect-ratio.js +17 -0
- package/dist/components/aspect-ratio.js.map +1 -0
- package/dist/components/avatar-group.d.ts +31 -0
- package/dist/components/avatar-group.d.ts.map +1 -0
- package/dist/components/avatar.d.ts +15 -0
- package/dist/components/avatar.d.ts.map +1 -0
- package/dist/components/avatar.js +20 -0
- package/dist/components/avatar.js.map +1 -0
- package/dist/components/badge.d.ts +27 -0
- package/dist/components/badge.d.ts.map +1 -0
- package/dist/components/badge.js +51 -0
- package/dist/components/badge.js.map +1 -0
- package/dist/components/breadcrumb.js.map +1 -0
- package/dist/components/button.d.ts +33 -0
- package/dist/components/button.d.ts.map +1 -0
- package/dist/components/button.js +58 -0
- package/dist/components/button.js.map +1 -0
- package/dist/components/calendar.d.ts +15 -0
- package/dist/components/calendar.d.ts.map +1 -0
- package/dist/components/calendar.js +33 -0
- package/dist/components/calendar.js.map +1 -0
- package/dist/components/capacity-bar.d.ts +32 -0
- package/dist/components/capacity-bar.d.ts.map +1 -0
- package/dist/components/capacity-bar.js +29 -0
- package/dist/components/capacity-bar.js.map +1 -0
- package/dist/components/card.d.ts +26 -0
- package/dist/components/card.d.ts.map +1 -0
- package/dist/components/card.js +20 -0
- package/dist/components/checkbox.d.ts +11 -0
- package/dist/components/checkbox.d.ts.map +1 -0
- package/dist/components/checkbox.js +19 -0
- package/dist/components/chip.d.ts +23 -0
- package/dist/components/chip.d.ts.map +1 -0
- package/dist/components/chip.js +32 -0
- package/dist/components/chip.js.map +1 -0
- package/dist/components/collapsible.d.ts +16 -0
- package/dist/components/collapsible.js +22 -0
- package/dist/components/collapsible.js.map +1 -0
- package/dist/components/combobox.d.ts +38 -0
- package/dist/components/combobox.d.ts.map +1 -0
- package/dist/components/combobox.js +17 -0
- package/dist/components/combobox.js.map +1 -0
- package/dist/components/confirm-action.d.ts +36 -0
- package/dist/components/confirm-action.d.ts.map +1 -0
- package/dist/components/confirm-action.js +21 -0
- package/dist/components/confirm-action.js.map +1 -0
- package/dist/components/container.js.map +1 -0
- package/dist/components/date-picker.d.ts.map +1 -0
- package/dist/components/date-picker.js +22 -0
- package/dist/components/date-picker.js.map +1 -0
- package/dist/components/dialog.d.ts +28 -0
- package/dist/components/dialog.d.ts.map +1 -0
- package/dist/components/dialog.js.map +1 -0
- package/dist/components/drawer.js +55 -0
- package/dist/components/drawer.js.map +1 -0
- package/dist/components/dropdown-menu.d.ts +39 -0
- package/dist/components/dropdown-menu.d.ts.map +1 -0
- package/dist/components/dropdown-menu.js +47 -0
- package/dist/components/empty-state.d.ts +15 -0
- package/dist/components/empty-state.d.ts.map +1 -0
- package/dist/components/empty-state.js +6 -0
- package/dist/components/form-field.d.ts +32 -0
- package/dist/components/form-field.d.ts.map +1 -0
- package/dist/components/form-field.js +31 -0
- package/dist/components/form-field.js.map +1 -0
- package/dist/components/input.d.ts +10 -0
- package/dist/components/input.d.ts.map +1 -0
- package/dist/components/input.js +6 -0
- package/dist/components/input.js.map +1 -0
- package/dist/components/label.d.ts +11 -0
- package/dist/components/label.d.ts.map +1 -0
- package/dist/components/label.js +13 -0
- package/dist/components/label.js.map +1 -0
- package/dist/components/live-pill.d.ts +14 -0
- package/dist/components/live-pill.js +15 -0
- package/dist/components/live-pill.js.map +1 -0
- package/dist/components/metric-tile.d.ts +23 -0
- package/dist/components/metric-tile.d.ts.map +1 -0
- package/dist/components/money-breakdown.d.ts +36 -0
- package/dist/components/money-breakdown.d.ts.map +1 -0
- package/dist/components/money-breakdown.js +12 -0
- package/dist/components/money-breakdown.js.map +1 -0
- package/dist/components/number-input.d.ts +16 -0
- package/dist/components/number-input.d.ts.map +1 -0
- package/dist/components/number-input.js +30 -0
- package/dist/components/number-input.js.map +1 -0
- package/dist/components/pagination.d.ts +27 -0
- package/dist/components/pagination.d.ts.map +1 -0
- package/dist/components/pagination.js +52 -0
- package/dist/components/pagination.js.map +1 -0
- package/dist/components/popover.d.ts +24 -0
- package/dist/components/popover.d.ts.map +1 -0
- package/dist/components/popover.js +28 -0
- package/dist/components/popover.js.map +1 -0
- package/dist/components/progress.d.ts +13 -0
- package/dist/components/progress.d.ts.map +1 -0
- package/dist/components/progress.js +17 -0
- package/dist/components/radio-group.d.ts +10 -0
- package/dist/components/radio-group.d.ts.map +1 -0
- package/dist/components/radio-group.js +17 -0
- package/dist/components/radio-group.js.map +1 -0
- package/dist/components/scroll-area.d.ts.map +1 -0
- package/dist/components/scroll-area.js +22 -0
- package/dist/components/scroll-area.js.map +1 -0
- package/dist/components/select.d.ts +27 -0
- package/dist/components/select.d.ts.map +1 -0
- package/dist/components/select.js +40 -0
- package/dist/components/select.js.map +1 -0
- package/dist/components/separator.d.ts +10 -0
- package/dist/components/separator.d.ts.map +1 -0
- package/dist/components/separator.js +14 -0
- package/dist/components/separator.js.map +1 -0
- package/dist/components/sidebar.d.ts.map +1 -0
- package/dist/components/sidebar.js +48 -0
- package/dist/components/sidebar.js.map +1 -0
- package/dist/components/skeleton.d.ts +23 -0
- package/dist/components/skeleton.js +25 -0
- package/dist/components/skeleton.js.map +1 -0
- package/dist/components/slider.d.ts +14 -0
- package/dist/components/slider.d.ts.map +1 -0
- package/dist/components/slider.js +30 -0
- package/dist/components/slider.js.map +1 -0
- package/dist/components/spec.d.ts +35 -0
- package/dist/components/spec.js +31 -0
- package/dist/components/spec.js.map +1 -0
- package/dist/components/spinner.d.ts +20 -0
- package/dist/components/spinner.d.ts.map +1 -0
- package/dist/components/spinner.js +18 -0
- package/dist/components/spinner.js.map +1 -0
- package/dist/components/stack.d.ts +48 -0
- package/dist/components/stack.d.ts.map +1 -0
- package/dist/components/stack.js +43 -0
- package/dist/components/stack.js.map +1 -0
- package/dist/components/status-pill.js +34 -0
- package/dist/components/status-pill.js.map +1 -0
- package/dist/components/switch.d.ts +9 -0
- package/dist/components/switch.d.ts.map +1 -0
- package/dist/components/switch.js +15 -0
- package/dist/components/switch.js.map +1 -0
- package/dist/components/table.d.ts +31 -0
- package/dist/components/table.d.ts.map +1 -0
- package/dist/components/tabs.d.ts +22 -0
- package/dist/components/tabs.d.ts.map +1 -0
- package/dist/components/tabs.js +28 -0
- package/dist/components/tabs.js.map +1 -0
- package/dist/components/textarea.d.ts +8 -0
- package/dist/components/textarea.d.ts.map +1 -0
- package/dist/components/textarea.js +6 -0
- package/dist/components/textarea.js.map +1 -0
- package/dist/components/theme-provider.d.ts +35 -0
- package/dist/components/theme-provider.d.ts.map +1 -0
- package/dist/components/theme-provider.js.map +1 -0
- package/dist/components/theme-toggle.d.ts +10 -0
- package/dist/components/theme-toggle.d.ts.map +1 -0
- package/dist/components/theme-toggle.js.map +1 -0
- package/dist/components/three-state.d.ts +39 -0
- package/dist/components/three-state.d.ts.map +1 -0
- package/dist/components/three-state.js +24 -0
- package/dist/components/three-state.js.map +1 -0
- package/dist/components/timestamp.d.ts +24 -0
- package/dist/components/timestamp.js +64 -0
- package/dist/components/timestamp.js.map +1 -0
- package/dist/components/toaster.d.ts +27 -0
- package/dist/components/toaster.d.ts.map +1 -0
- package/dist/components/toaster.js +21 -0
- package/dist/components/toaster.js.map +1 -0
- package/dist/components/tooltip.d.ts +28 -0
- package/dist/components/tooltip.d.ts.map +1 -0
- package/dist/components/tooltip.js.map +1 -0
- package/dist/components/top-bar.d.ts +13 -0
- package/dist/components/top-bar.d.ts.map +1 -0
- package/dist/components/top-bar.js +16 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/utils.d.ts +7 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +10 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/marketing/footer.d.ts +31 -0
- package/dist/marketing/footer.d.ts.map +1 -0
- package/dist/marketing/footer.js +9 -0
- package/dist/marketing/footer.js.map +1 -0
- package/dist/marketing/hero.d.ts +29 -0
- package/dist/marketing/hero.d.ts.map +1 -0
- package/dist/marketing/hero.js +7 -0
- package/dist/marketing/hero.js.map +1 -0
- package/dist/marketing/index.d.ts +21 -0
- package/dist/marketing/index.d.ts.map +1 -0
- package/dist/marketing/index.js +21 -0
- package/dist/marketing/index.js.map +1 -0
- package/dist/marketing/logo-cloud.d.ts +15 -0
- package/dist/marketing/logo-cloud.d.ts.map +1 -0
- package/dist/marketing/logo-cloud.js +6 -0
- package/dist/marketing/logo-cloud.js.map +1 -0
- package/dist/marketing/nav.d.ts +33 -0
- package/dist/marketing/nav.d.ts.map +1 -0
- package/dist/marketing/nav.js +14 -0
- package/dist/marketing/nav.js.map +1 -0
- package/dist/marketing/prose.d.ts +12 -0
- package/dist/marketing/prose.d.ts.map +1 -0
- package/dist/marketing/prose.js +18 -0
- package/dist/marketing/prose.js.map +1 -0
- package/dist/marketing/section.d.ts +22 -0
- package/dist/marketing/section.d.ts.map +1 -0
- package/dist/marketing/section.js +7 -0
- package/dist/marketing/section.js.map +1 -0
- package/dist/showcase/components/code-block.d.ts +11 -0
- package/dist/showcase/components/code-block.d.ts.map +1 -0
- package/dist/showcase/components/code-block.js +20 -0
- package/dist/showcase/components/code-block.js.map +1 -0
- package/dist/showcase/components/example.d.ts +16 -0
- package/dist/showcase/components/example.d.ts.map +1 -0
- package/dist/showcase/components/example.js +14 -0
- package/dist/showcase/components/example.js.map +1 -0
- package/dist/showcase/components/section.d.ts +21 -0
- package/dist/showcase/components/section.d.ts.map +1 -0
- package/dist/showcase/components/section.js +10 -0
- package/dist/showcase/components/section.js.map +1 -0
- package/dist/showcase/components/swatch.d.ts +17 -0
- package/dist/showcase/components/swatch.d.ts.map +1 -0
- package/dist/showcase/components/swatch.js +6 -0
- package/dist/showcase/components/swatch.js.map +1 -0
- package/dist/showcase/index.d.ts +17 -0
- package/dist/showcase/index.d.ts.map +1 -0
- package/dist/showcase/index.js +17 -0
- package/dist/showcase/index.js.map +1 -0
- package/dist/showcase/page.d.ts +2 -0
- package/dist/showcase/page.d.ts.map +1 -0
- package/dist/showcase/page.js +1085 -0
- package/dist/showcase/page.js.map +1 -0
- package/dist/styles/generated/tokens.d.ts +131 -0
- package/dist/styles/generated/tokens.d.ts.map +1 -0
- package/dist/styles/generated/tokens.js +107 -0
- package/dist/styles/generated/tokens.js.map +1 -0
- package/dist/styles/globals.css +131 -0
- package/dist/styles/tailwind.preset.d.ts +181 -0
- package/dist/styles/tailwind.preset.d.ts.map +1 -0
- package/dist/styles/tailwind.preset.js +114 -0
- package/dist/styles/tailwind.preset.js.map +1 -0
- package/dist/tokens/index.d.ts +176 -0
- package/dist/tokens/index.d.ts.map +1 -0
- package/dist/tokens/index.js +139 -0
- package/dist/tokens/index.js.map +1 -0
- package/dist/tokens/tokens.json +154 -0
- package/package.json +124 -0
- package/src/components/accordion.tsx +76 -0
- package/src/components/alert.tsx +113 -0
- package/src/components/app-shell.tsx +43 -0
- package/src/components/aspect-ratio.tsx +18 -0
- package/src/components/avatar-group.tsx +73 -0
- package/src/components/avatar.tsx +81 -0
- package/src/components/badge.tsx +77 -0
- package/src/components/breadcrumb.tsx +121 -0
- package/src/components/button.tsx +83 -0
- package/src/components/calendar.tsx +85 -0
- package/src/components/capacity-bar.tsx +80 -0
- package/src/components/card.tsx +104 -0
- package/src/components/checkbox.tsx +46 -0
- package/src/components/chip.tsx +74 -0
- package/src/components/collapsible.tsx +54 -0
- package/src/components/combobox.tsx +140 -0
- package/src/components/confirm-action.tsx +111 -0
- package/src/components/container.tsx +39 -0
- package/src/components/date-picker.tsx +98 -0
- package/src/components/dialog.tsx +174 -0
- package/src/components/drawer.tsx +157 -0
- package/src/components/dropdown-menu.tsx +200 -0
- package/src/components/empty-state.tsx +46 -0
- package/src/components/form-field.tsx +118 -0
- package/src/components/input.tsx +29 -0
- package/src/components/label.tsx +35 -0
- package/src/components/live-pill.tsx +35 -0
- package/src/components/metric-tile.tsx +83 -0
- package/src/components/money-breakdown.tsx +92 -0
- package/src/components/number-input.tsx +113 -0
- package/src/components/pagination.tsx +157 -0
- package/src/components/popover.tsx +46 -0
- package/src/components/progress.tsx +34 -0
- package/src/components/radio-group.tsx +46 -0
- package/src/components/scroll-area.tsx +55 -0
- package/src/components/select.tsx +169 -0
- package/src/components/separator.tsx +34 -0
- package/src/components/sidebar.tsx +168 -0
- package/src/components/skeleton.tsx +85 -0
- package/src/components/slider.tsx +58 -0
- package/src/components/spec.tsx +59 -0
- package/src/components/spinner.tsx +36 -0
- package/src/components/stack.tsx +80 -0
- package/src/components/status-pill.tsx +100 -0
- package/src/components/switch.tsx +37 -0
- package/src/components/table.tsx +129 -0
- package/src/components/tabs.tsx +72 -0
- package/src/components/textarea.tsx +27 -0
- package/src/components/theme-provider.tsx +131 -0
- package/src/components/theme-toggle.tsx +34 -0
- package/src/components/three-state.tsx +90 -0
- package/src/components/timestamp.tsx +107 -0
- package/src/components/toaster.tsx +55 -0
- package/src/components/tooltip.tsx +49 -0
- package/src/components/top-bar.tsx +29 -0
- package/src/index.ts +221 -0
- package/src/lib/utils.ts +10 -0
- package/src/marketing/footer.tsx +93 -0
- package/src/marketing/hero.tsx +92 -0
- package/src/marketing/index.ts +24 -0
- package/src/marketing/logo-cloud.tsx +35 -0
- package/src/marketing/nav.tsx +122 -0
- package/src/marketing/prose.tsx +45 -0
- package/src/marketing/section.tsx +77 -0
- package/src/showcase/components/code-block.tsx +66 -0
- package/src/showcase/components/example.tsx +47 -0
- package/src/showcase/components/section.tsx +70 -0
- package/src/showcase/components/swatch.tsx +52 -0
- package/src/showcase/index.tsx +16 -0
- package/src/showcase/page.tsx +4216 -0
- package/src/styles/generated/tokens.ts +119 -0
- package/src/styles/globals.css +131 -0
- package/src/styles/tailwind.preset.ts +134 -0
- package/src/test/a11y.test.tsx +638 -0
- package/src/test/marketing-a11y.test.tsx +138 -0
- package/src/test/setup.ts +47 -0
- package/src/tokens/index.ts +154 -0
- package/src/tokens/tokens.json +154 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../lib/utils";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Timestamp — implements design.md §3.16 Time UX.
|
|
8
|
+
*
|
|
9
|
+
* - Future events: absolute in venue local time
|
|
10
|
+
* ("Sat May 22, 6:00 pm"); tooltip reveals user-tz if different.
|
|
11
|
+
* - Past events within 7 days: relative ("3 min ago"); absolute beyond.
|
|
12
|
+
* - Within 24h of a future event with `relative` enabled: ticker
|
|
13
|
+
* ("Starts in 3h 12m").
|
|
14
|
+
*
|
|
15
|
+
* Accessibility:
|
|
16
|
+
* - Renders <time dateTime={iso}> for assistive tech.
|
|
17
|
+
* - Tooltip via native `title` attr — readable by screen readers.
|
|
18
|
+
*/
|
|
19
|
+
export interface TimestampProps {
|
|
20
|
+
/** ISO-8601 date string (UTC preferred). */
|
|
21
|
+
iso: string;
|
|
22
|
+
/** Venue timezone (IANA, e.g. "America/New_York"). Falls back to user tz. */
|
|
23
|
+
venueTz?: string;
|
|
24
|
+
/** Show relative ticker when within 24h before / 7d after. */
|
|
25
|
+
relative?: boolean;
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function formatInTz(date: Date, tz: string | undefined) {
|
|
30
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
31
|
+
timeZone: tz,
|
|
32
|
+
weekday: "short",
|
|
33
|
+
month: "short",
|
|
34
|
+
day: "numeric",
|
|
35
|
+
hour: "numeric",
|
|
36
|
+
minute: "2-digit",
|
|
37
|
+
hour12: true,
|
|
38
|
+
}).format(date);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function diffMinutes(a: Date, b: Date) {
|
|
42
|
+
return Math.round((a.getTime() - b.getTime()) / 60_000);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function relativeLabel(target: Date, now: Date): string | null {
|
|
46
|
+
const minsToTarget = diffMinutes(target, now);
|
|
47
|
+
const absMins = Math.abs(minsToTarget);
|
|
48
|
+
|
|
49
|
+
// Future, within 24h
|
|
50
|
+
if (minsToTarget > 0 && minsToTarget <= 24 * 60) {
|
|
51
|
+
const h = Math.floor(minsToTarget / 60);
|
|
52
|
+
const m = minsToTarget % 60;
|
|
53
|
+
return `Starts in ${h > 0 ? `${h}h ` : ""}${m}m`;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Past, within 7 days
|
|
57
|
+
if (minsToTarget < 0 && absMins <= 7 * 24 * 60) {
|
|
58
|
+
if (absMins < 1) return "just now";
|
|
59
|
+
if (absMins < 60) return `${absMins} min ago`;
|
|
60
|
+
const h = Math.floor(absMins / 60);
|
|
61
|
+
if (h < 24) return `${h}h ago`;
|
|
62
|
+
const d = Math.floor(h / 24);
|
|
63
|
+
return `${d}d ago`;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function Timestamp({
|
|
69
|
+
iso,
|
|
70
|
+
venueTz,
|
|
71
|
+
relative = false,
|
|
72
|
+
className,
|
|
73
|
+
}: TimestampProps) {
|
|
74
|
+
const target = React.useMemo(() => new Date(iso), [iso]);
|
|
75
|
+
|
|
76
|
+
// Tick once per minute so relative labels stay fresh
|
|
77
|
+
const [now, setNow] = React.useState(() => new Date());
|
|
78
|
+
React.useEffect(() => {
|
|
79
|
+
if (!relative) return;
|
|
80
|
+
const id = setInterval(() => setNow(new Date()), 60_000);
|
|
81
|
+
return () => clearInterval(id);
|
|
82
|
+
}, [relative]);
|
|
83
|
+
|
|
84
|
+
const userTz =
|
|
85
|
+
typeof Intl !== "undefined"
|
|
86
|
+
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
87
|
+
: undefined;
|
|
88
|
+
const showUserTz = !!venueTz && !!userTz && venueTz !== userTz;
|
|
89
|
+
|
|
90
|
+
const venueLabel = formatInTz(target, venueTz);
|
|
91
|
+
const rel = relative ? relativeLabel(target, now) : null;
|
|
92
|
+
const userLabel = showUserTz ? formatInTz(target, userTz) : null;
|
|
93
|
+
|
|
94
|
+
const title = userLabel
|
|
95
|
+
? `Venue: ${venueLabel}\nYour time: ${userLabel}`
|
|
96
|
+
: venueLabel;
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<time
|
|
100
|
+
dateTime={iso}
|
|
101
|
+
title={title}
|
|
102
|
+
className={cn("tabular text-sm text-foreground", className)}
|
|
103
|
+
>
|
|
104
|
+
{rel ?? venueLabel}
|
|
105
|
+
</time>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type * as React from "react";
|
|
4
|
+
import { Toaster as SonnerToaster, toast } from "sonner";
|
|
5
|
+
import { useTheme } from "./theme-provider";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Toaster — the design-system-themed sonner toaster. Mount ONCE inside
|
|
9
|
+
* ThemeProvider (typically in the root layout next to your app shell):
|
|
10
|
+
*
|
|
11
|
+
* <ThemeProvider>
|
|
12
|
+
* <App />
|
|
13
|
+
* <Toaster />
|
|
14
|
+
* </ThemeProvider>
|
|
15
|
+
*
|
|
16
|
+
* Fire toasts anywhere with the re-exported `toast`:
|
|
17
|
+
*
|
|
18
|
+
* toast.success("Game cancelled. 8 refunds issued as credit.");
|
|
19
|
+
*
|
|
20
|
+
* Per §3.10 every action outcome must be visible — toasts are the
|
|
21
|
+
* default surface for async success/failure. Per §3.7 cheap reversals
|
|
22
|
+
* get a 5s undo action:
|
|
23
|
+
*
|
|
24
|
+
* toast("Draft deleted", {
|
|
25
|
+
* action: { label: "Undo", onClick: restoreDraft },
|
|
26
|
+
* });
|
|
27
|
+
*/
|
|
28
|
+
export type ToasterProps = React.ComponentProps<typeof SonnerToaster>;
|
|
29
|
+
|
|
30
|
+
export function Toaster(props: ToasterProps) {
|
|
31
|
+
const { theme } = useTheme();
|
|
32
|
+
return (
|
|
33
|
+
<SonnerToaster
|
|
34
|
+
theme={theme}
|
|
35
|
+
duration={5000}
|
|
36
|
+
toastOptions={{
|
|
37
|
+
classNames: {
|
|
38
|
+
toast:
|
|
39
|
+
"group rounded border border-border bg-background text-foreground shadow-md text-sm",
|
|
40
|
+
description: "text-text-muted text-xs",
|
|
41
|
+
actionButton:
|
|
42
|
+
"bg-brand-forest text-brand-forest-foreground rounded-sm text-xs font-medium",
|
|
43
|
+
cancelButton: "bg-bg-muted text-text-muted rounded-sm text-xs",
|
|
44
|
+
success: "[&_[data-icon]]:text-success",
|
|
45
|
+
warning: "[&_[data-icon]]:text-warning",
|
|
46
|
+
error: "[&_[data-icon]]:text-danger",
|
|
47
|
+
info: "[&_[data-icon]]:text-info",
|
|
48
|
+
},
|
|
49
|
+
}}
|
|
50
|
+
{...props}
|
|
51
|
+
/>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { toast };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
5
|
+
import { cn } from "../lib/utils";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tooltip — keyboard-accessible hover hints.
|
|
9
|
+
*
|
|
10
|
+
* <Tooltip>
|
|
11
|
+
* <TooltipTrigger asChild><Button size="icon">…</Button></TooltipTrigger>
|
|
12
|
+
* <TooltipContent>Cancel game</TooltipContent>
|
|
13
|
+
* </Tooltip>
|
|
14
|
+
*
|
|
15
|
+
* NOTE: A `<TooltipProvider>` must wrap the app root for tooltips to
|
|
16
|
+
* function — the design system's `<ThemeProvider>` already includes it,
|
|
17
|
+
* so consumers get this for free.
|
|
18
|
+
*
|
|
19
|
+
* Use for:
|
|
20
|
+
* - Icon-only button labels (preferred over native `title=` which is
|
|
21
|
+
* mouse-only and not keyboard accessible)
|
|
22
|
+
* - "Did you know" tips beside an input label
|
|
23
|
+
* - Truncated text expansions
|
|
24
|
+
*
|
|
25
|
+
* Don't use for critical info or anything a user needs to interact with —
|
|
26
|
+
* tooltips hide on blur and aren't reachable on touch devices.
|
|
27
|
+
*/
|
|
28
|
+
export const TooltipProvider = TooltipPrimitive.Provider;
|
|
29
|
+
export const Tooltip = TooltipPrimitive.Root;
|
|
30
|
+
export const TooltipTrigger = TooltipPrimitive.Trigger;
|
|
31
|
+
|
|
32
|
+
export const TooltipContent = React.forwardRef<
|
|
33
|
+
React.ElementRef<typeof TooltipPrimitive.Content>,
|
|
34
|
+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
|
35
|
+
>(({ className, sideOffset = 4, ...props }, ref) => (
|
|
36
|
+
<TooltipPrimitive.Portal>
|
|
37
|
+
<TooltipPrimitive.Content
|
|
38
|
+
ref={ref}
|
|
39
|
+
sideOffset={sideOffset}
|
|
40
|
+
className={cn(
|
|
41
|
+
"z-50 overflow-hidden rounded-sm bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md max-w-xs",
|
|
42
|
+
"data-[state=delayed-open]:animate-fade-in",
|
|
43
|
+
className
|
|
44
|
+
)}
|
|
45
|
+
{...props}
|
|
46
|
+
/>
|
|
47
|
+
</TooltipPrimitive.Portal>
|
|
48
|
+
));
|
|
49
|
+
TooltipContent.displayName = "TooltipContent";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* TopBar — sticky 48px header bar. Renders the chrome (border, blur,
|
|
6
|
+
* height); content layout is the consumer's responsibility:
|
|
7
|
+
*
|
|
8
|
+
* <TopBar>
|
|
9
|
+
* <Breadcrumb>…</Breadcrumb>
|
|
10
|
+
* <div className="flex-1" />
|
|
11
|
+
* <ThemeToggle />
|
|
12
|
+
* </TopBar>
|
|
13
|
+
*/
|
|
14
|
+
export const TopBar = React.forwardRef<
|
|
15
|
+
HTMLElement,
|
|
16
|
+
React.HTMLAttributes<HTMLElement>
|
|
17
|
+
>(({ className, children, ...props }, ref) => (
|
|
18
|
+
<header
|
|
19
|
+
ref={ref}
|
|
20
|
+
className={cn(
|
|
21
|
+
"sticky top-0 z-20 bg-background/95 backdrop-blur border-b border-border",
|
|
22
|
+
className
|
|
23
|
+
)}
|
|
24
|
+
{...props}
|
|
25
|
+
>
|
|
26
|
+
<div className="flex h-12 items-center gap-3 px-6">{children}</div>
|
|
27
|
+
</header>
|
|
28
|
+
));
|
|
29
|
+
TopBar.displayName = "TopBar";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dtsports/design-system — public API.
|
|
3
|
+
*
|
|
4
|
+
* Import paths:
|
|
5
|
+
* import { Button, Card, Table, … } from "@dtsports/design-system";
|
|
6
|
+
*
|
|
7
|
+
* Styles:
|
|
8
|
+
* import "@dtsports/design-system/styles/globals.css";
|
|
9
|
+
*
|
|
10
|
+
* Tailwind preset:
|
|
11
|
+
* import preset from "@dtsports/design-system/tailwind-preset";
|
|
12
|
+
*
|
|
13
|
+
* Docs page (mountable):
|
|
14
|
+
* export { default } from "@dtsports/design-system/showcase";
|
|
15
|
+
*
|
|
16
|
+
* Fonts:
|
|
17
|
+
* The system expects "Exo 2" to be available. Load it via your app's
|
|
18
|
+
* font strategy (e.g. next/font/google) in your root layout.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// ── Primitives — forms ─────────────────────────────────────────────────
|
|
22
|
+
export { Button, buttonVariants, type ButtonProps } from "./components/button";
|
|
23
|
+
export { Input, type InputProps } from "./components/input";
|
|
24
|
+
export { Label } from "./components/label";
|
|
25
|
+
export { Textarea, type TextareaProps } from "./components/textarea";
|
|
26
|
+
export { Checkbox } from "./components/checkbox";
|
|
27
|
+
export { RadioGroup, RadioGroupItem } from "./components/radio-group";
|
|
28
|
+
export { Switch } from "./components/switch";
|
|
29
|
+
export {
|
|
30
|
+
Select,
|
|
31
|
+
SelectGroup,
|
|
32
|
+
SelectValue,
|
|
33
|
+
SelectTrigger,
|
|
34
|
+
SelectContent,
|
|
35
|
+
SelectLabel,
|
|
36
|
+
SelectItem,
|
|
37
|
+
SelectSeparator,
|
|
38
|
+
SelectScrollUpButton,
|
|
39
|
+
SelectScrollDownButton,
|
|
40
|
+
} from "./components/select";
|
|
41
|
+
export { NumberInput, type NumberInputProps } from "./components/number-input";
|
|
42
|
+
export { FormField, type FormFieldProps } from "./components/form-field";
|
|
43
|
+
export { Slider } from "./components/slider";
|
|
44
|
+
export {
|
|
45
|
+
Combobox,
|
|
46
|
+
type ComboboxOption,
|
|
47
|
+
type ComboboxProps,
|
|
48
|
+
} from "./components/combobox";
|
|
49
|
+
export { Calendar, type CalendarProps } from "./components/calendar";
|
|
50
|
+
export { DatePicker, type DatePickerProps } from "./components/date-picker";
|
|
51
|
+
|
|
52
|
+
// ── Primitives — display ───────────────────────────────────────────────
|
|
53
|
+
export { Badge, badgeVariants, type BadgeProps } from "./components/badge";
|
|
54
|
+
export { Chip, chipVariants, type ChipProps } from "./components/chip";
|
|
55
|
+
export {
|
|
56
|
+
Card,
|
|
57
|
+
type CardProps,
|
|
58
|
+
CardHeader,
|
|
59
|
+
CardTitle,
|
|
60
|
+
CardDescription,
|
|
61
|
+
CardContent,
|
|
62
|
+
CardFooter,
|
|
63
|
+
} from "./components/card";
|
|
64
|
+
export {
|
|
65
|
+
Table,
|
|
66
|
+
TableHeader,
|
|
67
|
+
TableBody,
|
|
68
|
+
TableFooter,
|
|
69
|
+
TableRow,
|
|
70
|
+
TableHead,
|
|
71
|
+
TableCell,
|
|
72
|
+
TableCaption,
|
|
73
|
+
} from "./components/table";
|
|
74
|
+
export { Avatar, type AvatarProps } from "./components/avatar";
|
|
75
|
+
export { AvatarGroup, type AvatarGroupProps } from "./components/avatar-group";
|
|
76
|
+
export { Separator } from "./components/separator";
|
|
77
|
+
export {
|
|
78
|
+
Spec,
|
|
79
|
+
SpecList,
|
|
80
|
+
type SpecProps,
|
|
81
|
+
type SpecListProps,
|
|
82
|
+
} from "./components/spec";
|
|
83
|
+
export { Pagination, type PaginationProps } from "./components/pagination";
|
|
84
|
+
|
|
85
|
+
// ── Primitives — feedback ──────────────────────────────────────────────
|
|
86
|
+
export { Alert, alertVariants, type AlertProps } from "./components/alert";
|
|
87
|
+
export { Toaster, toast, type ToasterProps } from "./components/toaster";
|
|
88
|
+
export { Skeleton, SkeletonList, SkeletonTable } from "./components/skeleton";
|
|
89
|
+
export { Spinner, type SpinnerProps } from "./components/spinner";
|
|
90
|
+
export { Progress } from "./components/progress";
|
|
91
|
+
|
|
92
|
+
// ── Overlays ───────────────────────────────────────────────────────────
|
|
93
|
+
export {
|
|
94
|
+
Dialog,
|
|
95
|
+
DialogTrigger,
|
|
96
|
+
DialogPortal,
|
|
97
|
+
DialogClose,
|
|
98
|
+
DialogOverlay,
|
|
99
|
+
DialogContent,
|
|
100
|
+
DialogHeader,
|
|
101
|
+
DialogTitle,
|
|
102
|
+
DialogDescription,
|
|
103
|
+
DialogBody,
|
|
104
|
+
DialogFooter,
|
|
105
|
+
} from "./components/dialog";
|
|
106
|
+
export {
|
|
107
|
+
Drawer,
|
|
108
|
+
DrawerTrigger,
|
|
109
|
+
DrawerClose,
|
|
110
|
+
DrawerPortal,
|
|
111
|
+
DrawerOverlay,
|
|
112
|
+
drawerVariants,
|
|
113
|
+
DrawerContent,
|
|
114
|
+
type DrawerContentProps,
|
|
115
|
+
DrawerHeader,
|
|
116
|
+
DrawerTitle,
|
|
117
|
+
DrawerDescription,
|
|
118
|
+
DrawerBody,
|
|
119
|
+
DrawerFooter,
|
|
120
|
+
} from "./components/drawer";
|
|
121
|
+
export {
|
|
122
|
+
Popover,
|
|
123
|
+
PopoverTrigger,
|
|
124
|
+
PopoverAnchor,
|
|
125
|
+
PopoverContent,
|
|
126
|
+
} from "./components/popover";
|
|
127
|
+
export {
|
|
128
|
+
DropdownMenu,
|
|
129
|
+
DropdownMenuTrigger,
|
|
130
|
+
DropdownMenuGroup,
|
|
131
|
+
DropdownMenuPortal,
|
|
132
|
+
DropdownMenuSub,
|
|
133
|
+
DropdownMenuRadioGroup,
|
|
134
|
+
DropdownMenuContent,
|
|
135
|
+
DropdownMenuItem,
|
|
136
|
+
DropdownMenuCheckboxItem,
|
|
137
|
+
DropdownMenuRadioItem,
|
|
138
|
+
DropdownMenuLabel,
|
|
139
|
+
DropdownMenuSeparator,
|
|
140
|
+
DropdownMenuShortcut,
|
|
141
|
+
DropdownMenuSubTrigger,
|
|
142
|
+
DropdownMenuSubContent,
|
|
143
|
+
} from "./components/dropdown-menu";
|
|
144
|
+
export {
|
|
145
|
+
TooltipProvider,
|
|
146
|
+
Tooltip,
|
|
147
|
+
TooltipTrigger,
|
|
148
|
+
TooltipContent,
|
|
149
|
+
} from "./components/tooltip";
|
|
150
|
+
|
|
151
|
+
// ── Disclosure / navigation ─────────────────────────────────────────────
|
|
152
|
+
export {
|
|
153
|
+
Accordion,
|
|
154
|
+
AccordionItem,
|
|
155
|
+
AccordionTrigger,
|
|
156
|
+
AccordionContent,
|
|
157
|
+
} from "./components/accordion";
|
|
158
|
+
export {
|
|
159
|
+
Collapsible,
|
|
160
|
+
CollapsibleTrigger,
|
|
161
|
+
CollapsibleContent,
|
|
162
|
+
} from "./components/collapsible";
|
|
163
|
+
export { Tabs, TabsList, TabsTrigger, TabsContent } from "./components/tabs";
|
|
164
|
+
export {
|
|
165
|
+
Breadcrumb,
|
|
166
|
+
BreadcrumbList,
|
|
167
|
+
BreadcrumbItem,
|
|
168
|
+
BreadcrumbLink,
|
|
169
|
+
BreadcrumbPage,
|
|
170
|
+
BreadcrumbSeparator,
|
|
171
|
+
type BreadcrumbLinkProps,
|
|
172
|
+
} from "./components/breadcrumb";
|
|
173
|
+
|
|
174
|
+
// ── Layout / shell ─────────────────────────────────────────────────────
|
|
175
|
+
export { AppShell, AppMain } from "./components/app-shell";
|
|
176
|
+
export {
|
|
177
|
+
Sidebar,
|
|
178
|
+
SidebarHeader,
|
|
179
|
+
SidebarBody,
|
|
180
|
+
SidebarFooter,
|
|
181
|
+
SidebarSection,
|
|
182
|
+
SidebarItem,
|
|
183
|
+
type SidebarSectionProps,
|
|
184
|
+
type SidebarItemProps,
|
|
185
|
+
} from "./components/sidebar";
|
|
186
|
+
export { TopBar } from "./components/top-bar";
|
|
187
|
+
export { Container, type ContainerProps } from "./components/container";
|
|
188
|
+
export { Stack, type StackProps } from "./components/stack";
|
|
189
|
+
export { AspectRatio } from "./components/aspect-ratio";
|
|
190
|
+
export { ScrollArea, ScrollBar } from "./components/scroll-area";
|
|
191
|
+
|
|
192
|
+
// ── Brand-specific primitives ──────────────────────────────────────────
|
|
193
|
+
export { LivePill } from "./components/live-pill";
|
|
194
|
+
export { StatusPill, type GameStatus } from "./components/status-pill";
|
|
195
|
+
export { MetricTile, type MetricTileProps } from "./components/metric-tile";
|
|
196
|
+
export { CapacityBar, type CapacityBarProps } from "./components/capacity-bar";
|
|
197
|
+
export { EmptyState, type EmptyStateProps } from "./components/empty-state";
|
|
198
|
+
|
|
199
|
+
// ── UX patterns (codified principles) ──────────────────────────────────
|
|
200
|
+
export { Timestamp, type TimestampProps } from "./components/timestamp";
|
|
201
|
+
export {
|
|
202
|
+
MoneyBreakdown,
|
|
203
|
+
type MoneyBreakdownProps,
|
|
204
|
+
type MoneyLine,
|
|
205
|
+
} from "./components/money-breakdown";
|
|
206
|
+
export {
|
|
207
|
+
ThreeState,
|
|
208
|
+
type ListState,
|
|
209
|
+
type ThreeStateProps,
|
|
210
|
+
} from "./components/three-state";
|
|
211
|
+
export {
|
|
212
|
+
ConfirmAction,
|
|
213
|
+
type ConfirmActionProps,
|
|
214
|
+
} from "./components/confirm-action";
|
|
215
|
+
|
|
216
|
+
// ── Theming ────────────────────────────────────────────────────────────
|
|
217
|
+
export { ThemeProvider, useTheme } from "./components/theme-provider";
|
|
218
|
+
export { ThemeToggle } from "./components/theme-toggle";
|
|
219
|
+
|
|
220
|
+
// ── Utilities ──────────────────────────────────────────────────────────
|
|
221
|
+
export { cn } from "./lib/utils";
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from "clsx";
|
|
2
|
+
import { twMerge } from "tailwind-merge";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Merge Tailwind classes with proper conflict resolution.
|
|
6
|
+
* Use this everywhere component variants need to be composable.
|
|
7
|
+
*/
|
|
8
|
+
export function cn(...inputs: ClassValue[]) {
|
|
9
|
+
return twMerge(clsx(inputs));
|
|
10
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Footer — marketing footer: link columns + a bottom bar for legal and
|
|
6
|
+
* social. Server-safe.
|
|
7
|
+
*
|
|
8
|
+
* <Footer
|
|
9
|
+
* brand={<Logo />}
|
|
10
|
+
* columns={[
|
|
11
|
+
* { title: "Product", links: [{ label: "Games", href: "/games" }] },
|
|
12
|
+
* { title: "Company", links: [{ label: "About", href: "/about" }] },
|
|
13
|
+
* ]}
|
|
14
|
+
* legal="© 2026 DT Sports"
|
|
15
|
+
* social={<SocialIcons />}
|
|
16
|
+
* />
|
|
17
|
+
*/
|
|
18
|
+
export interface FooterColumn {
|
|
19
|
+
title: string;
|
|
20
|
+
links: { label: string; href: string }[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FooterProps extends React.HTMLAttributes<HTMLElement> {
|
|
24
|
+
brand?: React.ReactNode;
|
|
25
|
+
tagline?: React.ReactNode;
|
|
26
|
+
columns?: FooterColumn[];
|
|
27
|
+
legal?: React.ReactNode;
|
|
28
|
+
social?: React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const footerLinkClass = cn(
|
|
32
|
+
"text-sm text-text-muted hover:text-foreground transition-colors rounded-sm",
|
|
33
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
export function Footer({
|
|
37
|
+
brand,
|
|
38
|
+
tagline,
|
|
39
|
+
columns = [],
|
|
40
|
+
legal,
|
|
41
|
+
social,
|
|
42
|
+
className,
|
|
43
|
+
...props
|
|
44
|
+
}: FooterProps) {
|
|
45
|
+
return (
|
|
46
|
+
<footer
|
|
47
|
+
className={cn("border-t border-border bg-bg-subtle", className)}
|
|
48
|
+
{...props}
|
|
49
|
+
>
|
|
50
|
+
<div className="mx-auto max-w-6xl px-6 lg:px-8 py-16">
|
|
51
|
+
<div
|
|
52
|
+
className="grid gap-10 md:grid-cols-[1.5fr_repeat(var(--footer-cols,3),1fr)]"
|
|
53
|
+
style={
|
|
54
|
+
{
|
|
55
|
+
"--footer-cols": Math.max(columns.length, 1),
|
|
56
|
+
} as React.CSSProperties
|
|
57
|
+
}
|
|
58
|
+
>
|
|
59
|
+
{(brand || tagline) && (
|
|
60
|
+
<div className="space-y-3">
|
|
61
|
+
{brand}
|
|
62
|
+
{tagline && (
|
|
63
|
+
<p className="text-sm text-text-muted max-w-xs">{tagline}</p>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
)}
|
|
67
|
+
{columns.map((col) => (
|
|
68
|
+
<nav key={col.title} aria-label={col.title}>
|
|
69
|
+
<h3 className="mb-3 text-xs font-semibold uppercase tracking-[0.06em] text-text-subtle">
|
|
70
|
+
{col.title}
|
|
71
|
+
</h3>
|
|
72
|
+
<ul className="space-y-2">
|
|
73
|
+
{col.links.map((l) => (
|
|
74
|
+
<li key={l.href}>
|
|
75
|
+
<a href={l.href} className={footerLinkClass}>
|
|
76
|
+
{l.label}
|
|
77
|
+
</a>
|
|
78
|
+
</li>
|
|
79
|
+
))}
|
|
80
|
+
</ul>
|
|
81
|
+
</nav>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
{(legal || social) && (
|
|
85
|
+
<div className="mt-12 flex flex-wrap items-center justify-between gap-4 border-t border-border pt-6">
|
|
86
|
+
<p className="text-xs text-text-muted">{legal}</p>
|
|
87
|
+
{social && <div className="flex items-center gap-3">{social}</div>}
|
|
88
|
+
</div>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</footer>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { cn } from "../lib/utils";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Hero — the opening section of a marketing page. Fluid display type,
|
|
6
|
+
* generous spacing, one lime CTA (§2.4 still applies on marketing pages).
|
|
7
|
+
*
|
|
8
|
+
* <Hero
|
|
9
|
+
* eyebrow="Pickup soccer, organized"
|
|
10
|
+
* title="Book a game in 30 seconds"
|
|
11
|
+
* description="Real fields, fair teams, instant refunds."
|
|
12
|
+
* actions={
|
|
13
|
+
* <>
|
|
14
|
+
* <Button variant="accent" size="touch">Find a game</Button>
|
|
15
|
+
* <Button variant="secondary" size="touch">Host with us</Button>
|
|
16
|
+
* </>
|
|
17
|
+
* }
|
|
18
|
+
* />
|
|
19
|
+
*/
|
|
20
|
+
export interface HeroProps extends Omit<
|
|
21
|
+
React.HTMLAttributes<HTMLElement>,
|
|
22
|
+
"title"
|
|
23
|
+
> {
|
|
24
|
+
eyebrow?: React.ReactNode;
|
|
25
|
+
title: React.ReactNode;
|
|
26
|
+
description?: React.ReactNode;
|
|
27
|
+
/** CTA row — keep to one accent button (§2.4). */
|
|
28
|
+
actions?: React.ReactNode;
|
|
29
|
+
/** Optional media (screenshot, illustration) below/beside the copy. */
|
|
30
|
+
media?: React.ReactNode;
|
|
31
|
+
align?: "center" | "start";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Hero({
|
|
35
|
+
eyebrow,
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
actions,
|
|
39
|
+
media,
|
|
40
|
+
align = "center",
|
|
41
|
+
className,
|
|
42
|
+
...props
|
|
43
|
+
}: HeroProps) {
|
|
44
|
+
const centered = align === "center";
|
|
45
|
+
return (
|
|
46
|
+
<section
|
|
47
|
+
className={cn(
|
|
48
|
+
"relative overflow-hidden bg-gradient-forest-fade",
|
|
49
|
+
"px-6 lg:px-8 py-20 md:py-24 lg:py-32",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...props}
|
|
53
|
+
>
|
|
54
|
+
<div
|
|
55
|
+
className={cn(
|
|
56
|
+
"mx-auto max-w-5xl",
|
|
57
|
+
centered && "text-center flex flex-col items-center"
|
|
58
|
+
)}
|
|
59
|
+
>
|
|
60
|
+
{eyebrow && (
|
|
61
|
+
<p className="mb-4 text-sm font-semibold uppercase tracking-[0.08em] text-brand-forest">
|
|
62
|
+
{eyebrow}
|
|
63
|
+
</p>
|
|
64
|
+
)}
|
|
65
|
+
<h1 className="text-display font-bold text-foreground max-w-3xl text-balance">
|
|
66
|
+
{title}
|
|
67
|
+
</h1>
|
|
68
|
+
{description && (
|
|
69
|
+
<p
|
|
70
|
+
className={cn(
|
|
71
|
+
"mt-5 text-lg text-text-muted max-w-2xl",
|
|
72
|
+
centered && "mx-auto"
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
{description}
|
|
76
|
+
</p>
|
|
77
|
+
)}
|
|
78
|
+
{actions && (
|
|
79
|
+
<div
|
|
80
|
+
className={cn(
|
|
81
|
+
"mt-8 flex flex-wrap gap-3",
|
|
82
|
+
centered && "justify-center"
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
{actions}
|
|
86
|
+
</div>
|
|
87
|
+
)}
|
|
88
|
+
{media && <div className="mt-16 w-full">{media}</div>}
|
|
89
|
+
</div>
|
|
90
|
+
</section>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @dtsports/design-system/marketing — the marketing tier.
|
|
3
|
+
*
|
|
4
|
+
* Primitives for the marketing website: fluid display typography
|
|
5
|
+
* (text-display-sm/display/display-lg), extended spacing (py-20…40),
|
|
6
|
+
* and page-level building blocks. Everything consumes the same token
|
|
7
|
+
* source as the app tier, so the brand can't fork.
|
|
8
|
+
*
|
|
9
|
+
* All components are server-safe except MarketingNav (mobile menu state).
|
|
10
|
+
*
|
|
11
|
+
* import {
|
|
12
|
+
* MarketingNav, Hero, Section, Footer, LogoCloud, Prose,
|
|
13
|
+
* } from "@dtsports/design-system/marketing";
|
|
14
|
+
*/
|
|
15
|
+
export { Hero, type HeroProps } from "./hero";
|
|
16
|
+
export { Section, type SectionProps } from "./section";
|
|
17
|
+
export {
|
|
18
|
+
MarketingNav,
|
|
19
|
+
type MarketingNavLink,
|
|
20
|
+
type MarketingNavProps,
|
|
21
|
+
} from "./nav";
|
|
22
|
+
export { Footer, type FooterColumn, type FooterProps } from "./footer";
|
|
23
|
+
export { LogoCloud, type LogoCloudProps } from "./logo-cloud";
|
|
24
|
+
export { Prose, type ProseProps } from "./prose";
|