@xenide-io/the-old-ui-theme 0.9.18 → 0.10.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 (69) hide show
  1. package/dist/{Chip-BzuNgJIa.d.mts → Chip-DKT-82Lf.d.mts} +5 -3
  2. package/dist/{Chip-BzuNgJIa.d.ts → Chip-DKT-82Lf.d.ts} +5 -3
  3. package/dist/{chunk-6SVJAU5K.mjs → chunk-LHE4ROON.mjs} +977 -521
  4. package/dist/index.d.mts +2 -2
  5. package/dist/index.d.ts +2 -2
  6. package/dist/index.js +138 -99
  7. package/dist/index.mjs +1 -1
  8. package/dist/suite.d.mts +23 -1
  9. package/dist/suite.d.ts +23 -1
  10. package/dist/suite.js +4248 -4039
  11. package/dist/suite.mjs +1006 -793
  12. package/dist/tailwind-preset.js +10 -1
  13. package/dist/tailwind-preset.mjs +10 -1
  14. package/dist/ui.d.mts +33 -4
  15. package/dist/ui.d.ts +33 -4
  16. package/dist/ui.js +1024 -566
  17. package/dist/ui.mjs +13 -1
  18. package/docs/macos-identity-audit.md +279 -0
  19. package/package.json +1 -1
  20. package/src/app/globals.css +334 -44
  21. package/src/components/ui/Accordion.tsx +4 -7
  22. package/src/components/ui/Avatar.tsx +5 -5
  23. package/src/components/ui/Calendar.tsx +7 -7
  24. package/src/components/ui/CodeBlock.tsx +3 -10
  25. package/src/components/ui/CollapsibleSection.tsx +4 -4
  26. package/src/components/ui/CommandPalette.tsx +7 -10
  27. package/src/components/ui/EmptyState.tsx +2 -2
  28. package/src/components/ui/Input.tsx +1 -1
  29. package/src/components/ui/Marketing.tsx +128 -0
  30. package/src/components/ui/MarketingIllustrations.tsx +287 -0
  31. package/src/components/ui/Panel.tsx +1 -1
  32. package/src/components/ui/SegmentedControl.tsx +59 -13
  33. package/src/components/ui/ShowcaseWrapper.tsx +3 -3
  34. package/src/components/ui/Table.tsx +18 -6
  35. package/src/components/ui/Typography.tsx +4 -4
  36. package/src/components/ui/avatar-stat.test.tsx +14 -1
  37. package/src/components/ui/index.ts +13 -12
  38. package/src/components/ui/interaction-primitives.test.tsx +12 -6
  39. package/src/components/ui/marketing.test.tsx +39 -0
  40. package/src/components/ui/table.test.tsx +20 -0
  41. package/src/styles/suite-skin.css +405 -40
  42. package/src/styles/themes.css +45 -45
  43. package/src/styles/typography.css +25 -1
  44. package/src/suite/components/ai-panel.tsx +17 -17
  45. package/src/suite/components/app-switcher.tsx +9 -9
  46. package/src/suite/components/suite-app-layout.tsx +7 -4
  47. package/src/suite/components/suite-bottom-nav.tsx +1 -1
  48. package/src/suite/components/suite-clients-projects-directory.tsx +6 -6
  49. package/src/suite/components/suite-integration-picker.tsx +7 -7
  50. package/src/suite/components/suite-integration-rules.tsx +5 -5
  51. package/src/suite/components/suite-layout.test.tsx +32 -3
  52. package/src/suite/components/suite-layout.tsx +4 -4
  53. package/src/suite/components/suite-mobile-drawer.tsx +2 -2
  54. package/src/suite/components/suite-mobile-header.tsx +1 -1
  55. package/src/suite/components/suite-notification-bell.tsx +4 -4
  56. package/src/suite/components/suite-notification-settings.tsx +171 -0
  57. package/src/suite/components/suite-palette-library.test.tsx +46 -0
  58. package/src/suite/components/suite-palette-library.tsx +82 -15
  59. package/src/suite/components/suite-settings-layout.tsx +2 -2
  60. package/src/suite/components/suite-settings-mobile-nav.tsx +5 -9
  61. package/src/suite/components/suite-sidebar.test.tsx +39 -0
  62. package/src/suite/components/suite-sidebar.tsx +41 -19
  63. package/src/suite/components/suite-skeleton.tsx +6 -6
  64. package/src/suite/components/suite-user-menu.tsx +2 -2
  65. package/src/suite/components/today-calibrating.tsx +3 -3
  66. package/src/suite/components/today-ui.tsx +12 -12
  67. package/src/suite/components/workspace-switcher.tsx +3 -3
  68. package/src/suite/index.ts +6 -0
  69. package/tailwind-preset.ts +9 -0
@@ -1,11 +1,12 @@
1
1
  "use client";
2
2
 
3
+ import { useRef, type KeyboardEvent, type ReactNode } from "react";
3
4
  import { cn } from "@/lib/cn";
4
5
 
5
6
  export interface SegmentedControlOption {
6
7
  value: string;
7
8
  label: string;
8
- icon?: React.ReactNode;
9
+ icon?: ReactNode;
9
10
  }
10
11
 
11
12
  export interface SegmentedControlProps {
@@ -13,30 +14,75 @@ export interface SegmentedControlProps {
13
14
  value: string;
14
15
  onChange: (value: string) => void;
15
16
  className?: string;
17
+ "aria-label"?: string;
16
18
  }
17
19
 
18
- export function SegmentedControl({ options, value, onChange, className }: SegmentedControlProps) {
20
+ export function SegmentedControl({
21
+ options,
22
+ value,
23
+ onChange,
24
+ className,
25
+ "aria-label": ariaLabel,
26
+ }: SegmentedControlProps) {
27
+ const itemRefs = useRef<Array<HTMLButtonElement | null>>([]);
28
+ const selectedIndex = options.findIndex((option) => option.value === value);
29
+
30
+ const handleKeyDown = (
31
+ event: KeyboardEvent<HTMLButtonElement>,
32
+ index: number,
33
+ ) => {
34
+ let nextIndex: number;
35
+
36
+ switch (event.key) {
37
+ case "ArrowLeft":
38
+ case "ArrowUp":
39
+ nextIndex = (index - 1 + options.length) % options.length;
40
+ break;
41
+ case "ArrowRight":
42
+ case "ArrowDown":
43
+ nextIndex = (index + 1) % options.length;
44
+ break;
45
+ case "Home":
46
+ nextIndex = 0;
47
+ break;
48
+ case "End":
49
+ nextIndex = options.length - 1;
50
+ break;
51
+ default:
52
+ return;
53
+ }
54
+
55
+ event.preventDefault();
56
+ const nextOption = options[nextIndex];
57
+ if (!nextOption) return;
58
+ onChange(nextOption.value);
59
+ itemRefs.current[nextIndex]?.focus();
60
+ };
61
+
19
62
  return (
20
63
  <div
21
- className={cn(
22
- "inline-flex rounded-lg bg-ph-muted p-1",
23
- className
24
- )}
64
+ role="radiogroup"
65
+ aria-label={ariaLabel}
66
+ className={cn("ph-segmented-control", className)}
25
67
  >
26
- {options.map((option) => (
68
+ {options.map((option, index) => (
27
69
  <button
28
70
  key={option.value}
71
+ ref={(node) => {
72
+ itemRefs.current[index] = node;
73
+ }}
29
74
  type="button"
30
- aria-pressed={value === option.value}
75
+ role="radio"
76
+ aria-checked={value === option.value}
77
+ tabIndex={selectedIndex === index || (selectedIndex < 0 && index === 0) ? 0 : -1}
31
78
  onClick={() => onChange(option.value)}
79
+ onKeyDown={(event) => handleKeyDown(event, index)}
32
80
  className={cn(
33
- "relative flex min-h-11 items-center gap-1.5 rounded-md px-3.5 py-1.5 text-sm font-medium transition-all sm:min-h-9",
34
- value === option.value
35
- ? "bg-ph-surface text-ph-ink shadow-ph"
36
- : "text-ph-subtle hover:text-ph-ink"
81
+ "ph-segmented-control__item",
82
+ value === option.value && "ph-segmented-control__item--selected",
37
83
  )}
38
84
  >
39
- {option.icon && <span className="h-4 w-4">{option.icon}</span>}
85
+ {option.icon && <span className="h-4 w-4" aria-hidden>{option.icon}</span>}
40
86
  {option.label}
41
87
  </button>
42
88
  ))}
@@ -38,9 +38,9 @@ export function ShowcaseWrapper({
38
38
  type="button"
39
39
  onClick={() => setShowCode(!showCode)}
40
40
  className={cn(
41
- "flex h-10 w-10 items-center justify-center rounded-full border transition-all",
41
+ "ph-showcase-toggle flex h-10 w-10 items-center justify-center rounded-full border transition-colors",
42
42
  showCode
43
- ? "border-ph-brand bg-ph-brand text-[var(--ph-on-accent)] shadow-lg shadow-ph-brand/25"
43
+ ? "border-ph-brand bg-ph-brand text-[var(--ph-on-accent)] shadow-[var(--ph-shadow-control)]"
44
44
  : "border-ph-border bg-ph-surface text-ph-subtle hover:border-ph-brand hover:text-ph-brand"
45
45
  )}
46
46
  title={showCode ? "Hide code" : "Show code"}
@@ -68,7 +68,7 @@ export function ShowcaseWrapper({
68
68
 
69
69
  <div
70
70
  className={cn(
71
- "transition-all duration-300",
71
+ "transition-opacity duration-200",
72
72
  showCode
73
73
  ? "block opacity-100"
74
74
  : "hidden opacity-0 lg:hidden"
@@ -20,6 +20,7 @@ export interface TableProps<T> {
20
20
  getRowKey?: (row: T, index: number) => Key;
21
21
  className?: string;
22
22
  caption?: string;
23
+ emptyState?: ReactNode;
23
24
  }
24
25
 
25
26
  export function Table<T>({
@@ -32,10 +33,13 @@ export function Table<T>({
32
33
  getRowKey,
33
34
  className,
34
35
  caption,
36
+ emptyState = "No data available.",
35
37
  }: TableProps<T>) {
36
38
  return (
37
39
  <div
38
40
  className={cn("ph-table-wrap", compact && "ph-table-compact", className)}
41
+ role="region"
42
+ aria-label={caption ?? "Data table"}
39
43
  tabIndex={0}
40
44
  >
41
45
  <table className={cn("ph-table", zebra && "ph-table-zebra", ribbon && "ph-table--ribbon")}>
@@ -48,13 +52,21 @@ export function Table<T>({
48
52
  </tr>
49
53
  </thead>
50
54
  <tbody>
51
- {data.map((row, i) => (
52
- <tr key={getRowKey?.(row, i) ?? i} style={getRowStyle?.(row)}>
53
- {columns.map((col) => (
54
- <td key={col.key} className={col.className}>{col.cell(row)}</td>
55
- ))}
55
+ {data.length === 0 ? (
56
+ <tr>
57
+ <td colSpan={columns.length} className="ph-table-empty">
58
+ {emptyState}
59
+ </td>
56
60
  </tr>
57
- ))}
61
+ ) : (
62
+ data.map((row, i) => (
63
+ <tr key={getRowKey?.(row, i) ?? i} style={getRowStyle?.(row)}>
64
+ {columns.map((col) => (
65
+ <td key={col.key} className={col.className}>{col.cell(row)}</td>
66
+ ))}
67
+ </tr>
68
+ ))
69
+ )}
58
70
  </tbody>
59
71
  </table>
60
72
  </div>
@@ -99,7 +99,7 @@ export const H1 = forwardRef<HTMLHeadingElement, H1Props>(function H1(
99
99
  { children, tone = "default", weight, truncate, className, ...props },
100
100
  ref,
101
101
  ) {
102
- return <h1 ref={ref} {...props} className={textClass("text-3xl font-bold tracking-tight md:text-4xl", tone, weight, truncate, className)}>{children}</h1>;
102
+ return <h1 ref={ref} {...props} className={textClass("ph-page-title font-bold", tone, weight, truncate, className)}>{children}</h1>;
103
103
  });
104
104
 
105
105
  /** H2 — section title */
@@ -107,7 +107,7 @@ export const H2 = forwardRef<HTMLHeadingElement, H2Props>(function H2(
107
107
  { children, tone = "default", weight, truncate, className, ...props },
108
108
  ref,
109
109
  ) {
110
- return <h2 ref={ref} {...props} className={textClass("text-2xl font-bold tracking-tight", tone, weight, truncate, className)}>{children}</h2>;
110
+ return <h2 ref={ref} {...props} className={textClass("text-2xl font-semibold tracking-tight", tone, weight, truncate, className)}>{children}</h2>;
111
111
  });
112
112
 
113
113
  /** H3 — subsection title */
@@ -155,7 +155,7 @@ export const Caption = forwardRef<HTMLSpanElement, CaptionProps>(function Captio
155
155
  { children, tone = "muted", weight, truncate, className, ...props },
156
156
  ref,
157
157
  ) {
158
- return <span ref={ref} {...props} className={textClass("text-[11px] font-medium uppercase tracking-wider", tone, weight, truncate, className)}>{children}</span>;
158
+ return <span ref={ref} {...props} className={textClass("text-xs font-medium", tone, weight, truncate, className)}>{children}</span>;
159
159
  });
160
160
 
161
161
  /** Overline — tiny eyebrow text */
@@ -163,7 +163,7 @@ export const Overline = forwardRef<HTMLSpanElement, OverlineProps>(function Over
163
163
  { children, tone = "muted", weight, truncate, className, ...props },
164
164
  ref,
165
165
  ) {
166
- return <span ref={ref} {...props} className={textClass("text-[10px] font-bold uppercase tracking-[0.15em]", tone, weight, truncate, className)}>{children}</span>;
166
+ return <span ref={ref} {...props} className={textClass("text-[length:var(--ph-type-overline)] font-semibold uppercase tracking-[0.08em]", tone, weight, truncate, className)}>{children}</span>;
167
167
  });
168
168
 
169
169
  /** Lead — intro paragraph under a Display/SectionTitle; measure-capped for readability */
@@ -1,6 +1,6 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import { describe, expect, it } from "vitest";
3
- import { Avatar } from "@/components/ui/Avatar";
3
+ import { Avatar, AvatarGroup } from "@/components/ui/Avatar";
4
4
  import { Stat } from "@/components/ui/Stat";
5
5
 
6
6
  describe("Avatar status", () => {
@@ -10,6 +10,19 @@ describe("Avatar status", () => {
10
10
  const status = screen.getByRole("img", { name: "online status" });
11
11
  expect(status).toHaveClass("bg-ph-success", "border-ph-surface");
12
12
  });
13
+
14
+ it("uses semantic accent contrast and counts grouped children", () => {
15
+ const { container } = render(
16
+ <AvatarGroup max={1}>
17
+ <Avatar label="JD" />
18
+ <Avatar label="AR" />
19
+ </AvatarGroup>,
20
+ );
21
+
22
+ expect(container.querySelector(".ph-avatar")).toHaveClass("text-[var(--ph-on-accent)]");
23
+ expect(screen.getByText("+1")).toBeInTheDocument();
24
+ expect(container.querySelectorAll(".ph-avatar")).toHaveLength(2);
25
+ });
13
26
  });
14
27
 
15
28
  describe("Stat warning", () => {
@@ -17,6 +17,16 @@ export {
17
17
  ButtonChrome as ButtonChrome,
18
18
  type ButtonChromeProps as ButtonChromeProps,
19
19
  } from "@/components/ui/ButtonChrome";
20
+ export {
21
+ MarketingHeader,
22
+ MarketingLink,
23
+ MarketingMotionControl,
24
+ MarketingReveal,
25
+ } from "@/components/ui/Marketing";
26
+ export {
27
+ AiWritingIllustration,
28
+ IntegrationBridgeIllustration,
29
+ } from "@/components/ui/MarketingIllustrations";
20
30
  export {
21
31
  Alert as Alert,
22
32
  type AlertProps as AlertProps,
@@ -158,10 +168,7 @@ export {
158
168
  type CommandItem,
159
169
  type CommandPaletteProps,
160
170
  } from "@/components/ui/CommandPalette";
161
- export {
162
- Calendar,
163
- type CalendarProps,
164
- } from "@/components/ui/Calendar";
171
+ export { Calendar, type CalendarProps } from "@/components/ui/Calendar";
165
172
  export {
166
173
  Tooltip,
167
174
  TooltipProvider,
@@ -170,10 +177,7 @@ export {
170
177
  type TooltipProviderProps,
171
178
  type TooltipSide,
172
179
  } from "@/components/ui/Tooltip";
173
- export {
174
- EmptyState,
175
- type EmptyStateProps,
176
- } from "@/components/ui/EmptyState";
180
+ export { EmptyState, type EmptyStateProps } from "@/components/ui/EmptyState";
177
181
  export {
178
182
  FilterChips,
179
183
  type FilterChip,
@@ -256,10 +260,7 @@ export {
256
260
  Spinner as Spinner,
257
261
  type SpinnerProps as SpinnerProps,
258
262
  } from "@/components/ui/Spinner";
259
- export {
260
- Dot as Dot,
261
- type DotProps as DotProps,
262
- } from "@/components/ui/Dot";
263
+ export { Dot as Dot, type DotProps as DotProps } from "@/components/ui/Dot";
263
264
  export {
264
265
  Chip as Chip,
265
266
  type ChipProps as ChipProps,
@@ -97,13 +97,14 @@ describe("chip", () => {
97
97
  });
98
98
 
99
99
  describe("segmented control", () => {
100
- it("exposes and updates the selected option as a pressed button", async () => {
100
+ it("exposes a radio group and moves selection with arrow keys", async () => {
101
101
  const user = userEvent.setup();
102
102
 
103
103
  function Fixture() {
104
104
  const [value, setValue] = useState("board");
105
105
  return (
106
106
  <SegmentedControl
107
+ aria-label="View"
107
108
  value={value}
108
109
  onChange={setValue}
109
110
  options={[
@@ -115,15 +116,20 @@ describe("segmented control", () => {
115
116
  }
116
117
 
117
118
  render(<Fixture />);
118
- expect(screen.getByRole("button", { name: "Board" })).toHaveAttribute(
119
- "aria-pressed",
119
+ expect(screen.getByRole("radiogroup", { name: "View" })).toBeInTheDocument();
120
+ const board = screen.getByRole("radio", { name: "Board" });
121
+ expect(board).toHaveAttribute(
122
+ "aria-checked",
120
123
  "true",
121
124
  );
122
125
 
123
- await user.click(screen.getByRole("button", { name: "List" }));
124
- expect(screen.getByRole("button", { name: "List" })).toHaveAttribute(
125
- "aria-pressed",
126
+ await user.click(board);
127
+ await user.keyboard("{ArrowRight}");
128
+ const list = screen.getByRole("radio", { name: "List" });
129
+ expect(list).toHaveAttribute(
130
+ "aria-checked",
126
131
  "true",
127
132
  );
133
+ expect(list).toHaveFocus();
128
134
  });
129
135
  });
@@ -0,0 +1,39 @@
1
+ import { cleanup, render, screen } from "@testing-library/react";
2
+ import userEvent from "@testing-library/user-event";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+
5
+ import { MarketingHeader, MarketingMotionControl } from "./Marketing";
6
+
7
+ afterEach(() => {
8
+ cleanup();
9
+ document.documentElement.removeAttribute("data-marketing-paused");
10
+ });
11
+
12
+ describe("marketing controls", () => {
13
+ it("names navigation and exposes shared focus targets", () => {
14
+ render(
15
+ <MarketingHeader
16
+ brand={<a href="https://example.com">ShellStack</a>}
17
+ navigation={<a href="https://example.com/features">Features</a>}
18
+ actions={<a href="https://example.com/login">Log in</a>}
19
+ />,
20
+ );
21
+
22
+ expect(screen.getByRole("navigation", { name: "Product navigation" })).toBeInTheDocument();
23
+ expect(screen.getByRole("link", { name: "ShellStack" })).toBeInTheDocument();
24
+ });
25
+
26
+ it("pauses and resumes landing animation state", async () => {
27
+ const user = userEvent.setup();
28
+ render(<MarketingMotionControl />);
29
+
30
+ const control = screen.getByRole("button", { name: "Pause landing animations" });
31
+ await user.click(control);
32
+ expect(control).toHaveAttribute("aria-pressed", "true");
33
+ expect(document.documentElement).toHaveAttribute("data-marketing-paused");
34
+
35
+ await user.click(control);
36
+ expect(control).toHaveAttribute("aria-pressed", "false");
37
+ expect(document.documentElement).not.toHaveAttribute("data-marketing-paused");
38
+ });
39
+ });
@@ -0,0 +1,20 @@
1
+ import { render, screen } from "@testing-library/react";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { Table } from "./Table";
5
+
6
+ describe("Table", () => {
7
+ it("names its scroll region and renders a custom empty state", () => {
8
+ render(
9
+ <Table
10
+ data={[] as Array<{ name: string }>}
11
+ columns={[{ key: "name", header: "Name", cell: (row) => row.name }]}
12
+ caption="People"
13
+ emptyState="No people yet."
14
+ />,
15
+ );
16
+
17
+ expect(screen.getByRole("region", { name: "People" })).toHaveAttribute("tabindex", "0");
18
+ expect(screen.getByRole("cell", { name: "No people yet." })).toHaveAttribute("colspan", "1");
19
+ });
20
+ });