@astryxdesign/cli 0.1.4-canary.e9b31f8 → 0.1.4-canary.ec9f8b2
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/docs/working-with-ai.doc.mjs +1 -1
- package/package.json +9 -9
- package/src/codemods/__tests__/registry.test.mjs +1 -0
- package/src/codemods/registry.mjs +1 -0
- package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs +93 -0
- package/src/codemods/transforms/v0.1.5/index.mjs +19 -0
- package/src/codemods/transforms/v0.1.5/rename-switch-label-spacing-default-to-hug.mjs +140 -0
- package/src/commands/build-theme.mjs +2 -8
- package/templates/blocks/components/AspectRatio/AspectRatioCircleImage.tsx +1 -6
- package/templates/blocks/components/AspectRatio/AspectRatioImageGallery.tsx +2 -7
- package/templates/blocks/components/AspectRatio/AspectRatioShowcase.tsx +2 -5
- package/templates/blocks/components/AspectRatio/AspectRatioSquareImage.tsx +1 -6
- package/templates/blocks/components/AspectRatio/AspectRatioWidescreen.tsx +1 -6
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.doc.mjs +1 -1
- package/templates/blocks/components/Collapsible/CollapsibleDividedAccordion.tsx +1 -1
- package/templates/pages/kanban-board/page.tsx +37 -13
|
@@ -125,7 +125,7 @@ If you don't know all three, run \`npx astryx init --features agents\` to genera
|
|
|
125
125
|
lang: 'json',
|
|
126
126
|
label: 'package.json',
|
|
127
127
|
code: `"scripts": {
|
|
128
|
-
"
|
|
128
|
+
"astryx": "node node_modules/@astryxdesign/cli/bin/astryx.mjs"
|
|
129
129
|
}`,
|
|
130
130
|
},
|
|
131
131
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/cli",
|
|
3
|
-
"version": "0.1.4-canary.
|
|
3
|
+
"version": "0.1.4-canary.ec9f8b2",
|
|
4
4
|
"displayName": "CLI",
|
|
5
5
|
"description": "Scaffold projects, browse templates, generate themes, and get agent-ready docs from the command line.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -75,10 +75,10 @@
|
|
|
75
75
|
"zod": "^4.4.3"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
78
|
-
"@astryxdesign/charts": "0.1.4-canary.
|
|
79
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
80
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
81
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
78
|
+
"@astryxdesign/charts": "0.1.4-canary.ec9f8b2",
|
|
79
|
+
"@astryxdesign/core": "0.1.4-canary.ec9f8b2",
|
|
80
|
+
"@astryxdesign/lab": "0.1.4-canary.ec9f8b2",
|
|
81
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.ec9f8b2",
|
|
82
82
|
"gpt-tokenizer": "^3.4.0"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
@@ -96,10 +96,10 @@
|
|
|
96
96
|
}
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
|
-
"@astryxdesign/charts": "0.1.4-canary.
|
|
100
|
-
"@astryxdesign/core": "0.1.4-canary.
|
|
101
|
-
"@astryxdesign/lab": "0.1.4-canary.
|
|
102
|
-
"@astryxdesign/theme-neutral": "0.1.4-canary.
|
|
99
|
+
"@astryxdesign/charts": "0.1.4-canary.ec9f8b2",
|
|
100
|
+
"@astryxdesign/core": "0.1.4-canary.ec9f8b2",
|
|
101
|
+
"@astryxdesign/lab": "0.1.4-canary.ec9f8b2",
|
|
102
|
+
"@astryxdesign/theme-neutral": "0.1.4-canary.ec9f8b2",
|
|
103
103
|
"gpt-tokenizer": "^3.4.0"
|
|
104
104
|
},
|
|
105
105
|
"scripts": {
|
|
@@ -20,6 +20,7 @@ const registry = new Map([
|
|
|
20
20
|
['0.1.0', () => import('./transforms/v0.1.0/index.mjs')],
|
|
21
21
|
['0.1.2', () => import('./transforms/v0.1.2/index.mjs')],
|
|
22
22
|
['0.1.3', () => import('./transforms/v0.1.3/index.mjs')],
|
|
23
|
+
['0.1.5', () => import('./transforms/v0.1.5/index.mjs')],
|
|
23
24
|
]);
|
|
24
25
|
|
|
25
26
|
// Re-export from the shared utility so registry callers and other consumers
|
package/src/codemods/transforms/v0.1.5/__tests__/rename-switch-label-spacing-default-to-hug.test.mjs
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect} from 'vitest';
|
|
4
|
+
|
|
5
|
+
async function applyTransform(source) {
|
|
6
|
+
const {default: transform} =
|
|
7
|
+
await import('../rename-switch-label-spacing-default-to-hug.mjs');
|
|
8
|
+
const jscodeshift = (await import('jscodeshift')).default;
|
|
9
|
+
const j = jscodeshift.withParser('tsx');
|
|
10
|
+
const api = {jscodeshift: j, stats: () => {}, report: () => {}};
|
|
11
|
+
const file = {source, path: 'test.tsx'};
|
|
12
|
+
const result = transform(file, api);
|
|
13
|
+
return result ?? source;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe('rename-switch-label-spacing-default-to-hug', () => {
|
|
17
|
+
it('renames Switch labelSpacing="default" to labelSpacing="hug"', async () => {
|
|
18
|
+
const input = `<Switch label="Notify" value={on} labelSpacing="default" />`;
|
|
19
|
+
const output = await applyTransform(input);
|
|
20
|
+
expect(output).toContain("'hug'");
|
|
21
|
+
expect(output).not.toContain('default');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("renames expression-container labelSpacing={'default'}", async () => {
|
|
25
|
+
const input = `<Switch label="Notify" value={on} labelSpacing={'default'} />`;
|
|
26
|
+
const output = await applyTransform(input);
|
|
27
|
+
expect(output).toContain("'hug'");
|
|
28
|
+
expect(output).not.toContain("'default'");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renames default inside a ternary labelSpacing expression', async () => {
|
|
32
|
+
const input = `<Switch label="Notify" value={on} labelSpacing={isRow ? 'spread' : 'default'} />`;
|
|
33
|
+
const output = await applyTransform(input);
|
|
34
|
+
expect(output).toContain("'hug'");
|
|
35
|
+
expect(output).toContain("'spread'");
|
|
36
|
+
expect(output).not.toContain("'default'");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not change labelSpacing="spread"', async () => {
|
|
40
|
+
const input = `<Switch label="Notify" value={on} labelSpacing="spread" />`;
|
|
41
|
+
const output = await applyTransform(input);
|
|
42
|
+
expect(output).toBe(input);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not touch labelSpacing="default" on non-Switch components', async () => {
|
|
46
|
+
const input = `<LegacyToggle labelSpacing="default" />`;
|
|
47
|
+
const output = await applyTransform(input);
|
|
48
|
+
expect(output).toBe(input);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('does not touch other props with a "default" value on Switch', async () => {
|
|
52
|
+
const input = `<Switch label="Notify" value={on} data-variant="default" />`;
|
|
53
|
+
const output = await applyTransform(input);
|
|
54
|
+
expect(output).toBe(input);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('does not touch unrelated "default" string literals', async () => {
|
|
58
|
+
const input = `const theme = {mode: 'default', label: 'Default Mode'};`;
|
|
59
|
+
const output = await applyTransform(input);
|
|
60
|
+
expect(output).toBe(input);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('renames object labelSpacing prop in files importing Switch', async () => {
|
|
64
|
+
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
65
|
+
const cfg = {labelSpacing: 'default'};`;
|
|
66
|
+
const output = await applyTransform(input);
|
|
67
|
+
expect(output).toContain("labelSpacing: 'hug'");
|
|
68
|
+
expect(output).not.toContain("'default'");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("renames labelSpacing: 'default' as const in files importing Switch", async () => {
|
|
72
|
+
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
73
|
+
const ROWS = [{labelSpacing: 'default' as const, label: 'Adjacent'}];`;
|
|
74
|
+
const output = await applyTransform(input);
|
|
75
|
+
expect(output).toContain("'hug' as const");
|
|
76
|
+
expect(output).not.toContain("'default'");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not rename object labelSpacing prop when Switch is not imported', async () => {
|
|
80
|
+
const input = `const cfg = {labelSpacing: 'default'};`;
|
|
81
|
+
const output = await applyTransform(input);
|
|
82
|
+
expect(output).toBe(input);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('renames Storybook argTypes options in files importing Switch', async () => {
|
|
86
|
+
const input = `import {Switch} from '@astryxdesign/core/Switch';
|
|
87
|
+
const meta = {argTypes: {labelSpacing: {control: 'select', options: ['default', 'spread']}}};`;
|
|
88
|
+
const output = await applyTransform(input);
|
|
89
|
+
expect(output).toContain("'hug'");
|
|
90
|
+
expect(output).toContain("'spread'");
|
|
91
|
+
expect(output).not.toContain("'default'");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file v0.1.5 transform manifest
|
|
5
|
+
*
|
|
6
|
+
* Lists all codemods for the v0.1.5 release in the order they should run.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import renameSwitchLabelSpacingDefaultToHug, {
|
|
10
|
+
meta as renameSwitchLabelSpacingDefaultToHugMeta,
|
|
11
|
+
} from './rename-switch-label-spacing-default-to-hug.mjs';
|
|
12
|
+
|
|
13
|
+
export default [
|
|
14
|
+
{
|
|
15
|
+
name: 'rename-switch-label-spacing-default-to-hug',
|
|
16
|
+
transform: renameSwitchLabelSpacingDefaultToHug,
|
|
17
|
+
meta: renameSwitchLabelSpacingDefaultToHugMeta,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Codemod: Rename Switch labelSpacing "default" to "hug"
|
|
5
|
+
* @see https://github.com/facebook/astryx/issues/2889
|
|
6
|
+
*
|
|
7
|
+
* The `labelSpacing="default"` value is renamed to `labelSpacing="hug"` so
|
|
8
|
+
* the name describes the behavior (label and switch hug each other), matching
|
|
9
|
+
* the behavioral register of the sibling `spread` value and the existing
|
|
10
|
+
* `hug`/`fill` layout vocabulary on TabList and SegmentedControl. The old
|
|
11
|
+
* `default` value keeps working as a deprecated alias, so this codemod is a
|
|
12
|
+
* cleanup, not a build fix.
|
|
13
|
+
*
|
|
14
|
+
* Transforms (scoped to Switch):
|
|
15
|
+
* - <Switch labelSpacing="default" /> → <Switch labelSpacing="hug" />
|
|
16
|
+
* - <Switch labelSpacing={'default'} /> → <Switch labelSpacing={'hug'} />
|
|
17
|
+
* - <Switch labelSpacing={cond ? 'default' : x}> → <Switch labelSpacing={cond ? 'hug' : x}>
|
|
18
|
+
* - Storybook argTypes: labelSpacing: { options: [..., 'default', ...] }
|
|
19
|
+
* → replaces 'default' with 'hug' (files importing Switch)
|
|
20
|
+
* - Object properties: { labelSpacing: 'default' } / { labelSpacing: 'default' as const }
|
|
21
|
+
* → { labelSpacing: 'hug' } (files importing Switch)
|
|
22
|
+
*
|
|
23
|
+
* The transform deliberately does NOT touch bare `'default'` strings that are
|
|
24
|
+
* not a `labelSpacing` value (an extremely common string otherwise), so it is
|
|
25
|
+
* safe to run across an entire codebase.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
export const meta = {
|
|
29
|
+
title: 'Rename Switch labelSpacing "default" to "hug"',
|
|
30
|
+
description:
|
|
31
|
+
'Renames the `labelSpacing="default"` value to `labelSpacing="hug"` on ' +
|
|
32
|
+
'Switch. The old value keeps working as a deprecated alias. Also updates ' +
|
|
33
|
+
'Storybook labelSpacing argTypes options and object-literal `labelSpacing` ' +
|
|
34
|
+
'props in files that import Switch.',
|
|
35
|
+
pr: '#2889',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const OLD_VALUE = 'default';
|
|
39
|
+
const NEW_VALUE = 'hug';
|
|
40
|
+
|
|
41
|
+
/** Prop whose value is being renamed. */
|
|
42
|
+
const TARGET_PROP = 'labelSpacing';
|
|
43
|
+
|
|
44
|
+
/** Components whose `labelSpacing` prop accepts a SwitchLabelSpacing value. */
|
|
45
|
+
const TARGET_COMPONENTS = new Set(['Switch']);
|
|
46
|
+
|
|
47
|
+
export default function transformer(file, api) {
|
|
48
|
+
const j = api.jscodeshift;
|
|
49
|
+
const root = j(file.source);
|
|
50
|
+
let hasChanges = false;
|
|
51
|
+
|
|
52
|
+
/** Rewrite a string-literal node when it equals the old value. */
|
|
53
|
+
function renameStringLiteral(node) {
|
|
54
|
+
if (!node) return false;
|
|
55
|
+
if (
|
|
56
|
+
(node.type === 'StringLiteral' || node.type === 'Literal') &&
|
|
57
|
+
node.value === OLD_VALUE
|
|
58
|
+
) {
|
|
59
|
+
node.value = NEW_VALUE;
|
|
60
|
+
if (node.raw) node.raw = `'${NEW_VALUE}'`;
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Recursively rewrite the old value inside ternary/logical expressions. */
|
|
67
|
+
function renameInExpression(node) {
|
|
68
|
+
if (!node) return false;
|
|
69
|
+
let changed = false;
|
|
70
|
+
if (node.type === 'StringLiteral' || node.type === 'Literal') {
|
|
71
|
+
changed = renameStringLiteral(node) || changed;
|
|
72
|
+
} else if (node.type === 'ConditionalExpression') {
|
|
73
|
+
changed = renameInExpression(node.consequent) || changed;
|
|
74
|
+
changed = renameInExpression(node.alternate) || changed;
|
|
75
|
+
} else if (node.type === 'LogicalExpression') {
|
|
76
|
+
changed = renameInExpression(node.left) || changed;
|
|
77
|
+
changed = renameInExpression(node.right) || changed;
|
|
78
|
+
} else if (node.type === 'TSAsExpression') {
|
|
79
|
+
// `'default' as const` → rewrite the inner expression
|
|
80
|
+
changed = renameInExpression(node.expression) || changed;
|
|
81
|
+
}
|
|
82
|
+
return changed;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// --- 1. JSX attribute: labelSpacing="default" / labelSpacing={'default'} on Switch ---
|
|
86
|
+
root.find(j.JSXOpeningElement).forEach(path => {
|
|
87
|
+
const name = path.node.name;
|
|
88
|
+
const componentName = name.type === 'JSXIdentifier' ? name.name : null;
|
|
89
|
+
if (!componentName || !TARGET_COMPONENTS.has(componentName)) return;
|
|
90
|
+
|
|
91
|
+
path.node.attributes.forEach(attr => {
|
|
92
|
+
if (attr.type !== 'JSXAttribute') return;
|
|
93
|
+
if (!attr.name || attr.name.name !== TARGET_PROP) return;
|
|
94
|
+
|
|
95
|
+
const value = attr.value;
|
|
96
|
+
if (!value) return;
|
|
97
|
+
|
|
98
|
+
if (value.type === 'StringLiteral' || value.type === 'Literal') {
|
|
99
|
+
if (renameStringLiteral(value)) hasChanges = true;
|
|
100
|
+
} else if (value.type === 'JSXExpressionContainer') {
|
|
101
|
+
if (renameInExpression(value.expression)) hasChanges = true;
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Object-property / argTypes transforms only run in files that use a target
|
|
107
|
+
// component — keeps unrelated `{ labelSpacing: 'default' }` strings safe.
|
|
108
|
+
const importsTarget =
|
|
109
|
+
root
|
|
110
|
+
.find(j.ImportSpecifier)
|
|
111
|
+
.filter(p => TARGET_COMPONENTS.has(p.node.imported?.name))
|
|
112
|
+
.size() > 0;
|
|
113
|
+
|
|
114
|
+
if (importsTarget) {
|
|
115
|
+
const PropertyType = j.ObjectProperty ?? j.Property;
|
|
116
|
+
|
|
117
|
+
// --- 2. Object property: { labelSpacing: 'default' } / 'default' as const ---
|
|
118
|
+
root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
|
|
119
|
+
if (renameInExpression(path.node.value)) hasChanges = true;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// --- 3. Storybook argTypes: labelSpacing: { options: [..., 'default', ...] } ---
|
|
123
|
+
root.find(PropertyType, {key: {name: TARGET_PROP}}).forEach(path => {
|
|
124
|
+
const value = path.node.value;
|
|
125
|
+
if (!value || value.type !== 'ObjectExpression') return;
|
|
126
|
+
|
|
127
|
+
const optionsProp = value.properties.find(
|
|
128
|
+
p => p.key && (p.key.name === 'options' || p.key.value === 'options'),
|
|
129
|
+
);
|
|
130
|
+
if (optionsProp && optionsProp.value.type === 'ArrayExpression') {
|
|
131
|
+
optionsProp.value.elements.forEach(el => {
|
|
132
|
+
if (renameStringLiteral(el)) hasChanges = true;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (!hasChanges) return undefined;
|
|
139
|
+
return root.toSource({quote: 'single'});
|
|
140
|
+
}
|
|
@@ -939,15 +939,9 @@ export function registerTheme(program) {
|
|
|
939
939
|
if (component.length > 0) {
|
|
940
940
|
const componentInner = component.join('\n\n');
|
|
941
941
|
const componentScope = `@scope (${scopeSelector}) to (${scopeTo}) {\n${componentInner}\n}`;
|
|
942
|
-
//
|
|
943
|
-
// bare `:root { color-scheme: light dark }` outranks reset.css's
|
|
944
|
-
// `html[data-theme]` mapping (astryx-theme layer comes after reset),
|
|
945
|
-
// silently defeating `<Theme mode>` forcing on <html>. Mirror the
|
|
946
|
-
// attribute-keyed mapping so the declaration follows the active mode.
|
|
942
|
+
// #3658: also emit attribute-specific rules so <Theme mode> can override color-scheme
|
|
947
943
|
const colorSchemeDecl = componentScope.includes('light-dark(')
|
|
948
|
-
? ' :root { color-scheme: light dark; }\n'
|
|
949
|
-
' html[data-theme="light"] { color-scheme: light; }\n' +
|
|
950
|
-
' html[data-theme="dark"] { color-scheme: dark; }\n\n'
|
|
944
|
+
? ' :root { color-scheme: light dark; }\n html[data-theme="light"] { color-scheme: light; }\n html[data-theme="dark"] { color-scheme: dark; }\n\n'
|
|
951
945
|
: '';
|
|
952
946
|
cssParts.push(
|
|
953
947
|
`@layer astryx-theme {\n${colorSchemeDecl}${componentScope}\n}`,
|
|
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioCircleImage() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={300}>
|
|
11
|
-
<AspectRatio ratio={1} shape="ellipse">
|
|
11
|
+
<AspectRatio ratio={1} shape="ellipse" fit="cover">
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
|
|
14
14
|
alt="Circular image"
|
|
15
|
-
style={{
|
|
16
|
-
width: '100%',
|
|
17
|
-
height: '100%',
|
|
18
|
-
objectFit: 'cover',
|
|
19
|
-
}}
|
|
20
15
|
/>
|
|
21
16
|
</AspectRatio>
|
|
22
17
|
</Center>
|
|
@@ -25,16 +25,11 @@ export default function AspectRatioImageGallery() {
|
|
|
25
25
|
<Center width={600}>
|
|
26
26
|
<Grid columns={3} gap={4} width="100%">
|
|
27
27
|
{images.map(({id, alt}) => (
|
|
28
|
-
<AspectRatio key={id} ratio={4 / 3}>
|
|
28
|
+
<AspectRatio key={id} ratio={4 / 3} fit="cover">
|
|
29
29
|
<img
|
|
30
30
|
src="https://lookaside.facebook.com/assets/astryx/illustrative-horizontal-1.png"
|
|
31
31
|
alt={alt}
|
|
32
|
-
style={{
|
|
33
|
-
width: '100%',
|
|
34
|
-
height: '100%',
|
|
35
|
-
objectFit: 'cover',
|
|
36
|
-
borderRadius: 8,
|
|
37
|
-
}}
|
|
32
|
+
style={{borderRadius: 8}}
|
|
38
33
|
/>
|
|
39
34
|
</AspectRatio>
|
|
40
35
|
))}
|
|
@@ -34,16 +34,13 @@ export default function AspectRatioShowcase() {
|
|
|
34
34
|
<VStack key={label} gap={2} hAlign="center">
|
|
35
35
|
<AspectRatio
|
|
36
36
|
ratio={ratio}
|
|
37
|
+
fit="cover"
|
|
37
38
|
style={{
|
|
38
39
|
height: 120,
|
|
39
40
|
width: 'auto',
|
|
40
41
|
borderRadius: 'var(--radius-container)',
|
|
41
42
|
}}>
|
|
42
|
-
<img
|
|
43
|
-
src={src}
|
|
44
|
-
alt={alt}
|
|
45
|
-
style={{width: '100%', height: '100%', objectFit: 'cover'}}
|
|
46
|
-
/>
|
|
43
|
+
<img src={src} alt={alt} />
|
|
47
44
|
</AspectRatio>
|
|
48
45
|
<Text type="supporting" color="secondary">
|
|
49
46
|
{label}
|
|
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioSquareImage() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={300}>
|
|
11
|
-
<AspectRatio ratio={1}>
|
|
11
|
+
<AspectRatio ratio={1} fit="cover">
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-home-square-1.png"
|
|
14
14
|
alt="1:1 square"
|
|
15
|
-
style={{
|
|
16
|
-
width: '100%',
|
|
17
|
-
height: '100%',
|
|
18
|
-
objectFit: 'cover',
|
|
19
|
-
}}
|
|
20
15
|
/>
|
|
21
16
|
</AspectRatio>
|
|
22
17
|
</Center>
|
|
@@ -8,15 +8,10 @@ import {Center} from '@astryxdesign/core/Center';
|
|
|
8
8
|
export default function AspectRatioWidescreen() {
|
|
9
9
|
return (
|
|
10
10
|
<Center width={600}>
|
|
11
|
-
<AspectRatio ratio={16 / 9}>
|
|
11
|
+
<AspectRatio ratio={16 / 9} fit="cover">
|
|
12
12
|
<img
|
|
13
13
|
src="https://lookaside.facebook.com/assets/astryx/light-scene-horizontal-1.png"
|
|
14
14
|
alt="16:9 widescreen"
|
|
15
|
-
style={{
|
|
16
|
-
width: '100%',
|
|
17
|
-
height: '100%',
|
|
18
|
-
objectFit: 'cover',
|
|
19
|
-
}}
|
|
20
15
|
/>
|
|
21
16
|
</AspectRatio>
|
|
22
17
|
</Center>
|
|
@@ -6,7 +6,7 @@ export const doc = {
|
|
|
6
6
|
exampleFor: 'Collapsible',
|
|
7
7
|
name: 'Collapsible — Divided Accordion',
|
|
8
8
|
displayName: 'Collapsible — Divided Accordion',
|
|
9
|
-
description: 'FAQ-style accordion using the
|
|
9
|
+
description: 'FAQ-style accordion using the hasDividers prop on CollapsibleGroup: built-in row hairlines and density padding with zero custom CSS. Use for FAQs, settings lists, and nav sections.',
|
|
10
10
|
isReady: true,
|
|
11
11
|
aspectRatio: 4 / 3,
|
|
12
12
|
componentsUsed: ['Collapsible', 'CollapsibleGroup', 'Text'],
|
|
@@ -8,7 +8,7 @@ import {Text} from '@astryxdesign/core/Text';
|
|
|
8
8
|
export default function CollapsibleDividedAccordion() {
|
|
9
9
|
return (
|
|
10
10
|
<div style={{width: '100%', maxWidth: 400}}>
|
|
11
|
-
<CollapsibleGroup type="single"
|
|
11
|
+
<CollapsibleGroup type="single" hasDividers defaultValue="reset">
|
|
12
12
|
<Collapsible trigger="How do I reset my password?" value="reset">
|
|
13
13
|
<Text type="body">
|
|
14
14
|
Go to Settings → Security → Change Password. You'll receive a
|
|
@@ -439,8 +439,11 @@ export default function KanbanBoardTemplate() {
|
|
|
439
439
|
let cb = columnRefCbs.current.get(id);
|
|
440
440
|
if (!cb) {
|
|
441
441
|
cb = el => {
|
|
442
|
-
if (el) {
|
|
443
|
-
|
|
442
|
+
if (el) {
|
|
443
|
+
columnEls.current.set(id, el);
|
|
444
|
+
} else {
|
|
445
|
+
columnEls.current.delete(id);
|
|
446
|
+
}
|
|
444
447
|
};
|
|
445
448
|
columnRefCbs.current.set(id, cb);
|
|
446
449
|
}
|
|
@@ -451,8 +454,11 @@ export default function KanbanBoardTemplate() {
|
|
|
451
454
|
let cb = cardRefCbs.current.get(id);
|
|
452
455
|
if (!cb) {
|
|
453
456
|
cb = el => {
|
|
454
|
-
if (el) {
|
|
455
|
-
|
|
457
|
+
if (el) {
|
|
458
|
+
cardEls.current.set(id, el);
|
|
459
|
+
} else {
|
|
460
|
+
cardEls.current.delete(id);
|
|
461
|
+
}
|
|
456
462
|
};
|
|
457
463
|
cardRefCbs.current.set(id, cb);
|
|
458
464
|
}
|
|
@@ -487,7 +493,9 @@ export default function KanbanBoardTemplate() {
|
|
|
487
493
|
): DropTarget | null => {
|
|
488
494
|
for (const [colId, el] of Array.from(columnEls.current.entries())) {
|
|
489
495
|
const r = el.getBoundingClientRect();
|
|
490
|
-
if (px < r.left || px > r.right || py < r.top || py > r.bottom) {
|
|
496
|
+
if (px < r.left || px > r.right || py < r.top || py > r.bottom) {
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
491
499
|
|
|
492
500
|
const ids = itemsByColumn[colId]
|
|
493
501
|
.filter(it => it.id !== draggedId)
|
|
@@ -496,7 +504,9 @@ export default function KanbanBoardTemplate() {
|
|
|
496
504
|
let index = ids.length;
|
|
497
505
|
for (let i = 0; i < ids.length; i++) {
|
|
498
506
|
const cardEl = cardEls.current.get(ids[i]);
|
|
499
|
-
if (!cardEl) {
|
|
507
|
+
if (!cardEl) {
|
|
508
|
+
continue;
|
|
509
|
+
}
|
|
500
510
|
const cr = cardEl.getBoundingClientRect();
|
|
501
511
|
if (py < cr.top + cr.height / 2) {
|
|
502
512
|
index = i;
|
|
@@ -513,21 +523,27 @@ export default function KanbanBoardTemplate() {
|
|
|
513
523
|
const commitDrag = (id: string, target: DropTarget) => {
|
|
514
524
|
setItems(prev => {
|
|
515
525
|
const moved = prev.find(it => it.id === id);
|
|
516
|
-
if (!moved) {
|
|
526
|
+
if (!moved) {
|
|
527
|
+
return prev;
|
|
528
|
+
}
|
|
517
529
|
|
|
518
530
|
const rest = prev.filter(it => it.id !== id);
|
|
519
531
|
const updated: WorkItem = {...moved, column: target.column};
|
|
520
532
|
const colItems = rest.filter(it => it.column === target.column);
|
|
521
533
|
const anchor = colItems[target.index];
|
|
522
534
|
|
|
523
|
-
if (!anchor) {
|
|
535
|
+
if (!anchor) {
|
|
536
|
+
return [...rest, updated];
|
|
537
|
+
}
|
|
524
538
|
const at = rest.indexOf(anchor);
|
|
525
539
|
return [...rest.slice(0, at), updated, ...rest.slice(at)];
|
|
526
540
|
});
|
|
527
541
|
};
|
|
528
542
|
|
|
529
543
|
const onCardPointerDown = (e: ReactPointerEvent, id: string) => {
|
|
530
|
-
if (e.button !== 0) {
|
|
544
|
+
if (e.button !== 0) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
531
547
|
// Let the card's own controls (the actions menu) handle the press.
|
|
532
548
|
if (
|
|
533
549
|
(e.target as HTMLElement).closest(
|
|
@@ -538,7 +554,9 @@ export default function KanbanBoardTemplate() {
|
|
|
538
554
|
}
|
|
539
555
|
|
|
540
556
|
const el = cardEls.current.get(id);
|
|
541
|
-
if (!el) {
|
|
557
|
+
if (!el) {
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
542
560
|
|
|
543
561
|
const rect = el.getBoundingClientRect();
|
|
544
562
|
const startX = e.clientX;
|
|
@@ -574,7 +592,9 @@ export default function KanbanBoardTemplate() {
|
|
|
574
592
|
|
|
575
593
|
const onUp = () => {
|
|
576
594
|
teardownRef.current?.();
|
|
577
|
-
if (started && target) {
|
|
595
|
+
if (started && target) {
|
|
596
|
+
commitDrag(id, target);
|
|
597
|
+
}
|
|
578
598
|
setDrag(null);
|
|
579
599
|
};
|
|
580
600
|
|
|
@@ -594,7 +614,9 @@ export default function KanbanBoardTemplate() {
|
|
|
594
614
|
|
|
595
615
|
// Suppress selection while dragging and detach listeners on unmount.
|
|
596
616
|
useEffect(() => {
|
|
597
|
-
if (!isDragging) {
|
|
617
|
+
if (!isDragging) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
598
620
|
const previous = document.body.style.userSelect;
|
|
599
621
|
document.body.style.userSelect = 'none';
|
|
600
622
|
return () => {
|
|
@@ -612,7 +634,9 @@ export default function KanbanBoardTemplate() {
|
|
|
612
634
|
const ghostTarget =
|
|
613
635
|
drag && drag.target && drag.target.column === colId ? drag : null;
|
|
614
636
|
|
|
615
|
-
if (visible.length === 0 && !ghostTarget) {
|
|
637
|
+
if (visible.length === 0 && !ghostTarget) {
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
616
640
|
|
|
617
641
|
const nodes: ReactNode[] = visible.map(it => (
|
|
618
642
|
<BoardCard
|