@astryxdesign/core 0.6.3-canary.f22695a → 0.6.3

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 (118) hide show
  1. package/dist/Button/Button.d.ts.map +1 -1
  2. package/dist/Button/Button.js +28 -13
  3. package/dist/Chat/ChatLayout.d.ts +0 -19
  4. package/dist/Chat/ChatLayout.d.ts.map +1 -1
  5. package/dist/Chat/ChatLayout.js +0 -19
  6. package/dist/Chat/ChatLayoutScrollButton.d.ts.map +1 -1
  7. package/dist/Chat/ChatLayoutScrollButton.js +34 -44
  8. package/dist/Citation/Citation.d.ts +1 -1
  9. package/dist/Citation/Citation.d.ts.map +1 -1
  10. package/dist/Citation/Citation.js +2 -3
  11. package/dist/CodeBlock/highlightRanges.d.ts.map +1 -1
  12. package/dist/CodeBlock/highlightRanges.js +3 -14
  13. package/dist/Icon/Icon.d.ts +31 -6
  14. package/dist/Icon/Icon.d.ts.map +1 -1
  15. package/dist/Icon/Icon.js +71 -8
  16. package/dist/Link/useLinkComponent.d.ts +15 -5
  17. package/dist/Link/useLinkComponent.d.ts.map +1 -1
  18. package/dist/Link/useLinkComponent.js +51 -34
  19. package/dist/Markdown/Markdown.js +2 -2
  20. package/dist/Markdown/url.d.ts +0 -2
  21. package/dist/Markdown/url.d.ts.map +1 -1
  22. package/dist/Markdown/url.js +2 -8
  23. package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
  24. package/dist/PowerSearch/PowerSearchValueEditor.js +0 -5
  25. package/dist/TopNav/TopNavMenu.d.ts +1 -1
  26. package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
  27. package/dist/TopNav/TopNavMenu.js +2 -3
  28. package/dist/astryx.css +4 -2
  29. package/dist/hooks/useClickableContainer.d.ts +1 -10
  30. package/dist/hooks/useClickableContainer.d.ts.map +1 -1
  31. package/dist/hooks/useClickableContainer.js +4 -15
  32. package/dist/index.d.ts +0 -1
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +0 -3
  35. package/dist/theme/generateThemeRules.d.ts +7 -13
  36. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  37. package/dist/theme/generateThemeRules.js +35 -131
  38. package/package.json +4 -9
  39. package/src/Button/Button.doc.mjs +5 -5
  40. package/src/Button/Button.tsx +13 -15
  41. package/src/Chat/ChatLayout.doc.mjs +4 -12
  42. package/src/Chat/ChatLayout.test.tsx +0 -28
  43. package/src/Chat/ChatLayout.tsx +0 -19
  44. package/src/Chat/ChatLayoutScrollButton.test.tsx +0 -31
  45. package/src/Chat/ChatLayoutScrollButton.tsx +14 -31
  46. package/src/Citation/Citation.doc.mjs +2 -2
  47. package/src/Citation/Citation.test.tsx +0 -17
  48. package/src/Citation/Citation.tsx +2 -4
  49. package/src/ClickableCard/ClickableCard.doc.mjs +2 -2
  50. package/src/CodeBlock/highlightRanges.test.ts +1 -143
  51. package/src/CodeBlock/highlightRanges.ts +5 -15
  52. package/src/Icon/Icon.doc.mjs +5 -7
  53. package/src/Icon/Icon.spec.md +40 -81
  54. package/src/Icon/Icon.test.tsx +3 -6
  55. package/src/Icon/Icon.tsx +68 -20
  56. package/src/IconButton/IconButton.doc.mjs +3 -3
  57. package/src/IconButton/IconButton.test.tsx +0 -40
  58. package/src/Item/Item.doc.mjs +2 -2
  59. package/src/Link/Link.doc.mjs +4 -6
  60. package/src/Link/LinkProvider.doc.mjs +2 -2
  61. package/src/Link/useLinkComponent.test.tsx +0 -333
  62. package/src/Link/useLinkComponent.ts +57 -29
  63. package/src/List/ListItem.doc.mjs +2 -2
  64. package/src/Markdown/Markdown.doc.mjs +2 -2
  65. package/src/Markdown/Markdown.spec.md +15 -19
  66. package/src/Markdown/Markdown.test.tsx +0 -52
  67. package/src/Markdown/Markdown.tsx +2 -2
  68. package/src/Markdown/parser.test.ts +0 -20
  69. package/src/Markdown/url.ts +3 -9
  70. package/src/PowerSearch/PowerSearchValueEditor.test.tsx +0 -51
  71. package/src/PowerSearch/PowerSearchValueEditor.tsx +0 -4
  72. package/src/Token/Token.doc.mjs +2 -2
  73. package/src/TopNav/TopNavMenu.doc.mjs +2 -2
  74. package/src/TopNav/TopNavMenu.test.tsx +0 -30
  75. package/src/TopNav/TopNavMenu.tsx +2 -7
  76. package/src/hooks/useClickableContainer.ts +4 -20
  77. package/src/index.ts +0 -3
  78. package/src/tailwind-theme.css +6 -0
  79. package/src/theme/Theme.test.tsx +0 -32
  80. package/src/theme/generateThemeRules.test.ts +2 -470
  81. package/src/theme/generateThemeRules.ts +51 -219
  82. package/src/theme/tokenValueCompat.test.ts +1 -4
  83. package/dist/Icon/IconDefaultSizeContext.d.ts +0 -4
  84. package/dist/Icon/IconDefaultSizeContext.d.ts.map +0 -1
  85. package/dist/Icon/IconDefaultSizeContext.js +0 -18
  86. package/dist/Icon/IconSize.stylex.d.ts +0 -65
  87. package/dist/Icon/IconSize.stylex.d.ts.map +0 -1
  88. package/dist/Icon/IconSize.stylex.js +0 -75
  89. package/dist/Timer/Timer.d.ts +0 -49
  90. package/dist/Timer/Timer.d.ts.map +0 -1
  91. package/dist/Timer/Timer.js +0 -158
  92. package/dist/Timer/index.d.ts +0 -9
  93. package/dist/Timer/index.d.ts.map +0 -1
  94. package/dist/Timer/index.js +0 -10
  95. package/dist/theme/declarationBoundary.d.ts +0 -17
  96. package/dist/theme/declarationBoundary.d.ts.map +0 -1
  97. package/dist/theme/declarationBoundary.js +0 -343
  98. package/dist/utils/safeUrl.d.ts +0 -11
  99. package/dist/utils/safeUrl.d.ts.map +0 -1
  100. package/dist/utils/safeUrl.js +0 -78
  101. package/src/Chat/ChatLayout.spec.md +0 -271
  102. package/src/Chat/ChatLayoutScrollButton.spec.md +0 -338
  103. package/src/Chat/__tests__/ChatLayoutScrollButton.a11y.chromium.spec.ts +0 -514
  104. package/src/Chat/__tests__/ChatLayoutScrollButtonSurface.a11y.chromium.spec.ts +0 -740
  105. package/src/Icon/IconDefaultSizeContext.ts +0 -23
  106. package/src/Icon/IconSize.stylex.ts +0 -70
  107. package/src/Link/__tests__/Link.navigation.a11y.chromium.spec.ts +0 -261
  108. package/src/Markdown/Markdown.renderBoundary.test.tsx +0 -72
  109. package/src/Timer/Timer.doc.mjs +0 -183
  110. package/src/Timer/Timer.spec.md +0 -215
  111. package/src/Timer/Timer.test.tsx +0 -302
  112. package/src/Timer/Timer.tsx +0 -258
  113. package/src/Timer/index.ts +0 -11
  114. package/src/hooks/useClickableContainer.test.tsx +0 -236
  115. package/src/theme/declarationBoundary.test.ts +0 -126
  116. package/src/theme/declarationBoundary.ts +0 -347
  117. package/src/utils/safeUrl.test.ts +0 -88
  118. package/src/utils/safeUrl.ts +0 -85
@@ -1,23 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- 'use client';
4
-
5
- /**
6
- * @file IconDefaultSizeContext.ts
7
- * @input Uses React context and an optional Icon size prop
8
- * @output Supplies a component-owned default size to descendant Icon instances
9
- * @position Internal Icon sizing context; consumed by Icon and icon-slot owners
10
- */
11
-
12
- import {createContext, use} from 'react';
13
- import type {IconSize} from './IconSize.stylex';
14
-
15
- const IconDefaultSizeContext = createContext<IconSize | null>(null);
16
- IconDefaultSizeContext.displayName = 'IconDefaultSizeContext';
17
-
18
- export const IconDefaultSizeProvider = IconDefaultSizeContext.Provider;
19
-
20
- export function useIconSize(size: IconSize | undefined): IconSize {
21
- const contextualSize = use(IconDefaultSizeContext);
22
- return size ?? contextualSize ?? 'md';
23
- }
@@ -1,70 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file IconSize.stylex.ts
5
- * @input Defines the Icon size scale
6
- * @output Exports shared Icon size types and StyleX styles
7
- * @position Internal Icon sizing source; consumed by Icon and icon-slot owners
8
- */
9
-
10
- import * as stylex from '@stylexjs/stylex';
11
-
12
- /**
13
- * Icon sizes are expressed in `rem` so they scale with the root font-size.
14
- * Values are the px-equivalents at a 16px root: 12, 16, 20, and 24px.
15
- */
16
- const iconSizeValues = {
17
- xsm: '0.75rem',
18
- sm: '1rem',
19
- md: '1.25rem',
20
- lg: '1.5rem',
21
- } as const;
22
-
23
- export type IconSize = keyof typeof iconSizeValues;
24
-
25
- /** Size styles for direct SVG icon components. */
26
- export const iconSizeStyles = stylex.create({
27
- xsm: {
28
- width: iconSizeValues.xsm,
29
- height: iconSizeValues.xsm,
30
- },
31
- sm: {
32
- width: iconSizeValues.sm,
33
- height: iconSizeValues.sm,
34
- },
35
- md: {
36
- width: iconSizeValues.md,
37
- height: iconSizeValues.md,
38
- },
39
- lg: {
40
- width: iconSizeValues.lg,
41
- height: iconSizeValues.lg,
42
- },
43
- });
44
-
45
- /**
46
- * Size styles for icon boxes and string-based registry icons. `fontSize`
47
- * keeps 1em-based icons in step with the box.
48
- */
49
- export const iconBoxSizeStyles = stylex.create({
50
- xsm: {
51
- width: iconSizeValues.xsm,
52
- height: iconSizeValues.xsm,
53
- fontSize: iconSizeValues.xsm,
54
- },
55
- sm: {
56
- width: iconSizeValues.sm,
57
- height: iconSizeValues.sm,
58
- fontSize: iconSizeValues.sm,
59
- },
60
- md: {
61
- width: iconSizeValues.md,
62
- height: iconSizeValues.md,
63
- fontSize: iconSizeValues.md,
64
- },
65
- lg: {
66
- width: iconSizeValues.lg,
67
- height: iconSizeValues.lg,
68
- fontSize: iconSizeValues.lg,
69
- },
70
- });
@@ -1,261 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file Link.navigation.a11y.chromium.spec.ts
5
- * @input Built Storybook navigation fixture and real Chromium activation
6
- * @output Blocked-scheme and accepted-navigation evidence across every sink
7
- * @position Browser proof for the shared navigation-destination contract
8
- */
9
-
10
- import {expect, test, type Page} from '@playwright/test';
11
- import {
12
- DEFAULT_STORYBOOK_DIR,
13
- serveStorybook,
14
- type StaticServer,
15
- } from '@astryxdesign/a11y-spec/storybook';
16
-
17
- let storybook: StaticServer;
18
- const storyId = 'core-link-navigation--destinations';
19
-
20
- test.beforeAll(async () => {
21
- storybook = await serveStorybook(
22
- process.env.ASTRYX_STORYBOOK_DIR ?? DEFAULT_STORYBOOK_DIR,
23
- );
24
- });
25
- test.afterAll(async () => {
26
- await storybook?.close();
27
- });
28
-
29
- async function mount(page: Page, href: string) {
30
- await page.goto(
31
- `${storybook.origin}/iframe.html?id=${storyId}&viewMode=story`,
32
- );
33
- await expect(page.getByTestId('navigation-fixture')).toBeVisible();
34
- await page.evaluate(
35
- ({storyId, href}) => {
36
- const channel = (
37
- window as unknown as {
38
- __STORYBOOK_ADDONS_CHANNEL__: {
39
- emit: (event: string, payload: unknown) => void;
40
- };
41
- }
42
- ).__STORYBOOK_ADDONS_CHANNEL__;
43
- channel.emit('updateStoryArgs', {storyId, updatedArgs: {href}});
44
- },
45
- {storyId, href},
46
- );
47
- await expect(page.getByTestId('navigation-fixture')).toHaveAttribute(
48
- 'data-destination',
49
- href,
50
- );
51
- }
52
-
53
- const blocked = [
54
- 'javascript:window.__navigationExecuted=true',
55
- 'vbscript:MsgBox(1)',
56
- 'data:text/html,<script>window.__navigationExecuted=true</script>',
57
- ' \u0000JaVa\tsCrIpT:window.__navigationExecuted=true',
58
- ];
59
-
60
- for (const href of blocked) {
61
- test(`rejected ${JSON.stringify(href)} is inert across native, router, Markdown, and imperative paths`, async ({
62
- page,
63
- context,
64
- }) => {
65
- await mount(page, href);
66
- const originalUrl = page.url();
67
- await page.evaluate(() => {
68
- (
69
- window as unknown as {__navigationExecuted: boolean}
70
- ).__navigationExecuted = false;
71
- });
72
- const pagesBefore = context.pages().length;
73
- for (const id of [
74
- 'native',
75
- 'provider',
76
- 'as',
77
- 'structured',
78
- 'citation',
79
- 'download',
80
- ]) {
81
- const target = page.getByTestId(id);
82
- await expect(target).not.toHaveAttribute('href');
83
- await expect(target).not.toHaveAttribute('data-router');
84
- await target.click();
85
- await target.click({button: 'middle'});
86
- }
87
- await expect(page.getByTestId('markdown').locator('a')).toHaveCount(0);
88
- await expect(page.getByTestId('delegated').locator('a[href]')).toHaveCount(
89
- 0,
90
- );
91
- for (const id of ['imperative', 'imperative-blank', 'delegated']) {
92
- const target =
93
- id === 'delegated'
94
- ? page.getByText('Delegated card surface', {exact: true})
95
- : page.getByTestId(id);
96
- await target.click();
97
- await target.click({modifiers: ['Control']});
98
- await target.click({modifiers: ['Meta']});
99
- await target.click({button: 'middle'});
100
- }
101
- // Drain browser activation tasks before checking that no sink fired.
102
- await page.evaluate(
103
- async () =>
104
- new Promise<void>(resolve =>
105
- requestAnimationFrame(() => requestAnimationFrame(() => resolve())),
106
- ),
107
- );
108
- expect(page.url()).toBe(originalUrl);
109
- expect(context.pages()).toHaveLength(pagesBefore);
110
- expect(
111
- await page.evaluate(
112
- () =>
113
- (window as unknown as {__navigationExecuted: boolean})
114
- .__navigationExecuted,
115
- ),
116
- ).toBe(false);
117
- });
118
- }
119
-
120
- test('ordinary schemes keep native attributes, custom routing, and downloads', async ({
121
- page,
122
- }) => {
123
- for (const href of [
124
- '#fragment',
125
- '/relative',
126
- 'https://example.com/docs',
127
- 'http://example.com/docs',
128
- 'mailto:a@example.com',
129
- 'tel:+1234567890',
130
- 'custom:document',
131
- 'data:text/plain,download',
132
- ]) {
133
- await mount(page, href);
134
- for (const id of [
135
- 'native',
136
- 'provider',
137
- 'as',
138
- 'structured',
139
- 'citation',
140
- 'download',
141
- ]) {
142
- await expect(page.getByTestId(id)).toHaveAttribute('href', href);
143
- }
144
- for (const id of ['provider', 'as', 'structured']) {
145
- await expect(page.getByTestId(id)).toHaveAttribute('data-router', 'true');
146
- }
147
- await expect(page.getByTestId('download')).toHaveAttribute(
148
- 'download',
149
- 'note.txt',
150
- );
151
- await expect(page.getByTestId('markdown').locator('a')).toHaveAttribute(
152
- 'href',
153
- href,
154
- );
155
- }
156
- });
157
-
158
- for (const id of [
159
- 'native',
160
- 'provider',
161
- 'as',
162
- 'structured',
163
- 'imperative',
164
- 'delegated',
165
- ]) {
166
- test(`${id} retains real same-tab navigation`, async ({page}) => {
167
- const href = '/navigation-destination?source=' + id;
168
- await page.route('**/navigation-destination?*', async route =>
169
- route.fulfill({
170
- contentType: 'text/html',
171
- body: '<h1>Destination reached</h1>',
172
- }),
173
- );
174
- await mount(page, href);
175
- const target =
176
- id === 'delegated'
177
- ? page.getByText('Delegated card surface', {exact: true})
178
- : page.getByTestId(id);
179
- await target.click();
180
- await expect(page).toHaveURL(`${storybook.origin}${href}`);
181
- await expect(
182
- page.getByRole('heading', {name: 'Destination reached'}),
183
- ).toBeVisible();
184
- });
185
- }
186
-
187
- for (const activation of ['blank', 'control', 'meta', 'middle'] as const) {
188
- test(`imperative ${activation} retains real new-tab navigation`, async ({
189
- page,
190
- context,
191
- }) => {
192
- const href = `${storybook.origin}/navigation-destination?source=${activation}`;
193
- await context.route('**/navigation-destination?*', async route =>
194
- route.fulfill({
195
- contentType: 'text/html',
196
- body: '<h1>Destination reached</h1>',
197
- }),
198
- );
199
- await mount(page, href);
200
- const originalUrl = page.url();
201
- const opened = context.waitForEvent('page');
202
- await page
203
- .getByTestId(activation === 'blank' ? 'imperative-blank' : 'imperative')
204
- .click({
205
- button: activation === 'middle' ? 'middle' : 'left',
206
- modifiers:
207
- activation === 'control'
208
- ? ['Control']
209
- : activation === 'meta'
210
- ? ['Meta']
211
- : [],
212
- });
213
- const popup = await opened;
214
- await expect(popup).toHaveURL(href);
215
- await expect(
216
- popup.getByRole('heading', {name: 'Destination reached'}),
217
- ).toBeVisible();
218
- expect(page.url()).toBe(originalUrl);
219
- await popup.close();
220
- });
221
- }
222
-
223
- test('native anchors retain real downloads and same-document fragments', async ({
224
- page,
225
- }) => {
226
- await mount(page, 'data:text/plain,download');
227
- const downloadEvent = page.waitForEvent('download');
228
- await page.getByTestId('download').click();
229
- const download = await downloadEvent;
230
- expect(download.suggestedFilename()).toBe('note.txt');
231
- expect(await download.failure()).toBeNull();
232
- await mount(page, '#destination');
233
- await page.getByTestId('native').click();
234
- await expect(page).toHaveURL(/#destination$/);
235
- });
236
-
237
- test('native anchors retain real middle-click and external target navigation', async ({
238
- page,
239
- context,
240
- }) => {
241
- const href = 'https://example.com/navigation-destination';
242
- await context.route(href, async route =>
243
- route.fulfill({
244
- contentType: 'text/html',
245
- body: '<h1>External destination</h1>',
246
- }),
247
- );
248
- for (const id of ['native', 'citation']) {
249
- await mount(page, href);
250
- const opened = context.waitForEvent('page');
251
- await page
252
- .getByTestId(id)
253
- .click({button: id === 'native' ? 'middle' : 'left'});
254
- const popup = await opened;
255
- await expect(popup).toHaveURL(href);
256
- await expect(
257
- popup.getByRole('heading', {name: 'External destination'}),
258
- ).toBeVisible();
259
- await popup.close();
260
- }
261
- });
@@ -1,72 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /**
4
- * @file Markdown.renderBoundary.test.tsx
5
- * @input Uses vitest, @testing-library/react, Markdown with a mocked parser
6
- * @output Tests for the render-side navigation check in Markdown
7
- * @position Testing; the parser already rejects blocked destinations, so
8
- * these tests bypass it and hand the renderer a pre-parsed link node.
9
- * That pins the render boundary on its own: it must apply the shared
10
- * navigation rule (utils/safeUrl.ts) even when a link node arrives with a
11
- * destination the parser never saw.
12
- */
13
-
14
- import {afterEach, describe, expect, it, vi} from 'vitest';
15
- import {cleanup, render} from '@testing-library/react';
16
- import {Markdown} from './Markdown';
17
- import type * as Parser from './parser';
18
- import type {MarkdownAstBlockContent} from './ast';
19
-
20
- const parsed: {blocks: MarkdownAstBlockContent[]} = {blocks: []};
21
-
22
- vi.mock('./parser', async importOriginal => {
23
- const actual = await importOriginal<typeof Parser>();
24
- return {
25
- ...actual,
26
- parseMarkdownAst: () => ({type: 'root', children: parsed.blocks}),
27
- };
28
- });
29
-
30
- function linkParagraph(url: string): MarkdownAstBlockContent[] {
31
- return [
32
- {
33
- type: 'paragraph',
34
- children: [{type: 'link', url, children: [{type: 'text', value: 'go'}]}],
35
- },
36
- ];
37
- }
38
-
39
- afterEach(() => {
40
- cleanup();
41
- });
42
-
43
- describe('Markdown render boundary — link destinations', () => {
44
- it('renders an accepted pre-parsed destination as a link', () => {
45
- parsed.blocks = linkParagraph('data:image/png;base64,iVBORw0KGgo=');
46
- const {container} = render(<Markdown>{'ignored'}</Markdown>);
47
- expect(container.querySelector('a')?.getAttribute('href')).toBe(
48
- 'data:image/png;base64,iVBORw0KGgo=',
49
- );
50
- });
51
-
52
- it.each([
53
- 'javascript:alert(1)',
54
- 'vbscript:MsgBox(1)',
55
- 'data:text/html,<script>alert(1)</script>',
56
- 'java\nscript:alert(1)',
57
- ])('renders a blocked pre-parsed destination %s as text', href => {
58
- parsed.blocks = linkParagraph(href);
59
- const {container} = render(<Markdown>{'ignored'}</Markdown>);
60
- expect(container.querySelector('a')).toBeNull();
61
- expect(container.textContent).toContain('go');
62
- });
63
-
64
- it('does not hand a blocked destination to a custom link renderer', () => {
65
- parsed.blocks = linkParagraph('javascript:alert(1)');
66
- const link = vi.fn(({children}: {href: string; children?: unknown}) => (
67
- <a data-custom>{children as never}</a>
68
- ));
69
- render(<Markdown components={{link}}>{'ignored'}</Markdown>);
70
- expect(link).not.toHaveBeenCalled();
71
- });
72
- });
@@ -1,183 +0,0 @@
1
- // Copyright (c) Meta Platforms, Inc. and affiliates.
2
-
3
- /** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
4
- const anatomy = [
5
- {
6
- name: 'Elapsed time',
7
- required: true,
8
- description:
9
- 'Semantic time element containing a standardized elapsed duration.',
10
- },
11
- ];
12
-
13
- /** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
14
- export const docs = {
15
- name: 'Timer',
16
- displayName: 'Timer',
17
- category: 'Content',
18
- keywords: [
19
- 'timer',
20
- 'elapsed',
21
- 'duration',
22
- 'seconds',
23
- 'minutes',
24
- 'hours',
25
- 'stopwatch',
26
- 'waiting',
27
- 'loading',
28
- 'processing',
29
- ],
30
- props: [
31
- {
32
- name: 'startTime',
33
- type: 'number',
34
- description:
35
- "Unix time in milliseconds when the measured operation began. Omit it to start from this Timer's mount.",
36
- },
37
- {
38
- name: 'format',
39
- type: "'elapsed' | 'clock'",
40
- description:
41
- 'Standard duration representation. Elapsed uses compact units and drops seconds after one hour; clock uses m:ss or h:mm:ss.',
42
- default: "'elapsed'",
43
- },
44
- {
45
- name: 'type',
46
- type: "'body' | 'large' | 'label' | 'supporting' | 'code' | 'display-1' | 'display-2' | 'display-3' | 'inherit'",
47
- description:
48
- 'Semantic text type. Uses the same typography behavior as Timestamp.',
49
- default: "'supporting'",
50
- },
51
- {
52
- name: 'size',
53
- type: "'4xs' | '3xs' | '2xs' | 'xsm' | 'sm' | 'base' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl'",
54
- description: 'Explicit font size override. Overrides the size from type.',
55
- },
56
- {
57
- name: 'color',
58
- type: "'primary' | 'secondary' | 'disabled' | 'placeholder' | 'accent' | 'inherit'",
59
- description: 'Text color.',
60
- default: "'secondary'",
61
- },
62
- {
63
- name: 'weight',
64
- type: "'normal' | 'medium' | 'semibold' | 'bold'",
65
- description: 'Font weight override.',
66
- },
67
- {
68
- name: 'xstyle',
69
- type: 'StyleXStyles',
70
- description:
71
- 'StyleX styles for the Text wrapper. Must be a stylex.create() value.',
72
- },
73
- {
74
- name: 'className',
75
- type: 'string',
76
- description:
77
- 'CSS class name for the Text wrapper. Prefer xstyle for styling.',
78
- },
79
- {
80
- name: 'style',
81
- type: 'CSSProperties',
82
- description:
83
- 'Inline styles for the Text wrapper. Prefer xstyle for styling.',
84
- },
85
- ],
86
- examples: [
87
- {
88
- label: 'Elapsed duration',
89
- code: '<Timer />',
90
- },
91
- {
92
- label: 'Stopwatch clock',
93
- code: '<Timer format="clock" />',
94
- },
95
- {
96
- label: 'Operation that started before mount',
97
- code: '<Timer startTime={operationStartedAt} />',
98
- },
99
- {
100
- label: 'Match surrounding text',
101
- code: `<Text>
102
- Processing for <Timer type="inherit" color="inherit" />
103
- </Text>`,
104
- },
105
- {
106
- label: 'Prominent elapsed time',
107
- code: '<Timer type="body" size="lg" color="primary" weight="semibold" />',
108
- },
109
- ],
110
- theming: {
111
- targets: [{className: 'astryx-timer'}],
112
- },
113
- usage: {
114
- anatomy,
115
- description:
116
- 'Displays a standardized elapsed duration for active work without scheduling a React render on every tick. Elapsed format updates by second below one hour and by minute after one hour; clock format remains second-precise.',
117
- bestPractices: [
118
- {
119
- guidance: true,
120
- description:
121
- 'Use elapsed for compact duration text that may span seconds, minutes, or hours.',
122
- },
123
- {
124
- guidance: true,
125
- description:
126
- 'Use clock for stopwatch-like surfaces where seconds remain meaningful after an hour.',
127
- },
128
- {
129
- guidance: true,
130
- description:
131
- 'Pass startTime when the operation began before Timer mounted so the display reflects the complete wait.',
132
- },
133
- {
134
- guidance: false,
135
- description:
136
- 'Do not use Timer for dates, time zones, or relative calendar language; use Timestamp instead.',
137
- },
138
- {
139
- guidance: false,
140
- description:
141
- 'Do not add aria-live unless hearing an announcement every tick is appropriate for the specific task.',
142
- },
143
- ],
144
- },
145
- };
146
-
147
- /** @type {import('@astryxdesign/cli/authoring').ComponentTranslationDoc} */
148
- export const docsDense = {
149
- description:
150
- 'Standardized elapsed or stopwatch duration with clock-derived, non-rendering DOM updates.',
151
- propDescriptions: {
152
- startTime:
153
- "operation start as Unix milliseconds; omit to count from Timer's mount",
154
- format: 'elapsed compact units or clock stopwatch notation',
155
- type: 'semantic text type; defaults to supporting like Timestamp',
156
- size: 'explicit font size override',
157
- color: 'text color; defaults to secondary like Timestamp',
158
- weight: 'font weight override',
159
- xstyle: 'StyleX styles for the Text wrapper',
160
- className: 'CSS class for the Text wrapper',
161
- style: 'inline styles for the Text wrapper',
162
- },
163
- usage: {
164
- anatomy,
165
- description:
166
- 'Use for active-operation elapsed time when periodic React renders would add avoidable work.',
167
- bestPractices: [
168
- {
169
- guidance: true,
170
- description:
171
- 'Use elapsed for compact durations and clock for stopwatch UI.',
172
- },
173
- {
174
- guidance: true,
175
- description: 'Pass startTime for work that began before mount.',
176
- },
177
- {
178
- guidance: false,
179
- description: 'Use Timestamp for dates and relative calendar language.',
180
- },
181
- ],
182
- },
183
- };