@xenide-io/the-old-ui-theme 0.2.9 → 0.3.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 (61) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-SHF57J7U.mjs → chunk-FDMD3IIN.mjs} +1573 -652
  3. package/dist/index-D1RTT2Ap.d.mts +1044 -0
  4. package/dist/index-D1RTT2Ap.d.ts +1044 -0
  5. package/dist/index.d.mts +10 -7
  6. package/dist/index.d.ts +10 -7
  7. package/dist/index.js +1705 -744
  8. package/dist/index.mjs +61 -1
  9. package/dist/tailwind-preset.js +7 -0
  10. package/dist/tailwind-preset.mjs +7 -0
  11. package/dist/ui.d.mts +3 -2
  12. package/dist/ui.d.ts +3 -2
  13. package/dist/ui.js +1584 -666
  14. package/dist/ui.mjs +31 -1
  15. package/package.json +18 -4
  16. package/src/app/globals.css +1005 -227
  17. package/src/components/icons/IconBase.tsx +20 -12
  18. package/src/components/icons/createIconBase.tsx +12 -7
  19. package/src/components/icons/icons.tsx +208 -18
  20. package/src/components/sections/AlertShowcase.tsx +2 -1
  21. package/src/components/sections/BadgeShowcase.tsx +1 -1
  22. package/src/components/sections/ButtonShowcase.tsx +6 -10
  23. package/src/components/sections/CardShowcase.tsx +22 -6
  24. package/src/components/sections/DropdownShowcase.tsx +4 -6
  25. package/src/components/sections/FilterChipsShowcase.tsx +74 -26
  26. package/src/components/sections/FormShowcase.tsx +2 -1
  27. package/src/components/sections/IconShowcase.tsx +86 -206
  28. package/src/components/sections/KbdShowcase.tsx +47 -21
  29. package/src/components/sections/ModalShowcase.tsx +7 -6
  30. package/src/components/sections/ProductLayoutsShowcase.tsx +138 -0
  31. package/src/components/sections/ProgressShowcase.tsx +50 -53
  32. package/src/components/sections/SwapShowcase.tsx +13 -5
  33. package/src/components/ui/Alert.tsx +28 -19
  34. package/src/components/ui/AuthLayout.tsx +58 -0
  35. package/src/components/ui/Badge.tsx +10 -5
  36. package/src/components/ui/Button.tsx +22 -12
  37. package/src/components/ui/ButtonChrome.tsx +1 -1
  38. package/src/components/ui/Calendar.tsx +6 -7
  39. package/src/components/ui/Card.tsx +21 -11
  40. package/src/components/ui/CollapsibleSection.tsx +11 -11
  41. package/src/components/ui/DropdownMenu.tsx +189 -65
  42. package/src/components/ui/FilterBar.tsx +165 -0
  43. package/src/components/ui/FilterChips.tsx +65 -35
  44. package/src/components/ui/FilterControls.tsx +30 -0
  45. package/src/components/ui/FormField.tsx +69 -0
  46. package/src/components/ui/Input.tsx +166 -140
  47. package/src/components/ui/Kbd.tsx +88 -13
  48. package/src/components/ui/Loader.tsx +96 -0
  49. package/src/components/ui/Modal.tsx +66 -36
  50. package/src/components/ui/SettingsLayout.tsx +94 -0
  51. package/src/components/ui/ShowcaseWrapper.tsx +4 -16
  52. package/src/components/ui/Stepper.tsx +5 -6
  53. package/src/components/ui/first-slice.test.tsx +98 -0
  54. package/src/components/ui/index.ts +62 -3
  55. package/src/components/ui/interaction-primitives.test.tsx +77 -0
  56. package/src/components/ui/kbd-icons.test.tsx +90 -0
  57. package/src/components/ui/product-patterns.test.tsx +77 -0
  58. package/src/styles/themes.css +411 -1
  59. package/tailwind-preset.ts +7 -0
  60. package/dist/index-CmS6hcUk.d.mts +0 -753
  61. package/dist/index-CmS6hcUk.d.ts +0 -753
@@ -1,50 +1,98 @@
1
1
  "use client";
2
2
 
3
3
  import { useState } from "react";
4
- import { ComponentDocs, FilterChips, ShowcaseWrapper } from "@/components/ui";
4
+ import {
5
+ ComponentDocs,
6
+ FilterControls,
7
+ FilterMenu,
8
+ ShowcaseWrapper,
9
+ SortMenu,
10
+ } from "@/components/ui";
11
+
12
+ const statusOptions = [
13
+ { value: "all", label: "Any status" },
14
+ { value: "active", label: "Active" },
15
+ { value: "draft", label: "Draft" },
16
+ { value: "archived", label: "Archived" },
17
+ ] as const;
18
+
19
+ const ownerOptions = [
20
+ { value: "anyone", label: "Anyone" },
21
+ { value: "me", label: "Owned by me" },
22
+ { value: "team", label: "My team" },
23
+ ] as const;
24
+
25
+ const sortOptions = [
26
+ { value: "updated", label: "Recently updated" },
27
+ { value: "created", label: "Recently created" },
28
+ { value: "name", label: "Name A-Z" },
29
+ ] as const;
5
30
 
6
31
  export default function FilterChipsShowcase() {
7
- const [chips, setChips] = useState([
8
- { id: "1", label: "Active", active: true },
9
- { id: "2", label: "Archived", active: false },
10
- { id: "3", label: "Draft", active: false },
11
- { id: "4", label: "Pending", active: true },
12
- { id: "5", label: "Failed", active: false },
13
- ]);
14
-
15
- const toggleChip = (id: string) => {
16
- setChips((prev) => prev.map((chip) => (chip.id === id ? { ...chip, active: !chip.active } : chip)));
32
+ const [status, setStatus] = useState("active");
33
+ const [owner, setOwner] = useState("me");
34
+ const [sort, setSort] = useState("updated");
35
+
36
+ const applied = [
37
+ ...(status !== "all"
38
+ ? [{ id: "status", label: "Status", value: statusOptions.find((option) => option.value === status)?.label, active: true }]
39
+ : []),
40
+ ...(owner !== "anyone"
41
+ ? [{ id: "owner", label: "Owner", value: ownerOptions.find((option) => option.value === owner)?.label, active: true }]
42
+ : []),
43
+ ];
44
+
45
+ const clear = () => {
46
+ setStatus("all");
47
+ setOwner("anyone");
48
+ setSort("updated");
17
49
  };
18
50
 
19
- const removeChip = (id: string) => {
20
- setChips((prev) => prev.filter((chip) => chip.id !== id));
51
+ const remove = (id: string) => {
52
+ if (id === "status") setStatus("all");
53
+ if (id === "owner") setOwner("anyone");
21
54
  };
22
55
 
23
- const code = `import { FilterChips } from "the-old-ui";
56
+ const code = `import { FilterControls, FilterMenu, SortMenu } from "@xenide-io/the-old-ui-theme";
24
57
 
25
- <div className="ph-panel">
26
- <FilterChips chips={chips} onToggle={toggleChip} onRemove={removeChip} />
27
- </div>`;
58
+ <FilterControls
59
+ barProps={{ onClear: clearFilters, resultCount: "24 insights" }}
60
+ chipsProps={{ chips: appliedFilters, onRemove: removeFilter }}
61
+ emptyState={<span>No filters applied</span>}
62
+ >
63
+ <FilterMenu label="Status" value={status} options={statusOptions} onValueChange={setStatus} />
64
+ <FilterMenu label="Owner" value={owner} options={ownerOptions} onValueChange={setOwner} />
65
+ <SortMenu label="Sort" value={sort} options={sortOptions} onValueChange={setSort} />
66
+ </FilterControls>`;
28
67
 
29
68
  return (
30
69
  <ShowcaseWrapper
31
- title="Filter Chips"
32
- description="Controlled chips for active filters, facets, and quick toggles."
70
+ id="filters"
71
+ title="Filter and sort"
72
+ description="Controlled, URL-ready filter menus, applied chips, sorting, clear-all, and result feedback. Menus flip and shift at viewport edges."
33
73
  code={code}
34
- filename="FilterChipsExample.tsx"
74
+ filename="FilterBarExample.tsx"
35
75
  docs={
36
76
  <ComponentDocs
37
77
  rows={[
38
- { name: "chips", type: "{ id; label; active }[]", description: "Chips to render." },
39
- { name: "onToggle", type: "(id: string) => void", description: "Called when a chip is clicked." },
40
- { name: "onRemove", type: "(id: string) => void", description: "Shows remove control when provided." },
78
+ { name: "FilterControls", type: "FilterControlsProps", description: "Combined filter bar and applied-chip composition without owning state." },
79
+ { name: "FilterBar", type: "FilterBarProps", description: "Lower-level responsive group for filter, sort, clear, and result controls." },
80
+ { name: "FilterMenu", type: "FilterMenuProps", description: "Single-choice filter using an adaptive radio menu." },
81
+ { name: "SortMenu", type: "SortMenuProps", description: "Dedicated sort control kept separate from filter state." },
82
+ { name: "FilterChips", type: "FilterChipsProps", description: "Applied or toggleable chips with independent keyboard-accessible removal." },
41
83
  ]}
42
84
  />
43
85
  }
44
86
  >
45
- <div className="ph-panel">
46
- <FilterChips chips={chips} onToggle={toggleChip} onRemove={removeChip} />
47
- </div>
87
+ <FilterControls
88
+ barProps={{ onClear: clear, resultCount: "24 insights" }}
89
+ chipsProps={{ chips: applied, onRemove: remove }}
90
+ emptyState={<p className="text-xs text-ph-mutedtext">No filters applied</p>}
91
+ >
92
+ <FilterMenu label="Status" value={status} options={statusOptions} onValueChange={setStatus} />
93
+ <FilterMenu label="Owner" value={owner} options={ownerOptions} onValueChange={setOwner} />
94
+ <SortMenu label="Sort" value={sort} options={sortOptions} onValueChange={setSort} />
95
+ </FilterControls>
48
96
  </ShowcaseWrapper>
49
97
  );
50
98
  }
@@ -15,7 +15,7 @@ import {
15
15
  } from "@/components/ui";
16
16
 
17
17
  export default function FormShowcase() {
18
- const code = `import { Checkbox, FileUpload, Input, Panel, Radio, Range, Rating, SearchGroup, Select, Textarea, Toggle } from "the-old-ui";
18
+ const code = `import { Checkbox, FileUpload, Input, Panel, Radio, Range, Rating, SearchGroup, Select, Textarea, Toggle } from "@xenide-io/the-old-ui-theme";
19
19
 
20
20
  <div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
21
21
  <Panel title="Text inputs" className="space-y-6">
@@ -67,6 +67,7 @@ export default function FormShowcase() {
67
67
  <ComponentDocs
68
68
  title="Input components"
69
69
  rows={[
70
+ { name: "FormField", type: "component", description: "Shared label, description, error, required-state, and generated-ID composition for custom controls." },
70
71
  { name: "Input", type: "text | search | email | password", description: "Labelled input with error, helperText, size, and variant props." },
71
72
  { name: "Select", type: "select", description: "Labelled select with error, helperText, size, and disabled props." },
72
73
  { name: "Textarea", type: "textarea", description: "Labelled multiline input with the same state props as Input." },
@@ -1,247 +1,127 @@
1
- /**
2
- * Bespoke Lemon glyphs from PostHog open source — filled Material / product paths,
3
- * not Lucide stroke icons.
4
- */
1
+ import type { ComponentType, CSSProperties } from "react";
5
2
  import {
6
- IconActivity,
3
+ CANONICAL_ICONS,
4
+ OLD_UI_ICONS,
7
5
  IconAreaChart,
8
- IconArrowDown,
9
- IconArrowUp,
10
- IconBell,
11
- IconBellOff,
12
- IconBolt,
13
- IconBox,
14
- IconCancel,
15
- IconCheck,
16
- IconCheckCircle,
17
- IconChevronDown,
18
- IconChevronLeft,
19
- IconChevronRight,
20
- IconClipboardEdit,
21
- IconClose,
22
- IconCohort,
23
- IconCopy,
24
- IconCumulativeChart,
25
6
  IconDatabase,
26
- IconDownload,
27
- IconEdit,
28
- IconErrorOutline,
29
- IconExclamation,
30
7
  IconFlag,
31
- IconFlare,
32
8
  IconFlask,
33
- IconGift,
34
9
  IconGridView,
35
- IconHandClick,
36
- IconHome,
37
- IconLayers,
38
- IconListView,
39
- IconLogout,
40
- IconMail,
41
- IconMoon,
42
- IconPanelRight,
43
- IconPerson,
44
- IconPlayCircle,
45
- IconPlus,
46
- IconPremium,
47
- IconQueryEditor,
48
- IconRadar,
49
10
  IconRecording,
50
- IconRobot,
51
- IconRocket,
52
- IconSave,
53
- IconSearch,
54
- IconSelectEvents,
55
- IconStar,
56
- IconSubArrowRight,
57
- IconSun,
58
11
  IconSurveys,
59
- IconSync,
60
- IconTableChart,
61
- IconTerminal,
62
- IconToggle,
63
- IconTrash,
64
- IconTuning,
65
- IconUpload,
66
- IconVolume,
67
- IconVolumeOff,
68
- PH_ICONS,
69
- type IconName,
12
+ type CanonicalIconName,
13
+ type IconProps,
70
14
  } from "@/components/icons";
71
- import { ShowcaseWrapper } from "@/components/ui";
15
+ import { Kbd, ShowcaseWrapper } from "@/components/ui";
72
16
 
73
- const iconCells: { name: IconName; Icon: (typeof PH_ICONS)[IconName] }[] = [
74
- { name: "activity", Icon: IconActivity },
75
- { name: "areaChart", Icon: IconAreaChart },
76
- { name: "arrowUp", Icon: IconArrowUp },
77
- { name: "arrowDown", Icon: IconArrowDown },
78
- { name: "bell", Icon: IconBell },
79
- { name: "bellOff", Icon: IconBellOff },
80
- { name: "bolt", Icon: IconBolt },
81
- { name: "box", Icon: IconBox },
82
- { name: "cancel", Icon: IconCancel },
83
- { name: "check", Icon: IconCheck },
84
- { name: "checkCircle", Icon: IconCheckCircle },
85
- { name: "chevronDown", Icon: IconChevronDown },
86
- { name: "chevronLeft", Icon: IconChevronLeft },
87
- { name: "chevronRight", Icon: IconChevronRight },
88
- { name: "clipboard", Icon: IconClipboardEdit },
89
- { name: "close", Icon: IconClose },
90
- { name: "cohort", Icon: IconCohort },
91
- { name: "copy", Icon: IconCopy },
92
- { name: "cumulativeChart", Icon: IconCumulativeChart },
93
- { name: "database", Icon: IconDatabase },
94
- { name: "download", Icon: IconDownload },
95
- { name: "edit", Icon: IconEdit },
96
- { name: "errorOutline", Icon: IconErrorOutline },
97
- { name: "exclamation", Icon: IconExclamation },
98
- { name: "flag", Icon: IconFlag },
99
- { name: "flare", Icon: IconFlare },
100
- { name: "flask", Icon: IconFlask },
101
- { name: "gift", Icon: IconGift },
102
- { name: "grid", Icon: IconGridView },
103
- { name: "handClick", Icon: IconHandClick },
104
- { name: "home", Icon: IconHome },
105
- { name: "layers", Icon: IconLayers },
106
- { name: "list", Icon: IconListView },
107
- { name: "logout", Icon: IconLogout },
108
- { name: "mail", Icon: IconMail },
109
- { name: "moon", Icon: IconMoon },
110
- { name: "panelRight", Icon: IconPanelRight },
111
- { name: "person", Icon: IconPerson },
112
- { name: "play", Icon: IconPlayCircle },
113
- { name: "plus", Icon: IconPlus },
114
- { name: "premium", Icon: IconPremium },
115
- { name: "queryEditor", Icon: IconQueryEditor },
116
- { name: "radar", Icon: IconRadar },
117
- { name: "recording", Icon: IconRecording },
118
- { name: "robot", Icon: IconRobot },
119
- { name: "rocket", Icon: IconRocket },
120
- { name: "save", Icon: IconSave },
121
- { name: "search", Icon: IconSearch },
122
- { name: "selectEvents", Icon: IconSelectEvents },
123
- { name: "settings", Icon: IconTuning },
124
- { name: "star", Icon: IconStar },
125
- { name: "subArrowRight", Icon: IconSubArrowRight },
126
- { name: "sun", Icon: IconSun },
127
- { name: "surveys", Icon: IconSurveys },
128
- { name: "sync", Icon: IconSync },
129
- { name: "tableChart", Icon: IconTableChart },
130
- { name: "terminal", Icon: IconTerminal },
131
- { name: "toggle", Icon: IconToggle },
132
- { name: "trash", Icon: IconTrash },
133
- { name: "upload", Icon: IconUpload },
134
- { name: "volume", Icon: IconVolume },
135
- { name: "volumeOff", Icon: IconVolumeOff },
136
- ];
17
+ const iconCells = Object.entries(CANONICAL_ICONS) as Array<[
18
+ CanonicalIconName,
19
+ ComponentType<IconProps>,
20
+ ]>;
21
+
22
+ const originalIcons = Object.entries(OLD_UI_ICONS) as Array<[
23
+ keyof typeof OLD_UI_ICONS,
24
+ ComponentType<IconProps>,
25
+ ]>;
137
26
 
138
- const colourTiles = [
139
- { label: "Session replay", Icon: IconRecording, fg: "--ph-product-session-replay" as const },
140
- { label: "Product analytics", Icon: IconAreaChart, fg: "--ph-product-product-analytics" as const },
141
- { label: "Data pipeline", Icon: IconGridView, fg: "--ph-product-data-pipeline" as const },
142
- { label: "Feature flags", Icon: IconFlag, fg: "--ph-product-feature-flags" as const },
143
- { label: "Experiments", Icon: IconFlask, fg: "--ph-product-experiments" as const },
144
- { label: "Data warehouse", Icon: IconDatabase, fg: "--ph-product-data-warehouse" as const },
145
- { label: "Surveys", Icon: IconSurveys, fg: "--ph-product-surveys" as const },
27
+ const productTiles = [
28
+ { label: "Session replay", Icon: IconRecording, color: "--ph-product-session-replay" },
29
+ { label: "Product analytics", Icon: IconAreaChart, color: "--ph-product-product-analytics" },
30
+ { label: "Data pipeline", Icon: IconGridView, color: "--ph-product-data-pipeline" },
31
+ { label: "Feature flags", Icon: IconFlag, color: "--ph-product-feature-flags" },
32
+ { label: "Experiments", Icon: IconFlask, color: "--ph-product-experiments" },
33
+ { label: "Data warehouse", Icon: IconDatabase, color: "--ph-product-data-warehouse" },
34
+ { label: "Surveys", Icon: IconSurveys, color: "--ph-product-surveys" },
146
35
  ];
147
36
 
148
37
  export default function IconShowcase() {
149
- const code = `import { IconActivity, IconAreaChart, IconArrowDown, IconArrowUp, IconBell, IconBellOff, IconBolt, IconBox, IconCancel, IconCheck, IconCheckCircle, IconChevronDown, IconChevronLeft, IconChevronRight, IconClipboardEdit, IconClose, IconCohort, IconCopy, IconCumulativeChart, IconDatabase, IconDownload, IconEdit, IconErrorOutline, IconExclamation, IconFlag, IconFlare, IconFlask, IconGift, IconGridView, IconHandClick, IconHome, IconLayers, IconListView, IconLogout, IconMail, IconMoon, IconPanelRight, IconPerson, IconPlayCircle, IconPlus, IconPremium, IconQueryEditor, IconRadar, IconRecording, IconRobot, IconRocket, IconSave, IconSearch, IconSelectEvents, IconStar, IconSubArrowRight, IconSun, IconSurveys, IconSync, IconTableChart, IconTerminal, IconToggle, IconTrash, IconTuning, IconUpload, IconVolume, IconVolumeOff, PH_ICONS, type IconName } from "the-old-ui";
150
-
151
- <div className="space-y-8">
152
- <div className="ph-panel space-y-4">
153
- <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Bespoke Lemon glyphs</h3>
154
- <p className="text-sm leading-relaxed text-ph-subtle">
155
- Paths mirror PostHog&apos;s Lemon UI — filled{" "}
156
- <code className="ph-kbd">currentColor</code> SVGs at <code className="ph-kbd">1em</code>, vendored in{" "}
157
- <code className="ph-kbd">the-old-ui</code> (no Lucide).
158
- </p>
159
-
160
- <div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8">
161
- {iconCells.map(({ name, Icon }) => (
162
- <div
163
- key={name}
164
- className="flex flex-col items-center gap-2 rounded-[var(--ph-radius-app)] border border-ph-border bg-ph-surface p-4 text-center"
165
- style={{ boxShadow: "var(--ph-shadow-elevation-3000)" }}
166
- >
167
- <Icon className="h-6 w-6 text-ph-ink" />
168
- <span className="text-[10px] font-medium uppercase tracking-wide text-ph-mutedtext">{name}</span>
169
- </div>
170
- ))}
171
- </div>
172
- </div>
38
+ const code = `import {
39
+ IconCommandCursor,
40
+ IconDataReel,
41
+ IconInsightBoard,
42
+ IconOldWindow,
43
+ } from "@xenide-io/the-old-ui-theme";
173
44
 
174
- <div className="ph-panel space-y-4">
175
- <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Product rail hues</h3>
176
- <p className="text-sm leading-relaxed text-ph-subtle">
177
- In-app nav tiles use saturated backgrounds with light glyphs — tokens map to{" "}
178
- <code className="ph-kbd">--ph-product-*</code> in <code className="ph-kbd">globals.css</code>.
179
- </p>
180
- <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7">
181
- {colourTiles.map(({ label, Icon, fg }) => (
182
- <div
183
- key={label}
184
- className="ph-icon-tile border-white/15 text-white"
185
- style={{ backgroundColor: \`var(\${fg})\` }}
186
- >
187
- <Icon className="mb-1 h-7 w-7 drop-shadow-sm" />
188
- <span className="max-w-full truncate px-1 text-center normal-case tracking-normal">{label}</span>
189
- </div>
190
- ))}
191
- </div>
192
- </div>
45
+ <div className="flex items-center gap-4 text-ph-ink">
46
+ <IconOldWindow size={16} />
47
+ <IconCommandCursor size={20} />
48
+ <IconDataReel size={24} />
49
+ <IconInsightBoard size={24} aria-label="Insights" />
193
50
  </div>`;
194
51
 
195
52
  return (
196
53
  <ShowcaseWrapper
197
54
  title="Icons"
198
- description="Bespoke Lemon glyphs from PostHog open source — filled Material / product paths, not Lucide stroke icons."
55
+ description="A canonical filled SVG registry with PostHog/Material foundations and clearly identified Old UI originals."
199
56
  code={code}
200
57
  filename="IconExample.tsx"
201
58
  >
202
59
  <div className="space-y-8">
203
- <div className="ph-panel space-y-4">
204
- <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Bespoke Lemon glyphs</h3>
205
- <p className="text-sm leading-relaxed text-ph-subtle">
206
- Paths mirror PostHog&apos;s Lemon UI — filled{" "}
207
- <code className="ph-kbd">currentColor</code> SVGs at <code className="ph-kbd">1em</code>, vendored in{" "}
208
- <code className="ph-kbd">the-old-ui</code> (no Lucide).
209
- </p>
60
+ <section className="ph-panel space-y-4">
61
+ <div>
62
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Old UI originals</h3>
63
+ <p className="mt-2 max-w-3xl text-sm leading-relaxed text-ph-subtle">
64
+ Authored 24px glyphs for retro interface and analytics concepts. Each uses <Kbd variant="token">currentColor</Kbd>, a filled silhouette, and enough negative space to survive at 16px.
65
+ </p>
66
+ </div>
67
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-6">
68
+ {originalIcons.map(([name, Icon]) => (
69
+ <div key={name} className="flex flex-col items-center gap-3 rounded-lg border border-ph-border-subtle bg-ph-raised p-4 text-center">
70
+ <div className="flex h-9 items-center gap-2 text-ph-ink">
71
+ <Icon size={16} />
72
+ <Icon size={24} />
73
+ </div>
74
+ <span className="max-w-full break-words text-xs font-medium text-ph-subtle">{formatName(name)}</span>
75
+ </div>
76
+ ))}
77
+ </div>
78
+ </section>
210
79
 
80
+ <section className="ph-panel space-y-4">
81
+ <div>
82
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Canonical registry</h3>
83
+ <p className="mt-2 max-w-3xl text-sm leading-relaxed text-ph-subtle">
84
+ Documentation is generated from <Kbd variant="token">CANONICAL_ICONS</Kbd>. Compatibility aliases remain available through <Kbd variant="token">PH_ICONS</Kbd> without duplicating this inventory.
85
+ </p>
86
+ </div>
211
87
  <div className="grid grid-cols-3 gap-3 sm:grid-cols-4 md:grid-cols-6 lg:grid-cols-8">
212
- {iconCells.map(({ name, Icon }) => (
213
- <div
214
- key={name}
215
- className="flex flex-col items-center gap-2 rounded-[var(--ph-radius-app)] border border-ph-border bg-ph-surface p-4 text-center"
216
- style={{ boxShadow: "var(--ph-shadow-elevation-3000)" }}
217
- >
218
- <Icon className="h-6 w-6 text-ph-ink" />
219
- <span className="text-[10px] font-medium uppercase tracking-wide text-ph-mutedtext">{name}</span>
88
+ {iconCells.map(([name, Icon]) => (
89
+ <div key={name} className="flex min-w-0 flex-col items-center gap-2 rounded-lg border border-ph-border-subtle bg-ph-raised p-3 text-center">
90
+ <div className="flex h-8 items-center gap-2 text-ph-ink">
91
+ <Icon size={16} />
92
+ <Icon size={24} />
93
+ </div>
94
+ <span className="max-w-full break-words text-[10px] font-medium tracking-wide text-ph-mutedtext">{name}</span>
220
95
  </div>
221
96
  ))}
222
97
  </div>
223
- </div>
98
+ </section>
224
99
 
225
- <div className="ph-panel space-y-4">
226
- <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Product rail hues</h3>
227
- <p className="text-sm leading-relaxed text-ph-subtle">
228
- In-app nav tiles use saturated backgrounds with light glyphs — tokens map to{" "}
229
- <code className="ph-kbd">--ph-product-*</code> in <code className="ph-kbd">globals.css</code>.
230
- </p>
100
+ <section className="ph-panel space-y-4">
101
+ <div>
102
+ <h3 className="text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Product hues</h3>
103
+ <p className="mt-2 max-w-3xl text-sm leading-relaxed text-ph-subtle">
104
+ Product color is used as a subtle tint and icon accent instead of assuming white foregrounds on every theme.
105
+ </p>
106
+ </div>
231
107
  <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7">
232
- {colourTiles.map(({ label, Icon, fg }) => (
108
+ {productTiles.map(({ label, Icon, color }) => (
233
109
  <div
234
110
  key={label}
235
- className="ph-icon-tile border-white/15 text-white"
236
- style={{ backgroundColor: `var(${fg})` }}
111
+ className="ph-icon-tile ph-product-icon-tile"
112
+ style={{ "--ph-icon-tile-color": `var(${color})` } as CSSProperties}
237
113
  >
238
- <Icon className="mb-1 h-7 w-7 drop-shadow-sm" />
239
- <span className="max-w-full truncate px-1 text-center normal-case tracking-normal">{label}</span>
114
+ <Icon className="mb-1" size={28} />
115
+ <span className="max-w-full px-1 text-center normal-case tracking-normal">{label}</span>
240
116
  </div>
241
117
  ))}
242
118
  </div>
243
- </div>
119
+ </section>
244
120
  </div>
245
121
  </ShowcaseWrapper>
246
122
  );
247
123
  }
124
+
125
+ function formatName(name: string) {
126
+ return name.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/^./, (letter) => letter.toUpperCase());
127
+ }
@@ -1,30 +1,56 @@
1
- import { Kbd, Panel, ShowcaseWrapper } from "@/components/ui";
1
+ import { ComponentDocs, Kbd, Panel, ShowcaseWrapper } from "@/components/ui";
2
2
 
3
3
  export default function KbdShowcase() {
4
- const code = `import { Kbd, Panel } from "the-old-ui";
4
+ const code = `import { Kbd, Panel } from "@xenide-io/the-old-ui-theme";
5
5
 
6
- <Panel className="flex flex-wrap items-center gap-3 text-ph-subtle">
7
- <Kbd keys={["⌘", "K"]} />
8
- <span className="text-xs font-semibold text-ph-mutedtext">/</span>
9
- <Kbd keys={["", "", "E"]} />
10
- <span className="text-xs font-semibold text-ph-mutedtext">/</span>
11
- <Kbd variant="token">HogQL</Kbd>
12
- <p className="w-full pt-5 text-xs text-ph-mutedtext">
13
- Keyboard hints and code tokens are rendered through the same reusable component.
14
- </p>
6
+ <Panel className="space-y-5">
7
+ <div className="flex flex-wrap items-center gap-4">
8
+ <Kbd keys={["Command", "K"]} platform="mac" />
9
+ <Kbd keys={["Control", "K"]} platform="windows" />
10
+ <Kbd keys={["Control", "K"]} />
11
+ </div>
12
+ <div className="flex flex-wrap items-center gap-3">
13
+ <Kbd variant="key">Escape</Kbd>
14
+ <Kbd variant="key">↵</Kbd>
15
+ <Kbd variant="token">HogQL</Kbd>
16
+ </div>
15
17
  </Panel>`;
16
18
 
17
19
  return (
18
- <ShowcaseWrapper title="Keyboard glyphs" code={code} filename="KbdExample.tsx">
19
- <Panel className="flex flex-wrap items-center gap-3 text-ph-subtle">
20
- <Kbd keys={["⌘", "K"]} />
21
- <span className="text-xs font-semibold text-ph-mutedtext">/</span>
22
- <Kbd keys={["", "⇧", "E"]} />
23
- <span className="text-xs font-semibold text-ph-mutedtext">/</span>
24
- <Kbd variant="token">HogQL</Kbd>
25
- <p className="w-full pt-5 text-xs text-ph-mutedtext">
26
- Keyboard hints and code tokens are rendered through the same reusable component.
27
- </p>
20
+ <ShowcaseWrapper
21
+ title="Keyboard glyphs"
22
+ description="PostHog-style joined shortcut caps with crisp lower borders, monospace type, and separate inline code tokens."
23
+ code={code}
24
+ filename="KbdExample.tsx"
25
+ docs={
26
+ <ComponentDocs
27
+ rows={[
28
+ { name: "variant", type: "shortcut | key | token", defaultValue: "shortcut", description: "Chooses a shortcut group, single physical key, or inline code token." },
29
+ { name: "keys", type: "readonly ReactNode[]", description: "Joins a shortcut chord inside one compact cap without stringifying React nodes." },
30
+ { name: "platform", type: "text | mac | windows", defaultValue: "text", description: "Converts named modifier keys only when explicitly requested." },
31
+ { name: "separator", type: "ReactNode", description: "Optional visual separator inside the joined shortcut cap." },
32
+ { name: "aria-label", type: "string", description: "Optional spoken shortcut override; textual keys generate one automatically." },
33
+ ]}
34
+ />
35
+ }
36
+ >
37
+ <Panel className="space-y-5">
38
+ <div>
39
+ <h3 className="mb-3 text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Platform shortcuts</h3>
40
+ <div className="flex flex-wrap items-center gap-4">
41
+ <Kbd keys={["Command", "K"]} platform="mac" />
42
+ <Kbd keys={["Control", "K"]} platform="windows" />
43
+ <Kbd keys={["Control", "K"]} />
44
+ </div>
45
+ </div>
46
+ <div>
47
+ <h3 className="mb-3 text-xs font-semibold uppercase tracking-wider text-ph-mutedtext">Keys and tokens</h3>
48
+ <div className="flex flex-wrap items-center gap-3">
49
+ <Kbd variant="key">Escape</Kbd>
50
+ <Kbd variant="key">↵</Kbd>
51
+ <Kbd variant="token">HogQL</Kbd>
52
+ </div>
53
+ </div>
28
54
  </Panel>
29
55
  </ShowcaseWrapper>
30
56
  );
@@ -9,8 +9,7 @@ export default function ModalShowcase() {
9
9
 
10
10
  const close = () => setOpenModal(null);
11
11
 
12
- const code = `import { IconCheck, IconClose, IconPlus } from "the-old-ui";
13
- import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
12
+ const code = `import { Button, IconCheck, IconClose, IconPlus, Input, Modal, Select, Textarea } from "@xenide-io/the-old-ui-theme";
14
13
 
15
14
  <div className="ph-panel space-y-4">
16
15
  <h3 className="text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Patterns</h3>
@@ -45,7 +44,7 @@ import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
45
44
  footer={<Button variant="success" onClick={close}>Continue</Button>}
46
45
  >
47
46
  <div className="flex items-start gap-3">
48
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-ph-success">
47
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--ph-success-bg)] text-[var(--ph-success-text)]">
49
48
  <IconCheck className="h-5 w-5" />
50
49
  </div>
51
50
  <div>
@@ -67,7 +66,7 @@ import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
67
66
  }
68
67
  >
69
68
  <div className="flex items-start gap-3">
70
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-red-100 text-ph-danger">
69
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--ph-danger-bg)] text-[var(--ph-danger-text)]">
71
70
  <IconClose className="h-5 w-5" />
72
71
  </div>
73
72
  <div>
@@ -114,6 +113,8 @@ import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
114
113
  { name: "size", type: "sm | md | lg | xl | full", defaultValue: "md", description: "Controls panel max width." },
115
114
  { name: "title", type: "string", description: "Accessible dialog heading." },
116
115
  { name: "footer", type: "ReactNode", description: "Renders action row with consistent spacing." },
116
+ { name: "closeOnOutsideClick", type: "boolean", defaultValue: "true", description: "Controls pointer dismissal outside the dialog." },
117
+ { name: "closeOnEscape", type: "boolean", defaultValue: "true", description: "Controls Escape-key dismissal." },
117
118
  ]}
118
119
  />
119
120
  }
@@ -151,7 +152,7 @@ import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
151
152
  footer={<Button variant="success" onClick={close}>Continue</Button>}
152
153
  >
153
154
  <div className="flex items-start gap-3">
154
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-ph-success">
155
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--ph-success-bg)] text-[var(--ph-success-text)]">
155
156
  <IconCheck className="h-5 w-5" />
156
157
  </div>
157
158
  <div>
@@ -173,7 +174,7 @@ import { Button, Input, Modal, Select, Textarea } from "the-old-ui";
173
174
  }
174
175
  >
175
176
  <div className="flex items-start gap-3">
176
- <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-red-100 text-ph-danger">
177
+ <div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-full bg-[var(--ph-danger-bg)] text-[var(--ph-danger-text)]">
177
178
  <IconClose className="h-5 w-5" />
178
179
  </div>
179
180
  <div>