@xenide-io/the-old-ui-theme 0.4.4 → 0.4.8

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 (76) hide show
  1. package/dist/{chunk-54ZR4VL5.mjs → chunk-RZXHZWUB.mjs} +44 -17
  2. package/dist/{index-B5NOyoKS.d.mts → index-Od4pIP4F.d.mts} +7 -1
  3. package/dist/{index-B5NOyoKS.d.ts → index-Od4pIP4F.d.ts} +7 -1
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +37 -10
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +152 -41
  9. package/dist/suite.d.ts +152 -41
  10. package/dist/suite.js +1366 -671
  11. package/dist/suite.mjs +1304 -610
  12. package/dist/ui.d.mts +1 -1
  13. package/dist/ui.d.ts +1 -1
  14. package/dist/ui.js +37 -10
  15. package/dist/ui.mjs +1 -1
  16. package/package.json +7 -3
  17. package/src/components/ui/Modal.tsx +70 -26
  18. package/src/styles/suite-skin.css +284 -24
  19. package/src/suite/components/ai-panel.test.ts +20 -0
  20. package/src/suite/components/ai-panel.tsx +494 -103
  21. package/src/suite/components/suite-app-layout.tsx +48 -28
  22. package/src/suite/components/suite-layout.tsx +84 -40
  23. package/src/suite/components/suite-mobile-drawer.tsx +35 -22
  24. package/src/suite/components/suite-notification-bell.tsx +9 -3
  25. package/src/suite/components/suite-settings-layout.tsx +82 -0
  26. package/src/suite/components/suite-settings-mobile-nav.tsx +18 -17
  27. package/src/suite/components/suite-skeleton.tsx +3 -3
  28. package/src/suite/components/today-calibrating.tsx +11 -35
  29. package/src/suite/components/today-page-frame.tsx +19 -11
  30. package/src/suite/components/today-ui.tsx +276 -19
  31. package/src/suite/index.ts +41 -25
  32. package/src/suite/lib/apps.ts +32 -2
  33. package/src/suite/lib/use-sidebar-width.ts +114 -0
  34. package/src/components/sections/AccordionShowcase.tsx +0 -45
  35. package/src/components/sections/AlertShowcase.tsx +0 -40
  36. package/src/components/sections/AvatarShowcase.tsx +0 -80
  37. package/src/components/sections/BadgeShowcase.tsx +0 -65
  38. package/src/components/sections/ButtonShowcase.tsx +0 -308
  39. package/src/components/sections/CalendarShowcase.tsx +0 -35
  40. package/src/components/sections/CardShowcase.tsx +0 -159
  41. package/src/components/sections/CodeMockupShowcase.tsx +0 -56
  42. package/src/components/sections/ColorPalette.tsx +0 -117
  43. package/src/components/sections/CommandPaletteShowcase.tsx +0 -48
  44. package/src/components/sections/CountdownShowcase.tsx +0 -43
  45. package/src/components/sections/DiffShowcase.tsx +0 -70
  46. package/src/components/sections/DrawerShowcase.tsx +0 -97
  47. package/src/components/sections/DropdownShowcase.tsx +0 -98
  48. package/src/components/sections/EmptyStateShowcase.tsx +0 -50
  49. package/src/components/sections/FilterChipsShowcase.tsx +0 -98
  50. package/src/components/sections/FormShowcase.tsx +0 -127
  51. package/src/components/sections/HoverCardShowcase.tsx +0 -50
  52. package/src/components/sections/IconShowcase.tsx +0 -121
  53. package/src/components/sections/IndicatorShowcase.tsx +0 -52
  54. package/src/components/sections/KbdShowcase.tsx +0 -57
  55. package/src/components/sections/ModalShowcase.tsx +0 -211
  56. package/src/components/sections/NavigationShowcase.tsx +0 -199
  57. package/src/components/sections/NewComponentsShowcase.tsx +0 -1052
  58. package/src/components/sections/ProductLayoutsShowcase.tsx +0 -78
  59. package/src/components/sections/ProgressShowcase.tsx +0 -82
  60. package/src/components/sections/QuillChartShowcase.tsx +0 -92
  61. package/src/components/sections/QuillDashboardShowcase.tsx +0 -149
  62. package/src/components/sections/SegmentedControlShowcase.tsx +0 -49
  63. package/src/components/sections/SetupThemeShowcase.tsx +0 -262
  64. package/src/components/sections/SidebarShowcase.tsx +0 -152
  65. package/src/components/sections/StackShowcase.tsx +0 -33
  66. package/src/components/sections/StepperShowcase.tsx +0 -37
  67. package/src/components/sections/SuiteShowcase.tsx +0 -669
  68. package/src/components/sections/SwapShowcase.tsx +0 -99
  69. package/src/components/sections/TabShowcase.tsx +0 -84
  70. package/src/components/sections/TableShowcase.tsx +0 -142
  71. package/src/components/sections/TimelineShowcase.tsx +0 -83
  72. package/src/components/sections/ToastShowcase.tsx +0 -108
  73. package/src/components/sections/TooltipShowcase.tsx +0 -38
  74. package/src/components/sections/UtilityShowcase.tsx +0 -81
  75. package/src/styles/excel-themes.css +0 -110
  76. package/src/styles/lemon-primitives.css +0 -550
@@ -5,7 +5,11 @@
5
5
  * suffixes (`switch-app-${slug}`, `suite-nav-${slug}`).
6
6
  */
7
7
 
8
- export type SuiteAppSlug = 'shellstack' | 'tides' | 'turtletime' | 'kraken';
8
+ export type SuiteAppSlug =
9
+ | 'shellstack'
10
+ | 'tides'
11
+ | 'turtletime'
12
+ | 'kraken';
9
13
 
10
14
  export interface SuiteAppDefinition {
11
15
  slug: SuiteAppSlug;
@@ -45,7 +49,7 @@ export const SUITE_APPS: readonly SuiteAppDefinition[] = [
45
49
  name: 'TurtleTime',
46
50
  description: 'Time tracking',
47
51
  icon: '/turtletime-icon.svg',
48
- landing: '/tracker',
52
+ landing: '/today',
49
53
  baseUrlEnv: 'NEXT_PUBLIC_TURTLETIME_URL',
50
54
  baseUrlFallback: 'http://localhost:3000',
51
55
  },
@@ -73,3 +77,29 @@ export function suiteAppBaseUrl(slug: SuiteAppSlug): string {
73
77
  typeof process !== 'undefined' ? process.env[def.baseUrlEnv] : undefined;
74
78
  return (fromEnv || def.baseUrlFallback).replace(/\/$/, '');
75
79
  }
80
+
81
+ const SOURCE_APP_SLUG: Record<string, SuiteAppSlug> = {
82
+ turtletime: 'turtletime',
83
+ tides: 'tides',
84
+ kraken: 'kraken',
85
+ portal: 'shellstack',
86
+ shellstack: 'shellstack',
87
+ };
88
+
89
+ /**
90
+ * Suite notification links are often stored as `/dashboard/...` relative to the
91
+ * *source* app. Resolving them with the current product origin opens the wrong
92
+ * app — prefix with that app's base URL instead.
93
+ */
94
+ export function resolveSuiteNotificationHref(
95
+ href: string | null | undefined,
96
+ sourceApp: string | null | undefined,
97
+ ): string {
98
+ const value = (href || '').trim();
99
+ if (!value) return '';
100
+ if (/^(https?:|mailto:|\/\/)/i.test(value)) return value;
101
+ const path = value.startsWith('/') ? value : `/${value}`;
102
+ const slug = SOURCE_APP_SLUG[(sourceApp || '').toLowerCase()];
103
+ if (!slug) return path;
104
+ return `${suiteAppBaseUrl(slug)}${path}`;
105
+ }
@@ -0,0 +1,114 @@
1
+ "use client";
2
+
3
+ import {
4
+ useCallback,
5
+ useEffect,
6
+ useState,
7
+ type PointerEvent as ReactPointerEvent,
8
+ } from "react";
9
+
10
+ export const SIDEBAR_RAIL_WIDTH = 56;
11
+ export const SIDEBAR_COLLAPSE_THRESHOLD = 80;
12
+ export const SIDEBAR_MIN_EXPANDED_WIDTH = 180;
13
+ export const SIDEBAR_MAX_WIDTH = 320;
14
+ export const SIDEBAR_DEFAULT_WIDTH = 320;
15
+
16
+ function clampWidth(width: number): number {
17
+ return Math.min(SIDEBAR_MAX_WIDTH, Math.max(SIDEBAR_RAIL_WIDTH, width));
18
+ }
19
+
20
+ function snapWidth(width: number): number {
21
+ if (width <= SIDEBAR_COLLAPSE_THRESHOLD) return SIDEBAR_RAIL_WIDTH;
22
+ if (width < SIDEBAR_MIN_EXPANDED_WIDTH) return SIDEBAR_MIN_EXPANDED_WIDTH;
23
+ return Math.min(SIDEBAR_MAX_WIDTH, width);
24
+ }
25
+
26
+ /**
27
+ * Shared suite sidebar resizing: drag below the threshold to collapse,
28
+ * drag the rail edge out to expand, and double-click to reset.
29
+ */
30
+ export function useSidebarWidth(storageKey: string) {
31
+ const [width, setWidth] = useState(SIDEBAR_DEFAULT_WIDTH);
32
+ const [narrowViewport, setNarrowViewport] = useState(false);
33
+ const collapsed = narrowViewport || width <= SIDEBAR_COLLAPSE_THRESHOLD;
34
+
35
+ useEffect(() => {
36
+ let stored: string | null = null;
37
+ try {
38
+ stored = localStorage.getItem(storageKey);
39
+ } catch {
40
+ // Storage can be unavailable in private or restricted browser contexts.
41
+ }
42
+ if (!stored) return;
43
+ const parsed = Number.parseInt(stored, 10);
44
+ if (!Number.isNaN(parsed)) {
45
+ queueMicrotask(() => setWidth(snapWidth(clampWidth(parsed))));
46
+ }
47
+ }, [storageKey]);
48
+
49
+ useEffect(() => {
50
+ const media = window.matchMedia("(max-width: 639px)");
51
+ const sync = () => setNarrowViewport(media.matches);
52
+ sync();
53
+ media.addEventListener("change", sync);
54
+ return () => media.removeEventListener("change", sync);
55
+ }, []);
56
+
57
+ const persist = useCallback(
58
+ (value: number) => {
59
+ try {
60
+ localStorage.setItem(storageKey, String(value));
61
+ } catch {
62
+ // A persisted preference is optional; resizing should still work.
63
+ }
64
+ },
65
+ [storageKey],
66
+ );
67
+
68
+ const setCollapsed = useCallback(
69
+ (value: boolean) => {
70
+ setWidth((current) => {
71
+ const next = value
72
+ ? SIDEBAR_RAIL_WIDTH
73
+ : current <= SIDEBAR_COLLAPSE_THRESHOLD
74
+ ? SIDEBAR_DEFAULT_WIDTH
75
+ : snapWidth(current);
76
+ persist(next);
77
+ return next;
78
+ });
79
+ },
80
+ [persist],
81
+ );
82
+
83
+ const resetWidth = useCallback(() => {
84
+ setWidth(SIDEBAR_DEFAULT_WIDTH);
85
+ persist(SIDEBAR_DEFAULT_WIDTH);
86
+ }, [persist]);
87
+
88
+ const startResize = useCallback(
89
+ (event: ReactPointerEvent<HTMLDivElement>) => {
90
+ event.preventDefault();
91
+ const startX = event.clientX;
92
+ const startWidth = width;
93
+
94
+ const onMove = (moveEvent: PointerEvent) => {
95
+ setWidth(clampWidth(startWidth + moveEvent.clientX - startX));
96
+ };
97
+ const onUp = () => {
98
+ document.removeEventListener("pointermove", onMove);
99
+ document.removeEventListener("pointerup", onUp);
100
+ setWidth((current) => {
101
+ const snapped = snapWidth(current);
102
+ persist(snapped);
103
+ return snapped;
104
+ });
105
+ };
106
+
107
+ document.addEventListener("pointermove", onMove);
108
+ document.addEventListener("pointerup", onUp);
109
+ },
110
+ [persist, width],
111
+ );
112
+
113
+ return { width, collapsed, setCollapsed, resetWidth, startResize };
114
+ }
@@ -1,45 +0,0 @@
1
- import { Accordion, ComponentDocs, ShowcaseWrapper } from "@/components/ui";
2
-
3
- export default function AccordionShowcase() {
4
- const items = [
5
- {
6
- id: "1",
7
- title: "What is The Old UI?",
8
- content: "The Old UI is a comprehensive component library with swappable themes, bespoke icons, and dashboard-ready components built with Tailwind CSS.",
9
- },
10
- {
11
- id: "2",
12
- title: "How do I install it?",
13
- content: "Simply copy the component files into your project. Each component is self-contained and uses Tailwind CSS utility classes.",
14
- },
15
- {
16
- id: "3",
17
- title: "Can I customize themes?",
18
- content: "Yes! The Old UI supports multiple themes including TurtleTime, HedgeHog Light, Sheets, and more. Themes are CSS-variable based.",
19
- },
20
- ];
21
-
22
- const code = `import { Accordion } from "the-old-ui";
23
-
24
- <Accordion items={items} allowMultiple defaultOpen={["1"]} />`;
25
-
26
- return (
27
- <ShowcaseWrapper
28
- title="Accordion"
29
- description="Disclosure list with controlled defaults for single or multiple open items."
30
- code={code}
31
- filename="AccordionExample.tsx"
32
- docs={
33
- <ComponentDocs
34
- rows={[
35
- { name: "items", type: "{ id; title; content }[]", description: "Accordion panels to render." },
36
- { name: "allowMultiple", type: "boolean", defaultValue: "false", description: "Allows more than one panel open." },
37
- { name: "defaultOpen", type: "string[]", description: "Initial open item ids." },
38
- ]}
39
- />
40
- }
41
- >
42
- <Accordion items={items} allowMultiple defaultOpen={["1"]} />
43
- </ShowcaseWrapper>
44
- );
45
- }
@@ -1,40 +0,0 @@
1
- import { Alert, ComponentDocs, ShowcaseWrapper } from "@/components/ui";
2
-
3
- export default function AlertShowcase() {
4
- const code = `import { Alert } from "@xenide-io/the-old-ui-theme";
5
-
6
- <div className="space-y-4">
7
- <Alert status="info" title="Pipeline delay" description="Events are flowing; warehouse sync may lag by ~2 minutes." />
8
- <Alert status="success" title="Flag shipped" description="The multivariate rollout finished for all environments." />
9
- <Alert status="warning" title="Sample rate adjusted" description="Replay capture dropped to 20% until billing resets." />
10
- <Alert status="danger" title="Exporter error" description="Snowflake credential rotation failed — check IAM role ARN." />
11
- </div>`;
12
-
13
- return (
14
- <ShowcaseWrapper
15
- title="Alerts"
16
- description="Status messages with built-in icon, title, description, and optional dismiss action."
17
- code={code}
18
- filename="AlertExample.tsx"
19
- docs={
20
- <ComponentDocs
21
- rows={[
22
- { name: "status", type: "info | success | warning | danger", defaultValue: "info", description: "Controls icon, border, and semantic colour." },
23
- { name: "title", type: "string", description: "Short message title." },
24
- { name: "description", type: "string", description: "Supporting message copy." },
25
- { name: "onDismiss", type: "() => void", description: "Shows a close button when provided." },
26
- { name: "icon", type: "ReactNode", description: "Optional custom icon override." },
27
- { name: "live", type: "off | polite | assertive", description: "Opt into live-region announcements for content that appears dynamically." },
28
- ]}
29
- />
30
- }
31
- >
32
- <div className="space-y-4">
33
- <Alert status="info" title="Pipeline delay" description="Events are flowing; warehouse sync may lag by ~2 minutes." />
34
- <Alert status="success" title="Flag shipped" description="The multivariate rollout finished for all environments." />
35
- <Alert status="warning" title="Sample rate adjusted" description="Replay capture dropped to 20% until billing resets." />
36
- <Alert status="danger" title="Exporter error" description="Snowflake credential rotation failed — check IAM role ARN." />
37
- </div>
38
- </ShowcaseWrapper>
39
- );
40
- }
@@ -1,80 +0,0 @@
1
- import { Avatar, AvatarGroup, ComponentDocs, ShowcaseWrapper } from "@/components/ui";
2
-
3
- export default function AvatarShowcase() {
4
- const code = `import { Avatar, AvatarGroup } from "the-old-ui";
5
-
6
- <div className="grid gap-8 lg:grid-cols-2">
7
- <div className="ph-panel space-y-4">
8
- <h3>Sizes</h3>
9
- <Avatar label="XL" size="xl" color="bg-ph-brand text-white" />
10
- <Avatar label="LG" size="lg" color="bg-ph-blue text-white" />
11
- <Avatar label="MD" size="md" color="bg-ph-purple text-white" />
12
- <Avatar label="SM" size="sm" color="bg-ph-toolbar text-ph-ink" />
13
- </div>
14
-
15
- <div className="ph-panel space-y-5">
16
- <h3>Stacked collaborators</h3>
17
- <AvatarGroup>
18
- <Avatar label="MJ" color="bg-orange-400 text-orange-950" />
19
- <Avatar label="AR" color="bg-sky-500 text-white" />
20
- <Avatar label="CK" color="bg-violet-600 text-white" />
21
- </AvatarGroup>
22
-
23
- <div className="flex gap-4">
24
- <Avatar label="ON" color="bg-emerald-200 text-emerald-950" status="online" />
25
- <Avatar label="JD" color="bg-ph-muted text-ph-ink" status="offline" />
26
- <Avatar label="CP" color="bg-amber-200 text-amber-950" badge="12" />
27
- </div>
28
- </div>
29
- </div>`;
30
-
31
- return (
32
- <ShowcaseWrapper
33
- title="Avatars"
34
- description="Reusable avatar component with sizes, status indicators, badges, and group stacking."
35
- code={code}
36
- filename="AvatarExample.tsx"
37
- docs={
38
- <ComponentDocs
39
- rows={[
40
- { name: "label", type: "string", description: "Text initials or label." },
41
- { name: "size", type: "xs | sm | md | lg | xl", defaultValue: "md", description: "Avatar size." },
42
- { name: "color", type: "string", defaultValue: "bg-ph-brand text-white", description: "Tailwind colour classes." },
43
- { name: "status", type: "online | offline | away | busy", description: "Status indicator dot." },
44
- { name: "badge", type: "string", description: "Badge count/text." },
45
- { name: "icon", type: "ReactNode", description: "Optional icon or image." },
46
- ]}
47
- />
48
- }
49
- >
50
- <div className="grid gap-8 lg:grid-cols-2">
51
- <div className="ph-panel space-y-4">
52
- <h3 className="text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Sizes</h3>
53
- <div className="flex flex-wrap items-end gap-5">
54
- <Avatar label="XL" size="xl" color="bg-ph-brand text-white" />
55
- <Avatar label="LG" size="lg" color="bg-ph-blue text-white" />
56
- <Avatar label="MD" size="md" color="bg-ph-purple text-white" />
57
- <Avatar label="SM" size="sm" color="bg-ph-toolbar text-ph-ink" />
58
- <Avatar label="XS" size="xs" color="bg-ph-muted text-ph-ink" />
59
- </div>
60
- </div>
61
-
62
- <div className="ph-panel space-y-5">
63
- <h3 className="text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Stacked collaborators</h3>
64
- <AvatarGroup max={3}>
65
- <Avatar label="MJ" color="bg-orange-400 text-orange-950" />
66
- <Avatar label="AR" color="bg-sky-500 text-white" />
67
- <Avatar label="CK" color="bg-violet-600 text-white" />
68
- <Avatar label="+9" color="bg-ph-toolbar text-ph-subtle" />
69
- </AvatarGroup>
70
-
71
- <div className="flex gap-4">
72
- <Avatar label="ON" color="bg-emerald-200 text-emerald-950" status="online" />
73
- <Avatar label="JD" color="bg-ph-muted text-ph-ink" status="offline" />
74
- <Avatar label="CP" color="bg-amber-200 text-amber-950" badge="12" />
75
- </div>
76
- </div>
77
- </div>
78
- </ShowcaseWrapper>
79
- );
80
- }
@@ -1,65 +0,0 @@
1
- import { Badge, ComponentDocs, ShowcaseWrapper } from "@/components/ui";
2
-
3
- export default function BadgeShowcase() {
4
- const code = `import { Badge } from "@xenide-io/the-old-ui-theme";
5
-
6
- <div className="ph-panel space-y-6">
7
- <div>
8
- <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Semantic</h3>
9
- <div className="flex flex-wrap gap-2">
10
- <Badge variant="brand">Beta</Badge>
11
- <Badge variant="neutral">Archived</Badge>
12
- <Badge variant="success">Live</Badge>
13
- <Badge variant="warning">Queued</Badge>
14
- <Badge variant="danger">Failed</Badge>
15
- <Badge variant="info">Docs</Badge>
16
- </div>
17
- </div>
18
- <div>
19
- <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Outline</h3>
20
- <div className="flex flex-wrap gap-2">
21
- <Badge variant="outline">Feature flag</Badge>
22
- <Badge variant="outline">Experiment</Badge>
23
- </div>
24
- </div>
25
- </div>`;
26
-
27
- return (
28
- <ShowcaseWrapper
29
- title="Badges"
30
- description="Use badges for compact status, category, and product metadata. Defaults work without classes; use props for variants."
31
- code={code}
32
- filename="BadgeExample.tsx"
33
- docs={
34
- <ComponentDocs
35
- rows={[
36
- { name: "variant", type: "brand | neutral | success | warning | danger | info | outline", defaultValue: "neutral", description: "Controls semantic colour and border treatment." },
37
- { name: "size", type: "sm | md", defaultValue: "md", description: "Use sm for dense tables or compact nav." },
38
- { name: "className", type: "string", description: "Optional escape hatch for layout-only overrides." },
39
- ]}
40
- />
41
- }
42
- >
43
- <div className="ph-panel space-y-6">
44
- <div>
45
- <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Semantic</h3>
46
- <div className="flex flex-wrap gap-2">
47
- <Badge variant="brand">Beta</Badge>
48
- <Badge variant="neutral">Archived</Badge>
49
- <Badge variant="success">Live</Badge>
50
- <Badge variant="warning">Queued</Badge>
51
- <Badge variant="danger">Failed</Badge>
52
- <Badge variant="info">Docs</Badge>
53
- </div>
54
- </div>
55
- <div>
56
- <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-ph-mutedtext">Outline</h3>
57
- <div className="flex flex-wrap gap-2">
58
- <Badge variant="outline">Feature flag</Badge>
59
- <Badge variant="outline">Experiment</Badge>
60
- </div>
61
- </div>
62
- </div>
63
- </ShowcaseWrapper>
64
- );
65
- }