@astryxdesign/core 0.1.1 → 0.1.2-canary.2581eb4
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.
- package/CHANGELOG.md +105 -0
- package/README.md +119 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +4 -1
- package/dist/Banner/Banner.d.ts +7 -0
- package/dist/Banner/Banner.d.ts.map +1 -1
- package/dist/Banner/Banner.js +9 -2
- package/dist/Button/Button.d.ts +12 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +51 -13
- package/dist/Calendar/Calendar.d.ts +5 -4
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +6 -1
- package/dist/Calendar/index.d.ts +1 -1
- package/dist/Calendar/index.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.d.ts +7 -2
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +5 -4
- package/dist/EmptyState/EmptyState.d.ts.map +1 -1
- package/dist/EmptyState/EmptyState.js +7 -1
- package/dist/HoverCard/HoverCard.d.ts +2 -2
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +18 -6
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +7 -3
- package/dist/Kbd/Kbd.d.ts.map +1 -1
- package/dist/Kbd/Kbd.js +1 -1
- package/dist/Layer/anchorName.d.ts +20 -0
- package/dist/Layer/anchorName.d.ts.map +1 -0
- package/dist/Layer/anchorName.js +36 -0
- package/dist/Layer/useLayer.d.ts +19 -0
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +15 -6
- package/dist/Link/Link.d.ts +1 -1
- package/dist/Link/Link.js +2 -2
- package/dist/Link/LinkProvider.d.ts +1 -0
- package/dist/Link/LinkProvider.d.ts.map +1 -1
- package/dist/Link/LinkProvider.js +1 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +13 -3
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +13 -0
- package/dist/Pagination/Pagination.d.ts.map +1 -1
- package/dist/Pagination/Pagination.js +31 -27
- package/dist/Slider/Slider.js +2 -2
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +27 -8
- package/dist/Table/Table.d.ts.map +1 -1
- package/dist/Table/Table.js +30 -7
- package/dist/Table/TableRow.d.ts +6 -1
- package/dist/Table/TableRow.d.ts.map +1 -1
- package/dist/Table/TableRow.js +15 -7
- package/dist/Table/index.d.ts +3 -1
- package/dist/Table/index.d.ts.map +1 -1
- package/dist/Table/index.js +1 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts +3 -0
- package/dist/Table/plugins/stickyColumns/index.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/index.js +3 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts +25 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.d.ts.map +1 -0
- package/dist/Table/plugins/stickyColumns/useTableStickyColumns.js +369 -0
- package/dist/Table/types.d.ts +95 -5
- package/dist/Table/types.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.d.ts.map +1 -1
- package/dist/Table/useBaseTablePlugins.js +1 -1
- package/dist/Text/text.stylex.d.ts +2 -2
- package/dist/Text/text.stylex.js +2 -2
- package/dist/Timestamp/Timestamp.d.ts +1 -1
- package/dist/Timestamp/Timestamp.d.ts.map +1 -1
- package/dist/Timestamp/Timestamp.js +28 -3
- package/dist/ToggleButton/ToggleButton.d.ts +7 -9
- package/dist/ToggleButton/ToggleButton.d.ts.map +1 -1
- package/dist/ToggleButton/ToggleButton.js +34 -57
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +1 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +1 -0
- package/dist/astryx.css +30 -1
- package/dist/astryx.umd.js +147 -0
- package/dist/astryx.umd.js.map +7 -0
- package/dist/theme/generateThemeRules.js +1 -1
- package/dist/theme/syntax/SyntaxTheme.d.ts +1 -1
- package/dist/theme/syntax/SyntaxTheme.js +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.d.ts.map +1 -1
- package/dist/theme/syntax/defineSyntaxTheme.js +2 -2
- package/dist/theme/syntax/presets.d.ts +1 -1
- package/dist/theme/syntax/presets.d.ts.map +1 -1
- package/dist/theme/syntax/presets.js +1 -1
- package/dist/theme/syntax/tokens.d.ts +1 -1
- package/dist/theme/syntax/tokens.js +1 -1
- package/dist/theme/types.d.ts +1 -1
- package/dist/utils/dateTypes.d.ts +12 -0
- package/dist/utils/dateTypes.d.ts.map +1 -1
- package/dist/utils/dateTypes.js +41 -1
- package/package.json +6 -2
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -0
- package/src/Banner/Banner.doc.mjs +8 -0
- package/src/Banner/Banner.test.tsx +16 -7
- package/src/Banner/Banner.tsx +9 -2
- package/src/Button/Button.doc.mjs +6 -0
- package/src/Button/Button.test.tsx +105 -11
- package/src/Button/Button.tsx +73 -5
- package/src/Calendar/Calendar.doc.mjs +4 -4
- package/src/Calendar/Calendar.test.tsx +21 -26
- package/src/Calendar/Calendar.tsx +21 -5
- package/src/Calendar/index.ts +1 -0
- package/src/Chat/ChatComposer.doc.mjs +3 -0
- package/src/Chat/ChatComposerDrawer.doc.mjs +12 -0
- package/src/CheckboxInput/CheckboxInput.doc.mjs +8 -0
- package/src/ClickableCard/ClickableCard.doc.mjs +15 -0
- package/src/DateTimeInput/DateTimeInput.doc.mjs +11 -3
- package/src/DateTimeInput/DateTimeInput.test.tsx +23 -3
- package/src/DateTimeInput/DateTimeInput.tsx +13 -10
- package/src/Dialog/Dialog.doc.mjs +2 -2
- package/src/EmptyState/EmptyState.test.tsx +4 -2
- package/src/EmptyState/EmptyState.tsx +6 -2
- package/src/HoverCard/HoverCard.doc.mjs +3 -0
- package/src/HoverCard/HoverCard.test.tsx +189 -2
- package/src/HoverCard/HoverCard.tsx +20 -16
- package/src/HoverCard/useHoverCard.tsx +13 -10
- package/src/Icon/Icon.doc.mjs +8 -0
- package/src/Kbd/Kbd.tsx +2 -9
- package/src/Layer/anchorName.test.ts +146 -0
- package/src/Layer/anchorName.ts +46 -0
- package/src/Layer/useLayer.doc.mjs +7 -2
- package/src/Layer/useLayer.tsx +33 -9
- package/src/Layout/Layout.doc.mjs +1 -1
- package/src/Lightbox/Lightbox.doc.mjs +0 -2
- package/src/Link/Link.test.tsx +2 -2
- package/src/Link/Link.tsx +3 -3
- package/src/Link/LinkProvider.tsx +1 -0
- package/src/List/List.doc.mjs +7 -0
- package/src/Markdown/Markdown.doc.mjs +2 -0
- package/src/Markdown/Markdown.test.tsx +17 -26
- package/src/Markdown/Markdown.tsx +16 -6
- package/src/MobileNav/MobileNav.doc.mjs +8 -8
- package/src/MobileNav/MobileNav.tsx +13 -0
- package/src/MobileNav/MobileNavReopen.test.tsx +118 -0
- package/src/OverflowList/OverflowList.doc.mjs +17 -0
- package/src/Pagination/Pagination.test.tsx +137 -13
- package/src/Pagination/Pagination.tsx +33 -28
- package/src/ProgressBar/ProgressBar.test.tsx +13 -3
- package/src/Resizable/Resizable.doc.mjs +1 -1
- package/src/Section/Section.doc.mjs +11 -0
- package/src/SelectableCard/SelectableCard.doc.mjs +15 -0
- package/src/Selector/Selector.doc.mjs +4 -0
- package/src/Skeleton/Skeleton.doc.mjs +11 -1
- package/src/Slider/Slider.doc.mjs +4 -0
- package/src/Slider/Slider.test.tsx +3 -16
- package/src/Slider/Slider.tsx +1 -1
- package/src/TabList/TabList.doc.mjs +6 -0
- package/src/Table/BaseTable.tsx +51 -24
- package/src/Table/Table.doc.mjs +12 -0
- package/src/Table/Table.test.tsx +29 -30
- package/src/Table/Table.tsx +22 -1
- package/src/Table/TableRow.tsx +37 -7
- package/src/Table/index.ts +3 -0
- package/src/Table/plugins/stickyColumns/index.ts +4 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.test.tsx +163 -0
- package/src/Table/plugins/stickyColumns/useTableStickyColumns.tsx +407 -0
- package/src/Table/types.ts +101 -4
- package/src/Table/useBaseTablePlugins.ts +1 -0
- package/src/Table/useTableColumnResize.doc.mjs +96 -0
- package/src/Table/useTableStickyColumns.doc.mjs +59 -0
- package/src/Text/Heading.doc.mjs +15 -2
- package/src/Text/Text.doc.mjs +7 -1
- package/src/Text/text.stylex.ts +2 -2
- package/src/Timestamp/Timestamp.doc.mjs +9 -0
- package/src/Timestamp/Timestamp.test.tsx +44 -18
- package/src/Timestamp/Timestamp.tsx +30 -10
- package/src/ToggleButton/ToggleButton.doc.mjs +9 -3
- package/src/ToggleButton/ToggleButton.test.tsx +39 -18
- package/src/ToggleButton/ToggleButton.tsx +46 -83
- package/src/Tokenizer/Tokenizer.tsx +4 -1
- package/src/Toolbar/Toolbar.doc.mjs +65 -5
- package/src/Tooltip/Tooltip.test.tsx +13 -0
- package/src/Tooltip/useTooltip.tsx +2 -3
- package/src/hooks/useImageMode.test.ts +133 -0
- package/src/theme/derivedVarRegistry.test.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +7 -8
- package/src/theme/generateThemeRules.ts +1 -1
- package/src/theme/syntax/SyntaxTheme.tsx +1 -1
- package/src/theme/syntax/defineSyntaxTheme.ts +20 -11
- package/src/theme/syntax/presets.ts +5 -2
- package/src/theme/syntax/tokens.ts +1 -1
- package/src/theme/types.ts +1 -1
- package/src/utils/dateTypes.test.ts +40 -0
- package/src/utils/dateTypes.ts +40 -0
|
@@ -20,14 +20,7 @@
|
|
|
20
20
|
* - /packages/cli/templates/blocks/components/ToggleButton/ (showcase blocks)
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
import React, {
|
|
24
|
-
useCallback,
|
|
25
|
-
useEffect,
|
|
26
|
-
useOptimistic,
|
|
27
|
-
useState,
|
|
28
|
-
useTransition,
|
|
29
|
-
type ReactNode,
|
|
30
|
-
} from 'react';
|
|
23
|
+
import React, {useOptimistic, type ReactNode} from 'react';
|
|
31
24
|
import * as stylex from '@stylexjs/stylex';
|
|
32
25
|
import {colorVars, fontWeightVars} from '../theme/tokens.stylex';
|
|
33
26
|
|
|
@@ -36,37 +29,6 @@ import {useToggleButtonGroup} from './ToggleButtonGroup';
|
|
|
36
29
|
import type {BaseProps} from '../BaseProps';
|
|
37
30
|
import {themeProps} from '../utils/themeProps';
|
|
38
31
|
|
|
39
|
-
// =============================================================================
|
|
40
|
-
// Constants & helpers
|
|
41
|
-
// =============================================================================
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* The spinner only appears once the action has been pending for this long.
|
|
45
|
-
* A fast action shows the optimistic pressed state immediately with no spinner
|
|
46
|
-
* flash, and rapid re-clicks can interrupt the in-flight action before the
|
|
47
|
-
* button locks behind the spinner.
|
|
48
|
-
*/
|
|
49
|
-
const PENDING_SPINNER_DELAY_MS = 150;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Returns `true` only once `active` has stayed `true` for `delayMs`.
|
|
53
|
-
* Used to debounce the loading spinner so the optimistic state shows first.
|
|
54
|
-
*/
|
|
55
|
-
function useDelayed(active: boolean, delayMs: number): boolean {
|
|
56
|
-
const [delayed, setDelayed] = useState(false);
|
|
57
|
-
useEffect(() => {
|
|
58
|
-
if (!active) {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
const timer = setTimeout(() => setDelayed(true), delayMs);
|
|
62
|
-
return () => {
|
|
63
|
-
clearTimeout(timer);
|
|
64
|
-
setDelayed(false);
|
|
65
|
-
};
|
|
66
|
-
}, [active, delayMs]);
|
|
67
|
-
return active && delayed;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
32
|
// =============================================================================
|
|
71
33
|
// Styles
|
|
72
34
|
// =============================================================================
|
|
@@ -122,23 +84,24 @@ export interface ToggleButtonProps extends BaseProps<HTMLButtonElement> {
|
|
|
122
84
|
isPressed?: boolean;
|
|
123
85
|
|
|
124
86
|
/**
|
|
125
|
-
* Called when the pressed state should change.
|
|
87
|
+
* Called when the pressed state should change. Receives the next pressed
|
|
88
|
+
* state and the originating click event. Call `event.preventDefault()` to
|
|
89
|
+
* opt out of running `pressedChangeAction` (e.g. to handle the toggle
|
|
90
|
+
* entirely in this callback).
|
|
126
91
|
* When used inside ToggleButtonGroup, this is handled by the group
|
|
127
92
|
* and this prop is ignored.
|
|
128
93
|
*/
|
|
129
|
-
onPressedChange?: (
|
|
94
|
+
onPressedChange?: (
|
|
95
|
+
isPressed: boolean,
|
|
96
|
+
event: React.MouseEvent<HTMLButtonElement>,
|
|
97
|
+
) => void;
|
|
130
98
|
|
|
131
99
|
/**
|
|
132
100
|
* Action handler for API- or navigation-backed toggles, run inside a
|
|
133
|
-
* transition. The button shows a loading spinner while the action
|
|
134
|
-
* pending — whether it returns a promise or synchronously triggers a
|
|
101
|
+
* transition by Button. The button shows a loading spinner while the action
|
|
102
|
+
* is pending — whether it returns a promise or synchronously triggers a
|
|
135
103
|
* suspending update (e.g. a router navigation that suspends on data).
|
|
136
104
|
*
|
|
137
|
-
* Because it runs in a transition, the toggle is *interruptible*: clicking
|
|
138
|
-
* again while an action is pending starts a new transition with the next
|
|
139
|
-
* optimistic state, so the action reflects the latest intent rather than
|
|
140
|
-
* being dropped.
|
|
141
|
-
*
|
|
142
105
|
* @example
|
|
143
106
|
* ```
|
|
144
107
|
* <ToggleButton
|
|
@@ -272,54 +235,52 @@ export function ToggleButton({
|
|
|
272
235
|
const size = sizeProp ?? group?.size ?? 'md';
|
|
273
236
|
const isDisabled = group?.isDisabled ?? isDisabledProp;
|
|
274
237
|
|
|
275
|
-
// Track the pressed state optimistically
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
238
|
+
// Track the pressed state optimistically so the button reflects the intended
|
|
239
|
+
// state immediately while an async action is pending. The optimistic update
|
|
240
|
+
// is applied inside Button's clickAction transition (below), satisfying
|
|
241
|
+
// React's "useOptimistic must run in a transition" rule.
|
|
279
242
|
const [optimisticPressed, setOptimisticPressed] =
|
|
280
243
|
useOptimistic(committedPressed);
|
|
281
244
|
const isPressed = optimisticPressed;
|
|
282
245
|
|
|
246
|
+
// Next state derives from the *optimistic* value, not the committed one, so a
|
|
247
|
+
// re-click while an action is pending reads the in-flight intent and toggles
|
|
248
|
+
// (true -> false -> true) rather than recomputing from the stale committed
|
|
249
|
+
// state. handleClick and clickAction share this so the callback and the
|
|
250
|
+
// optimistic update always agree within a single click.
|
|
251
|
+
const nextPressed = !isPressed;
|
|
252
|
+
|
|
283
253
|
const resolvedIcon = isPressed && pressedIcon ? pressedIcon : icon;
|
|
284
254
|
|
|
285
|
-
//
|
|
286
|
-
//
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
// which means a synchronous-but-suspending handler (e.g. a router navigation
|
|
290
|
-
// that suspends on data) also drives the pending state — not just promises.
|
|
291
|
-
const [isPending, startTransition] = useTransition();
|
|
292
|
-
// Debounce the spinner so a fast action shows the optimistic state without a
|
|
293
|
-
// spinner flash, and rapid re-clicks can interrupt before the button locks.
|
|
294
|
-
const showSpinner = useDelayed(isPending, PENDING_SPINNER_DELAY_MS);
|
|
295
|
-
const isLoadingState = isLoading || showSpinner;
|
|
296
|
-
|
|
297
|
-
const handleClick = useCallback(() => {
|
|
255
|
+
// Synchronous part of the toggle. Button calls onClick before clickAction and
|
|
256
|
+
// skips clickAction when the event is defaultPrevented, so calling
|
|
257
|
+
// preventDefault() inside onPressedChange opts out of pressedChangeAction.
|
|
258
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
298
259
|
if (isDisabled) {
|
|
299
260
|
return;
|
|
300
261
|
}
|
|
301
|
-
|
|
302
262
|
if (group && value != null) {
|
|
303
263
|
// Group mode delegates selection to the group; no async-action path.
|
|
304
264
|
group.toggle(value);
|
|
265
|
+
event.preventDefault();
|
|
305
266
|
return;
|
|
306
267
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
value
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
268
|
+
onPressedChangeProp?.(nextPressed, event);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
// Async part of the toggle, run inside Button's transition (so the optimistic
|
|
272
|
+
// update is valid and Button's isPending drives the pending spinner). Skipped
|
|
273
|
+
// automatically by Button when handleClick called preventDefault, and never
|
|
274
|
+
// wired in group mode (where there is no async action). Button is rendered
|
|
275
|
+
// interruptible, so it stays clickable while pending and a re-click starts a
|
|
276
|
+
// fresh transition that interrupts the previous one.
|
|
277
|
+
const clickAction =
|
|
278
|
+
group && value != null
|
|
279
|
+
? undefined
|
|
280
|
+
: async () => {
|
|
281
|
+
setOptimisticPressed(nextPressed);
|
|
282
|
+
await pressedChangeAction?.(nextPressed);
|
|
283
|
+
};
|
|
323
284
|
|
|
324
285
|
// isIconOnly prop is the source of truth for icon-only rendering.
|
|
325
286
|
const labelContent =
|
|
@@ -352,7 +313,8 @@ export function ToggleButton({
|
|
|
352
313
|
variant="ghost"
|
|
353
314
|
size={size}
|
|
354
315
|
isDisabled={isDisabled}
|
|
355
|
-
isLoading={
|
|
316
|
+
isLoading={isLoading}
|
|
317
|
+
isInterruptible
|
|
356
318
|
isIconOnly={isIconOnly}
|
|
357
319
|
aria-pressed={isPressed}
|
|
358
320
|
icon={resolvedIcon}
|
|
@@ -363,6 +325,7 @@ export function ToggleButton({
|
|
|
363
325
|
xstyle={[isPressed ? pressedStyles.background : undefined, xstyle]}
|
|
364
326
|
style={style}
|
|
365
327
|
onClick={handleClick}
|
|
328
|
+
clickAction={clickAction}
|
|
366
329
|
{...props}>
|
|
367
330
|
{labelContent}
|
|
368
331
|
</Button>
|
|
@@ -222,7 +222,10 @@ const styles = stylex.create({
|
|
|
222
222
|
},
|
|
223
223
|
endSection: {
|
|
224
224
|
position: 'absolute',
|
|
225
|
-
|
|
225
|
+
// Match the field's inline padding (inputWrapperStyles.base uses
|
|
226
|
+
// spacing-2) so end content (clear button, resultCount) lines up with
|
|
227
|
+
// the text/start-icon inset instead of hugging the border at ~3px.
|
|
228
|
+
insetInlineEnd: spacingVars['--spacing-2'],
|
|
226
229
|
display: 'flex',
|
|
227
230
|
alignItems: 'center',
|
|
228
231
|
gap: spacingVars['--spacing-2'],
|
|
@@ -7,6 +7,25 @@ export const docs = {
|
|
|
7
7
|
displayName: 'Toolbar',
|
|
8
8
|
category: 'Action',
|
|
9
9
|
keywords: ['toolbar', 'nav', 'bar', 'actions', 'buttonbar', 'header', 'footer', 'action-bar', 'control-bar'],
|
|
10
|
+
playground: {
|
|
11
|
+
defaults: {
|
|
12
|
+
label: 'Table actions',
|
|
13
|
+
size: 'sm',
|
|
14
|
+
dividers: ['bottom'],
|
|
15
|
+
startContent: {
|
|
16
|
+
__element: 'TabList',
|
|
17
|
+
props: {value: 'overview'},
|
|
18
|
+
children: [
|
|
19
|
+
{__element: 'Tab', props: {label: 'Overview', value: 'overview'}},
|
|
20
|
+
{__element: 'Tab', props: {label: 'Activity', value: 'activity'}},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
endContent: [
|
|
24
|
+
{__element: 'Selector', props: {label: 'Status', isLabelHidden: true, placeholder: 'Status', size: 'sm', options: ['Open', 'In progress', 'Done']}},
|
|
25
|
+
{__element: 'Button', props: {label: 'New item', variant: 'primary', size: 'sm'}},
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
},
|
|
10
29
|
theming: {
|
|
11
30
|
targets: [
|
|
12
31
|
{className: 'astryx-toolbar', states: ['size']},
|
|
@@ -29,21 +48,62 @@ export const docs = {
|
|
|
29
48
|
name: 'startContent',
|
|
30
49
|
type: 'ReactNode',
|
|
31
50
|
description: 'Content aligned to the start (left in LTR).',
|
|
32
|
-
slotElements: [
|
|
51
|
+
slotElements: [
|
|
52
|
+
{__element: 'Text', props: {type: 'body', weight: 'bold'}, children: 'Title'},
|
|
53
|
+
{__element: 'Button', props: {label: 'Back', variant: 'ghost', size: 'sm'}},
|
|
54
|
+
{__element: 'IconButton', props: {label: 'Filter', icon: {__element: 'Icon', props: {icon: 'check', size: 'sm'}}, variant: 'ghost', size: 'sm'}},
|
|
55
|
+
{
|
|
56
|
+
__element: 'TabList',
|
|
57
|
+
props: {value: 'overview'},
|
|
58
|
+
children: [
|
|
59
|
+
{__element: 'Tab', props: {label: 'Overview', value: 'overview'}},
|
|
60
|
+
{__element: 'Tab', props: {label: 'Activity', value: 'activity'}},
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
__element: 'SegmentedControl',
|
|
65
|
+
props: {label: 'View', value: 'list'},
|
|
66
|
+
children: [
|
|
67
|
+
{__element: 'SegmentedControlItem', props: {label: 'List', value: 'list'}},
|
|
68
|
+
{__element: 'SegmentedControlItem', props: {label: 'Grid', value: 'grid'}},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
{__element: 'Selector', props: {label: 'Status', isLabelHidden: true, placeholder: 'Status', size: 'sm', options: ['Open', 'In progress', 'Done']}},
|
|
72
|
+
],
|
|
33
73
|
},
|
|
34
74
|
{
|
|
35
75
|
name: 'centerContent',
|
|
36
76
|
type: 'ReactNode',
|
|
37
77
|
description:
|
|
38
78
|
'Centered content. Switches layout to CSS grid (1fr auto 1fr).',
|
|
39
|
-
slotElements: [
|
|
79
|
+
slotElements: [
|
|
80
|
+
{__element: 'Text', props: {type: 'body', weight: 'bold'}, children: 'Center'},
|
|
81
|
+
{
|
|
82
|
+
__element: 'SegmentedControl',
|
|
83
|
+
props: {label: 'View', value: 'list'},
|
|
84
|
+
children: [
|
|
85
|
+
{__element: 'SegmentedControlItem', props: {label: 'List', value: 'list'}},
|
|
86
|
+
{__element: 'SegmentedControlItem', props: {label: 'Grid', value: 'grid'}},
|
|
87
|
+
],
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
__element: 'TabList',
|
|
91
|
+
props: {value: 'overview'},
|
|
92
|
+
children: [
|
|
93
|
+
{__element: 'Tab', props: {label: 'Overview', value: 'overview'}},
|
|
94
|
+
{__element: 'Tab', props: {label: 'Activity', value: 'activity'}},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
],
|
|
40
98
|
},
|
|
41
99
|
{
|
|
42
100
|
name: 'endContent',
|
|
43
101
|
type: 'ReactNode',
|
|
44
102
|
description: 'Content aligned to the end (right in LTR).',
|
|
45
103
|
slotElements: [
|
|
46
|
-
{__element: '
|
|
104
|
+
{__element: 'Button', props: {label: 'Save', variant: 'primary', size: 'sm'}},
|
|
105
|
+
{__element: 'IconButton', props: {label: 'More', icon: {__element: 'Icon', props: {icon: 'chevronDown', size: 'sm'}}, variant: 'ghost', size: 'sm'}},
|
|
106
|
+
{__element: 'Selector', props: {label: 'Sort', isLabelHidden: true, placeholder: 'Sort by', size: 'sm', options: ['Newest', 'Oldest', 'A–Z']}},
|
|
47
107
|
{__element: 'Badge', props: {label: '3'}},
|
|
48
108
|
],
|
|
49
109
|
},
|
|
@@ -51,14 +111,14 @@ export const docs = {
|
|
|
51
111
|
name: 'size',
|
|
52
112
|
type: "'sm' | 'md' | 'lg'",
|
|
53
113
|
description:
|
|
54
|
-
'Size of the toolbar. Controls minimum height and coordinates with Button, TextInput, TabList, and Selector
|
|
114
|
+
'Size of the toolbar. Controls minimum height and coordinates with Button, TextInput, TabList, and Selector; children inherit this size as their default via SizeContext.',
|
|
55
115
|
default: "'md'",
|
|
56
116
|
},
|
|
57
117
|
{
|
|
58
118
|
name: 'gap',
|
|
59
119
|
type: 'SpacingStep',
|
|
60
120
|
description: 'Gap between items within each slot.',
|
|
61
|
-
default: '
|
|
121
|
+
default: '1',
|
|
62
122
|
},
|
|
63
123
|
{
|
|
64
124
|
name: 'orientation',
|
|
@@ -55,6 +55,19 @@ describe('Tooltip', () => {
|
|
|
55
55
|
expect(screen.getByRole('button', {name: 'Trigger'})).toBeInTheDocument();
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it('gives the tooltip layer role="tooltip" linked from the trigger', () => {
|
|
59
|
+
render(
|
|
60
|
+
<Tooltip content="Tooltip text">
|
|
61
|
+
<button type="button">Trigger</button>
|
|
62
|
+
</Tooltip>,
|
|
63
|
+
);
|
|
64
|
+
const layer = screen.getByRole('tooltip', {hidden: true});
|
|
65
|
+
expect(layer).toHaveTextContent('Tooltip text');
|
|
66
|
+
// ARIA tooltip pattern: trigger references the layer via aria-describedby.
|
|
67
|
+
const trigger = screen.getByRole('button', {name: 'Trigger'});
|
|
68
|
+
expect(trigger.getAttribute('aria-describedby')).toBe(layer.id);
|
|
69
|
+
});
|
|
70
|
+
|
|
58
71
|
it('calls onOpenChange(true) when shown via hover', async () => {
|
|
59
72
|
const onOpenChange = vi.fn();
|
|
60
73
|
render(
|
|
@@ -233,9 +233,7 @@ function isFocusable(element: HTMLElement): boolean {
|
|
|
233
233
|
* {tooltip.renderTooltip('Helpful tooltip text')}
|
|
234
234
|
* ```
|
|
235
235
|
*/
|
|
236
|
-
export function useTooltip(
|
|
237
|
-
options: TooltipOptions = {},
|
|
238
|
-
): TooltipReturn {
|
|
236
|
+
export function useTooltip(options: TooltipOptions = {}): TooltipReturn {
|
|
239
237
|
const {
|
|
240
238
|
placement = 'above',
|
|
241
239
|
alignment = 'center',
|
|
@@ -429,6 +427,7 @@ export function useTooltip(
|
|
|
429
427
|
const renderProps = {
|
|
430
428
|
placement: renderPlacement,
|
|
431
429
|
alignment: props?.alignment ?? alignment,
|
|
430
|
+
role: 'tooltip',
|
|
432
431
|
xstyle: [popoverXstyle, layerAnimations[renderPlacement]],
|
|
433
432
|
className: themeProps('tooltip').className,
|
|
434
433
|
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file useImageMode.test.ts
|
|
5
|
+
* @input Uses vitest, @testing-library/react, useImageMode hook
|
|
6
|
+
* @output Unit tests for useImageMode hook
|
|
7
|
+
* @position Testing; validates useImageMode.ts pixel-sampling + CORS behavior
|
|
8
|
+
*
|
|
9
|
+
* SYNC: When useImageMode.ts changes, update tests to match new behavior
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
|
+
import {renderHook, waitFor, act} from '@testing-library/react';
|
|
14
|
+
import {useImageMode} from './useImageMode';
|
|
15
|
+
|
|
16
|
+
// jsdom has no fetch/createImageBitmap/OffscreenCanvas — stub them per test so
|
|
17
|
+
// we can exercise the sampling path and the cross-origin failure path.
|
|
18
|
+
|
|
19
|
+
/** Fill the sampled canvas with a flat RGB so perceptualLightness is deterministic. */
|
|
20
|
+
function stubSamplingPipeline(rgb: [number, number, number]) {
|
|
21
|
+
// fetch resolves to a blob (same-origin / CORS-enabled image)
|
|
22
|
+
vi.stubGlobal(
|
|
23
|
+
'fetch',
|
|
24
|
+
vi.fn(async () => ({blob: async () => ({}) as Blob})),
|
|
25
|
+
);
|
|
26
|
+
vi.stubGlobal(
|
|
27
|
+
'createImageBitmap',
|
|
28
|
+
vi.fn(async () => ({width: 100, height: 100}) as ImageBitmap),
|
|
29
|
+
);
|
|
30
|
+
const [r, g, b] = rgb;
|
|
31
|
+
class FakeOffscreenCanvas {
|
|
32
|
+
constructor(
|
|
33
|
+
public width: number,
|
|
34
|
+
public height: number,
|
|
35
|
+
) {}
|
|
36
|
+
getContext() {
|
|
37
|
+
return {
|
|
38
|
+
drawImage: vi.fn(),
|
|
39
|
+
getImageData: (_x: number, _y: number, w: number, h: number) => {
|
|
40
|
+
const data = new Uint8ClampedArray(w * h * 4);
|
|
41
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
42
|
+
data[i] = r;
|
|
43
|
+
data[i + 1] = g;
|
|
44
|
+
data[i + 2] = b;
|
|
45
|
+
data[i + 3] = 255;
|
|
46
|
+
}
|
|
47
|
+
return {data};
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
vi.stubGlobal('OffscreenCanvas', FakeOffscreenCanvas);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Simulate a cross-origin image with no CORS headers: fetch rejects. */
|
|
56
|
+
function stubCorsBlockedFetch() {
|
|
57
|
+
vi.stubGlobal(
|
|
58
|
+
'fetch',
|
|
59
|
+
vi.fn(async () => {
|
|
60
|
+
throw new TypeError(
|
|
61
|
+
'Failed to fetch: No Access-Control-Allow-Origin header',
|
|
62
|
+
);
|
|
63
|
+
}),
|
|
64
|
+
);
|
|
65
|
+
vi.stubGlobal('createImageBitmap', vi.fn());
|
|
66
|
+
vi.stubGlobal('OffscreenCanvas', class {});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
describe('useImageMode', () => {
|
|
70
|
+
beforeEach(() => {
|
|
71
|
+
vi.restoreAllMocks();
|
|
72
|
+
});
|
|
73
|
+
afterEach(() => {
|
|
74
|
+
vi.unstubAllGlobals();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('returns the fallback while no src is provided', () => {
|
|
78
|
+
const {result} = renderHook(() => useImageMode(null, {fallback: 'dark'}));
|
|
79
|
+
expect(result.current).toBe('dark');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('samples a dark image and reports "dark"', async () => {
|
|
83
|
+
stubSamplingPipeline([10, 10, 10]);
|
|
84
|
+
const {result} = renderHook(() =>
|
|
85
|
+
useImageMode('data:image/png;base64,AAAA'),
|
|
86
|
+
);
|
|
87
|
+
await waitFor(() => expect(result.current).toBe('dark'));
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('samples a light image and reports "light"', async () => {
|
|
91
|
+
stubSamplingPipeline([240, 240, 240]);
|
|
92
|
+
const {result} = renderHook(() =>
|
|
93
|
+
useImageMode('data:image/png;base64,AAAA'),
|
|
94
|
+
);
|
|
95
|
+
await waitFor(() => expect(result.current).toBe('light'));
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── Reproduction for BB-001 (cluster C6) ────────────────────────────────
|
|
99
|
+
// A cross-origin image with no `Access-Control-Allow-Origin` header cannot be
|
|
100
|
+
// fetched with `mode: 'cors'`, so the APCA sampling pipeline never runs and the
|
|
101
|
+
// hook silently stays on its fallback. This is why image-backed Thumbnail
|
|
102
|
+
// overlay/remove-button contrast is unreliable for hosted demo examples that
|
|
103
|
+
// point `src` at a non-CORS CDN URL.
|
|
104
|
+
it('stays on fallback when a cross-origin image cannot be sampled (CORS)', async () => {
|
|
105
|
+
stubCorsBlockedFetch();
|
|
106
|
+
const {result} = renderHook(() =>
|
|
107
|
+
useImageMode(
|
|
108
|
+
'https://lookaside.facebook.com/assets/astryx/moody-scene-vertical-2.png',
|
|
109
|
+
{fallback: null},
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
// Give the rejected fetch + state update a chance to flush.
|
|
113
|
+
await act(async () => {
|
|
114
|
+
await new Promise(r => setTimeout(r, 0));
|
|
115
|
+
});
|
|
116
|
+
expect(result.current).toBeNull();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('requests the image with CORS mode (the constraint that fails for non-CORS hosts)', async () => {
|
|
120
|
+
const fetchSpy = vi.fn(async () => {
|
|
121
|
+
throw new TypeError('CORS');
|
|
122
|
+
});
|
|
123
|
+
vi.stubGlobal('fetch', fetchSpy);
|
|
124
|
+
vi.stubGlobal('createImageBitmap', vi.fn());
|
|
125
|
+
vi.stubGlobal('OffscreenCanvas', class {});
|
|
126
|
+
renderHook(() => useImageMode('https://cross-origin.example/x.png'));
|
|
127
|
+
await waitFor(() => expect(fetchSpy).toHaveBeenCalled());
|
|
128
|
+
expect(fetchSpy).toHaveBeenCalledWith(
|
|
129
|
+
'https://cross-origin.example/x.png',
|
|
130
|
+
{mode: 'cors'},
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -64,6 +64,10 @@ const STRUCTURAL_VARS = new Set([
|
|
|
64
64
|
'--indicator-color',
|
|
65
65
|
'--indicator-width',
|
|
66
66
|
'--table-resize-height',
|
|
67
|
+
// sticky-columns plugin: opaque backdrop (overridable) + the row overlay it
|
|
68
|
+
// replays on pinned cells. Structural/runtime, not themeable design tokens.
|
|
69
|
+
'--table-sticky-background',
|
|
70
|
+
'--table-row-overlay',
|
|
67
71
|
'--separator-display',
|
|
68
72
|
'--astryx-section-padding',
|
|
69
73
|
]);
|
|
@@ -101,7 +101,9 @@ describe('generateThemeRules', () => {
|
|
|
101
101
|
|
|
102
102
|
it('includes .astryx-heading.level-* rules for all 6 levels', () => {
|
|
103
103
|
for (let level = 1; level <= 6; level++) {
|
|
104
|
-
const rule = rules.find(r =>
|
|
104
|
+
const rule = rules.find(r =>
|
|
105
|
+
r.includes(`.astryx-heading.level-${level}`),
|
|
106
|
+
);
|
|
105
107
|
expect(rule).toBeDefined();
|
|
106
108
|
expect(rule).toContain('font-family');
|
|
107
109
|
expect(rule).toContain(`var(--text-heading-${level}-size)`);
|
|
@@ -137,9 +139,7 @@ describe('generateThemeRules', () => {
|
|
|
137
139
|
});
|
|
138
140
|
const pseudoRules = generateThemeRules(pseudoTheme);
|
|
139
141
|
expect(
|
|
140
|
-
pseudoRules.some(rule =>
|
|
141
|
-
rule.includes('.astryx-button:hover'),
|
|
142
|
-
),
|
|
142
|
+
pseudoRules.some(rule => rule.includes('.astryx-button:hover')),
|
|
143
143
|
).toBe(true);
|
|
144
144
|
});
|
|
145
145
|
|
|
@@ -186,7 +186,8 @@ describe('generateThemeRules', () => {
|
|
|
186
186
|
expect(rules.some(r => r.includes('.astryx-text.secondary'))).toBe(true);
|
|
187
187
|
expect(rules.some(r => r.includes('.astryx-heading.primary'))).toBe(true);
|
|
188
188
|
expect(rules.some(r => r.includes('.astryx-heading.disabled'))).toBe(true);
|
|
189
|
-
expect(rules.some(r => r.includes('.astryx-text.active'))).toBe(
|
|
189
|
+
expect(rules.some(r => r.includes('.astryx-text.active'))).toBe(false);
|
|
190
|
+
expect(rules.some(r => r.includes('.astryx-text.accent'))).toBe(true);
|
|
190
191
|
});
|
|
191
192
|
|
|
192
193
|
// --- Consistency ---
|
|
@@ -194,9 +195,7 @@ describe('generateThemeRules', () => {
|
|
|
194
195
|
it('generateThemeCSS returns prose and component blocks with @scope', () => {
|
|
195
196
|
const {prose, component} = generateThemeCSS(theme);
|
|
196
197
|
const combined = prose + component;
|
|
197
|
-
expect(combined).toContain(
|
|
198
|
-
'@scope ([data-astryx-theme="default"])',
|
|
199
|
-
);
|
|
198
|
+
expect(combined).toContain('@scope ([data-astryx-theme="default"])');
|
|
200
199
|
expect(combined).toContain('to ([data-astryx-theme])');
|
|
201
200
|
// Every rule from generateThemeRules should appear in one of the blocks
|
|
202
201
|
for (const rule of rules) {
|
|
@@ -472,7 +472,7 @@ function generateColorOverrides(
|
|
|
472
472
|
secondary: 'var(--color-text-secondary)',
|
|
473
473
|
disabled: 'var(--color-text-disabled)',
|
|
474
474
|
placeholder: 'var(--color-text-secondary)',
|
|
475
|
-
|
|
475
|
+
accent: 'var(--color-text-accent)',
|
|
476
476
|
};
|
|
477
477
|
|
|
478
478
|
const touchesText = 'text' in components;
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @output SyntaxTheme provider, useSyntaxTheme hook
|
|
9
9
|
* @position Provider component; wraps code surfaces to apply syntax coloring
|
|
10
10
|
*
|
|
11
|
-
* @see https://github.com/
|
|
11
|
+
* @see https://github.com/facebook/astryx/issues/1148
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import React, {createContext, use, useMemo} from 'react';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
'use strict';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file defineSyntaxTheme.ts
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @output defineSyntaxTheme, SyntaxTheme, syntaxThemeStyle
|
|
9
9
|
* @position Syntax theme definition API; consumed by presets, SyntaxTheme, defineTheme
|
|
10
10
|
*
|
|
11
|
-
* @see https://github.com/
|
|
11
|
+
* @see https://github.com/facebook/astryx/issues/1148
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import {syntaxTokenDefaults, type SyntaxTokenName} from './tokens';
|
|
@@ -41,7 +41,10 @@ export type SyntaxThemeTokenKey =
|
|
|
41
41
|
export type SyntaxTokenValue = string | [light: string, dark: string];
|
|
42
42
|
|
|
43
43
|
/** Token map for defineSyntaxTheme input — values can be strings or tuples. */
|
|
44
|
-
export type SyntaxThemeTokenInput = Record<
|
|
44
|
+
export type SyntaxThemeTokenInput = Record<
|
|
45
|
+
SyntaxThemeTokenKey,
|
|
46
|
+
SyntaxTokenValue
|
|
47
|
+
>;
|
|
45
48
|
|
|
46
49
|
/** Resolved token map — all values are CSS strings (tuples resolved to light-dark()). */
|
|
47
50
|
export type SyntaxThemeTokenMap = Record<SyntaxThemeTokenKey, string>;
|
|
@@ -73,8 +76,9 @@ function toCSSProperty(key: SyntaxThemeTokenKey): SyntaxTokenName {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
/** All valid human-readable token keys, derived from the defaults. */
|
|
76
|
-
export const ALL_SYNTAX_KEYS: SyntaxThemeTokenKey[] = Object.keys(
|
|
77
|
-
|
|
79
|
+
export const ALL_SYNTAX_KEYS: SyntaxThemeTokenKey[] = Object.keys(
|
|
80
|
+
syntaxTokenDefaults,
|
|
81
|
+
).map(k => k.replace(CSS_PREFIX, '') as SyntaxThemeTokenKey);
|
|
78
82
|
|
|
79
83
|
// =============================================================================
|
|
80
84
|
// Helpers
|
|
@@ -130,12 +134,17 @@ export function resolveSyntaxTokenForMode(
|
|
|
130
134
|
* },
|
|
131
135
|
* });
|
|
132
136
|
*/
|
|
133
|
-
export function defineSyntaxTheme(
|
|
137
|
+
export function defineSyntaxTheme(
|
|
138
|
+
input: SyntaxThemeInput,
|
|
139
|
+
): SyntaxThemeDefinition {
|
|
134
140
|
const missing = ALL_SYNTAX_KEYS.filter(key => !(key in input.tokens));
|
|
135
141
|
if (missing.length > 0) {
|
|
136
142
|
console.warn(
|
|
137
|
-
'[Astryx] defineSyntaxTheme("' +
|
|
138
|
-
|
|
143
|
+
'[Astryx] defineSyntaxTheme("' +
|
|
144
|
+
input.name +
|
|
145
|
+
'"): missing tokens: ' +
|
|
146
|
+
missing.join(', ') +
|
|
147
|
+
'. All 14 syntax tokens are required.',
|
|
139
148
|
);
|
|
140
149
|
}
|
|
141
150
|
|
|
@@ -169,7 +178,7 @@ export function syntaxThemeStyle(
|
|
|
169
178
|
|
|
170
179
|
/** Convert a syntax theme to CSS declarations (no selector wrapper). */
|
|
171
180
|
export function syntaxThemeToCSS(theme: SyntaxThemeDefinition): string {
|
|
172
|
-
return ALL_SYNTAX_KEYS
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
return ALL_SYNTAX_KEYS.map(
|
|
182
|
+
key => toCSSProperty(key) + ': ' + theme.tokens[key] + ';',
|
|
183
|
+
).join('\n ');
|
|
175
184
|
}
|
|
@@ -8,10 +8,13 @@
|
|
|
8
8
|
* All original themes are MIT licensed. Only color values are extracted.
|
|
9
9
|
* See THIRD_PARTY_LICENSES.md for full attribution.
|
|
10
10
|
*
|
|
11
|
-
* @see https://github.com/
|
|
11
|
+
* @see https://github.com/facebook/astryx/issues/1148
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
defineSyntaxTheme,
|
|
16
|
+
type SyntaxThemeDefinition,
|
|
17
|
+
} from './defineSyntaxTheme';
|
|
15
18
|
|
|
16
19
|
/** One Dark Pro (Binaryify) — MIT */
|
|
17
20
|
export const oneDarkPro: SyntaxThemeDefinition = defineSyntaxTheme({
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 14-token architecture validated against 11 community code themes.
|
|
17
17
|
* All themes map cleanly to these 14 slots.
|
|
18
18
|
*
|
|
19
|
-
* @see https://github.com/
|
|
19
|
+
* @see https://github.com/facebook/astryx/issues/1148
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
export const syntaxTokenDefaults = {
|