@rdna/radiants 0.1.3 → 0.1.5

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/base.css +1 -1
  2. package/components/core/AppWindow/AppWindow.meta.ts +69 -0
  3. package/components/core/AppWindow/AppWindow.schema.json +55 -0
  4. package/components/core/AppWindow/AppWindow.test.tsx +150 -0
  5. package/components/core/AppWindow/AppWindow.tsx +830 -0
  6. package/components/core/Button/Button.test.tsx +18 -0
  7. package/components/core/Button/Button.tsx +26 -16
  8. package/components/core/DialPanel/DialPanel.tsx +1 -1
  9. package/components/core/Separator/Separator.tsx +1 -1
  10. package/components/core/Tabs/Tabs.tsx +14 -2
  11. package/components/core/__tests__/smoke.test.tsx +2 -0
  12. package/components/core/index.ts +1 -0
  13. package/contract/system.ts +18 -4
  14. package/dark.css +11 -1
  15. package/eslint/contract.mjs +1 -1
  16. package/eslint/index.mjs +10 -0
  17. package/eslint/rules/no-raw-font-family.mjs +91 -0
  18. package/eslint/rules/no-raw-line-height.mjs +119 -0
  19. package/fonts/.gitkeep +0 -0
  20. package/fonts/Mondwest-Bold.woff2 +0 -0
  21. package/fonts/Mondwest.woff2 +0 -0
  22. package/fonts/PixeloidSans-Bold.woff2 +0 -0
  23. package/fonts/PixeloidSans.woff2 +0 -0
  24. package/fonts/WavesBlackletterCPC-Base.woff2 +0 -0
  25. package/fonts/WavesTinyCPC-Extended.woff2 +0 -0
  26. package/fonts-core.css +70 -0
  27. package/fonts-editorial.css +45 -0
  28. package/fonts.css +19 -89
  29. package/generated/ai-contract.json +11 -2
  30. package/generated/contract.freshness.json +2 -1
  31. package/generated/eslint-contract.json +35 -4
  32. package/generated/figma/contracts/app-window.contract.json +82 -0
  33. package/generated/figma/primitive/color.tokens.json +9 -0
  34. package/generated/figma/primitive/shape.tokens.json +0 -4
  35. package/generated/figma/primitive/typography.tokens.json +16 -4
  36. package/generated/figma/rdna.tokens.json +28 -11
  37. package/generated/figma/semantic/semantic.tokens.json +3 -3
  38. package/generated/figma/tokens.d.ts +1 -1
  39. package/generated/figma/validation-report.json +1 -1
  40. package/icons/DesktopIcons.tsx +4 -3
  41. package/icons/Icon.tsx +10 -2
  42. package/icons/types.ts +7 -1
  43. package/meta/index.ts +6 -0
  44. package/package.json +6 -5
  45. package/patterns/pretext-type-scale.ts +115 -0
  46. package/pixel-corners.generated.css +15 -0
  47. package/schemas/index.ts +2 -0
  48. package/tokens.css +47 -21
  49. package/typography.css +10 -5
  50. package/fonts/PixelCode-Black-Italic.woff2 +0 -0
  51. package/fonts/PixelCode-Black.woff2 +0 -0
  52. package/fonts/PixelCode-DemiBold-Italic.woff2 +0 -0
  53. package/fonts/PixelCode-DemiBold.woff2 +0 -0
  54. package/fonts/PixelCode-ExtraBlack-Italic.woff2 +0 -0
  55. package/fonts/PixelCode-ExtraBlack.woff2 +0 -0
  56. package/fonts/PixelCode-ExtraBold-Italic.woff2 +0 -0
  57. package/fonts/PixelCode-ExtraBold.woff2 +0 -0
  58. package/fonts/PixelCode-ExtraLight-Italic.woff2 +0 -0
  59. package/fonts/PixelCode-ExtraLight.woff2 +0 -0
  60. package/fonts/PixelCode-Thin-Italic.woff2 +0 -0
  61. package/fonts/PixelCode-Thin.woff2 +0 -0
package/base.css CHANGED
@@ -911,7 +911,7 @@ body {
911
911
  --color-sub: oklch(0.1641 0.0044 84.59 / 0.85);
912
912
  --color-flip: var(--color-cream);
913
913
  --color-mute: oklch(0.1641 0.0044 84.59 / 0.6);
914
- --color-link: var(--color-sky-blue);
914
+ --color-link: var(--color-sky-blue-dark);
915
915
 
916
916
  --color-line: var(--color-ink);
917
917
  --color-rule: oklch(0.1641 0.0044 84.59 / 0.2);
@@ -0,0 +1,69 @@
1
+ import type { ReactNode } from "react";
2
+ import { defineComponentMeta } from "@rdna/preview/define-component-meta";
3
+
4
+ interface AppWindowProps {
5
+ id: string;
6
+ title: string;
7
+ children?: ReactNode;
8
+ open?: boolean;
9
+ resizable?: boolean;
10
+ presentation?: "window" | "fullscreen" | "mobile";
11
+ }
12
+
13
+ export const AppWindowMeta = defineComponentMeta<AppWindowProps>()({
14
+ name: "AppWindow",
15
+ description:
16
+ "Desktop-style application window with draggable chrome, optional resize handles, and mobile/fullscreen presentations.",
17
+ subcomponents: [
18
+ "AppWindow",
19
+ "AppWindowBody",
20
+ "AppWindowSplitView",
21
+ "AppWindowPane",
22
+ ],
23
+ props: {
24
+ id: {
25
+ type: "string",
26
+ description: "Stable window identifier used for aria and share-link behavior.",
27
+ },
28
+ title: {
29
+ type: "string",
30
+ description: "Window title shown in the title bar.",
31
+ },
32
+ open: {
33
+ type: "boolean",
34
+ default: true,
35
+ description: "Whether the window is currently rendered.",
36
+ },
37
+ resizable: {
38
+ type: "boolean",
39
+ default: true,
40
+ description: "Enables resize handles in desktop window presentation.",
41
+ },
42
+ presentation: {
43
+ type: "enum",
44
+ values: ["window", "fullscreen", "mobile"],
45
+ default: "window",
46
+ description: "Selects windowed, fullscreen, or mobile shell presentation.",
47
+ },
48
+ },
49
+ slots: {
50
+ children: {
51
+ description: "Window content, usually an AppWindowBody or custom app layout.",
52
+ },
53
+ },
54
+ examples: [
55
+ {
56
+ name: "Desktop window shell",
57
+ code: "<AppWindow id=\"about\" title=\"About\">\n <AppWindowBody>Content</AppWindowBody>\n</AppWindow>",
58
+ },
59
+ {
60
+ name: "Two-pane compare layout",
61
+ code: "<AppWindow id=\"lab\" title=\"Control Surface Lab\">\n <AppWindowSplitView>\n <AppWindowPane>Legacy</AppWindowPane>\n <AppWindowPane>RDNA</AppWindowPane>\n </AppWindowSplitView>\n</AppWindow>",
62
+ },
63
+ ],
64
+ registry: {
65
+ category: "layout",
66
+ tags: ["window", "desktop", "shell"],
67
+ renderMode: "description-only",
68
+ },
69
+ });
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "AppWindow",
3
+ "description": "Desktop-style application window with draggable chrome, optional resize handles, and mobile/fullscreen presentations.",
4
+ "subcomponents": [
5
+ "AppWindow",
6
+ "AppWindowBody",
7
+ "AppWindowSplitView",
8
+ "AppWindowPane"
9
+ ],
10
+ "props": {
11
+ "id": {
12
+ "type": "string",
13
+ "description": "Stable window identifier used for aria and share-link behavior."
14
+ },
15
+ "title": {
16
+ "type": "string",
17
+ "description": "Window title shown in the title bar."
18
+ },
19
+ "open": {
20
+ "type": "boolean",
21
+ "default": true,
22
+ "description": "Whether the window is currently rendered."
23
+ },
24
+ "resizable": {
25
+ "type": "boolean",
26
+ "default": true,
27
+ "description": "Enables resize handles in desktop window presentation."
28
+ },
29
+ "presentation": {
30
+ "type": "enum",
31
+ "values": [
32
+ "window",
33
+ "fullscreen",
34
+ "mobile"
35
+ ],
36
+ "default": "window",
37
+ "description": "Selects windowed, fullscreen, or mobile shell presentation."
38
+ }
39
+ },
40
+ "slots": {
41
+ "children": {
42
+ "description": "Window content, usually an AppWindowBody or custom app layout."
43
+ }
44
+ },
45
+ "examples": [
46
+ {
47
+ "name": "Desktop window shell",
48
+ "code": "<AppWindow id=\"about\" title=\"About\">\n <AppWindowBody>Content</AppWindowBody>\n</AppWindow>"
49
+ },
50
+ {
51
+ "name": "Two-pane compare layout",
52
+ "code": "<AppWindow id=\"lab\" title=\"Control Surface Lab\">\n <AppWindowSplitView>\n <AppWindowPane>Legacy</AppWindowPane>\n <AppWindowPane>RDNA</AppWindowPane>\n </AppWindowSplitView>\n</AppWindow>"
53
+ }
54
+ ]
55
+ }
@@ -0,0 +1,150 @@
1
+ import { fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import userEvent from '@testing-library/user-event';
3
+ import { AppWindow, AppWindowBody, AppWindowPane, AppWindowSplitView } from './AppWindow';
4
+
5
+ class ResizeObserverMock {
6
+ observe() {}
7
+ unobserve() {}
8
+ disconnect() {}
9
+ }
10
+
11
+ describe('AppWindow', () => {
12
+ beforeEach(() => {
13
+ vi.stubGlobal('ResizeObserver', ResizeObserverMock);
14
+ Object.defineProperty(window, 'innerWidth', { value: 1000, writable: true });
15
+ Object.defineProperty(window, 'innerHeight', { value: 800, writable: true });
16
+ });
17
+
18
+ afterEach(() => {
19
+ vi.unstubAllGlobals();
20
+ });
21
+
22
+ test('renders desktop presentation with title and window body', () => {
23
+ render(
24
+ <AppWindow id="about" title="About">
25
+ <AppWindowBody>Body content</AppWindowBody>
26
+ </AppWindow>,
27
+ );
28
+
29
+ expect(screen.getByRole('dialog', { name: 'About' })).toBeInTheDocument();
30
+ expect(screen.getByText('Body content')).toBeInTheDocument();
31
+ });
32
+
33
+ test('renders split compare panes with shared window layout helper', () => {
34
+ render(
35
+ <AppWindow id="lab" title="Control Surface Lab">
36
+ <AppWindowSplitView>
37
+ <AppWindowPane padding="sm">Legacy pane</AppWindowPane>
38
+ <AppWindowPane padding="sm">RDNA pane</AppWindowPane>
39
+ </AppWindowSplitView>
40
+ </AppWindow>,
41
+ );
42
+
43
+ expect(screen.getByText('Legacy pane')).toBeInTheDocument();
44
+ expect(screen.getByText('RDNA pane')).toBeInTheDocument();
45
+ expect(document.querySelector('[data-window-layout="split"]')).toBeInTheDocument();
46
+ });
47
+
48
+ test('renders mobile presentation with close action', async () => {
49
+ const user = userEvent.setup();
50
+ const onClose = vi.fn();
51
+
52
+ render(
53
+ <AppWindow id="about" title="About" presentation="mobile" onClose={onClose}>
54
+ <div>Mobile content</div>
55
+ </AppWindow>,
56
+ );
57
+
58
+ expect(screen.getByRole('dialog', { name: 'About' })).toBeInTheDocument();
59
+ await user.click(screen.getByRole('button', { name: 'Close About' }));
60
+ expect(onClose).toHaveBeenCalledTimes(1);
61
+ });
62
+
63
+ test('renders action button links as real anchors', () => {
64
+ render(
65
+ <AppWindow
66
+ id="about"
67
+ title="About"
68
+ showActionButton
69
+ actionButton={{ text: 'Docs', href: 'https://example.com/docs', target: '_blank' }}
70
+ >
71
+ <div>Body</div>
72
+ </AppWindow>,
73
+ );
74
+
75
+ const link = screen.getByRole('link', { name: 'Docs' });
76
+ expect(link).toHaveAttribute('href', 'https://example.com/docs');
77
+ expect(link).toHaveAttribute('target', '_blank');
78
+ expect(link).toHaveAttribute('rel', 'noopener noreferrer');
79
+ });
80
+
81
+ test('auto-centers windowed presentation when requested', async () => {
82
+ const onPositionChange = vi.fn();
83
+
84
+ render(
85
+ <AppWindow id="about" title="About" autoCenter onPositionChange={onPositionChange}>
86
+ <AppWindowBody>Body content</AppWindowBody>
87
+ </AppWindow>,
88
+ );
89
+
90
+ await waitFor(() => {
91
+ expect(onPositionChange).toHaveBeenCalledWith({ x: 350, y: 276 });
92
+ });
93
+ });
94
+
95
+ test('calls onSizeChange when resized from an edge handle', () => {
96
+ const onSizeChange = vi.fn();
97
+ const rectSpy = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(() => ({
98
+ x: 0,
99
+ y: 0,
100
+ top: 0,
101
+ left: 0,
102
+ right: 400,
103
+ bottom: 300,
104
+ width: 400,
105
+ height: 300,
106
+ toJSON: () => ({}),
107
+ }) as DOMRect);
108
+
109
+ render(
110
+ <AppWindow
111
+ id="about"
112
+ title="About"
113
+ size={{ width: 400, height: 300 }}
114
+ onSizeChange={onSizeChange}
115
+ >
116
+ <div>Body</div>
117
+ </AppWindow>,
118
+ );
119
+
120
+ fireEvent.pointerDown(document.querySelector('[data-resize-handle="e"]')!, {
121
+ clientX: 100,
122
+ clientY: 100,
123
+ });
124
+ fireEvent.pointerMove(document, { clientX: 150, clientY: 100 });
125
+ fireEvent.pointerUp(document);
126
+
127
+ expect(onSizeChange).toHaveBeenLastCalledWith({ width: 450, height: 300 });
128
+ rectSpy.mockRestore();
129
+ });
130
+
131
+ test('does not render when widget mode is active', () => {
132
+ render(
133
+ <AppWindow id="about" title="About" widgetActive>
134
+ <div>Hidden by widget mode</div>
135
+ </AppWindow>,
136
+ );
137
+
138
+ expect(screen.queryByRole('dialog', { name: 'About' })).not.toBeInTheDocument();
139
+ });
140
+
141
+ test('does not render when open is false', () => {
142
+ render(
143
+ <AppWindow id="about" title="About" open={false}>
144
+ <div>Hidden</div>
145
+ </AppWindow>,
146
+ );
147
+
148
+ expect(screen.queryByRole('dialog', { name: 'About' })).not.toBeInTheDocument();
149
+ });
150
+ });