@astryxdesign/cli 0.4.5-canary.ffd05c4 → 0.4.6-canary.cea9ecd
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 +26 -0
- package/api/component/_adapter.d.mts +22 -5
- package/api/component/_adapter.mjs +51 -3
- package/api/docs/_adapter.d.mts +37 -8
- package/api/docs/_adapter.mjs +70 -41
- package/api/docs/detail/detail.d.mts +2 -0
- package/api/docs/detail/detail.mjs +12 -10
- package/api/docs/detail/section/section.d.mts +2 -0
- package/api/docs/detail/section/section.mjs +1 -0
- package/api/docs/docs.d.mts +2 -0
- package/api/docs/docs.doc.mjs +11 -2
- package/api/docs/docs.mjs +2 -1
- package/api/docs/docs.type.d.mts +15 -0
- package/api/docs/docs.type.mjs +6 -0
- package/api/docs/integrationDocs.test.mjs +208 -0
- package/api/docs/list/list.d.mts +5 -1
- package/api/docs/list/list.mjs +30 -14
- package/api/init/run/run.mjs +9 -4
- package/api/integration/validate-integration.mjs +2 -1
- package/api/integration/validate-integration.type.d.mts +1 -0
- package/api/integration/validate-integration.type.mjs +1 -0
- package/api/search/search.mjs +26 -15
- package/api/upgrade/_adapter.d.mts +2 -2
- package/api/upgrade/_adapter.mjs +7 -3
- package/api/upgrade/run/run.mjs +1 -1
- package/assets/codemods/transforms/v0.1.0/__tests__/drop-xds-prefix-imports.test.mjs +154 -0
- package/assets/codemods/transforms/v0.1.0/drop-xds-prefix-imports.mjs +111 -9
- package/assets/docs/cli-integrations.doc.mjs +41 -3
- package/assets/docs/theme.doc.mjs +3 -3
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupInteractive.doc.mjs +15 -0
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupInteractive.tsx +47 -0
- package/assets/templates/blocks/components/AvatarGroup/AvatarGroupShowcase.doc.mjs +1 -1
- package/assets/templates/blocks/components/Breadcrumbs/BreadcrumbsCustomSeparator.tsx +18 -5
- package/assets/templates/blocks/components/Selector/SelectorOptionDescriptions.doc.mjs +1 -1
- package/assets/templates/pages/dashboard-cohort-funnel/page.tsx +914 -0
- package/assets/templates/pages/dashboard-cohort-funnel/template.doc.mjs +12 -0
- package/assets/templates/pages/dashboard-data/page.tsx +894 -0
- package/assets/templates/pages/dashboard-data/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-executive-summary/page.tsx +874 -0
- package/assets/templates/pages/dashboard-executive-summary/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-project-status/page.tsx +1139 -0
- package/assets/templates/pages/dashboard-project-status/template.doc.mjs +13 -0
- package/assets/templates/pages/dashboard-service-monitoring/page.tsx +1590 -0
- package/assets/templates/pages/dashboard-service-monitoring/template.doc.mjs +12 -0
- package/authoring/doctypes/_schema.d.mts +2 -0
- package/authoring/doctypes/_schema.mjs +5 -0
- package/authoring/doctypes/reference/reference.doc.mjs +14 -0
- package/authoring/doctypes/reference/type.ts +12 -0
- package/authoring/doctypes/template/type.ts +2 -0
- package/authoring/integration/integration.doc.mjs +10 -1
- package/authoring/integration/parse.d.mts +1 -0
- package/authoring/integration/parse.mjs +1 -0
- package/authoring/integration/parse.test.mjs +1 -0
- package/authoring/integration/type.ts +5 -0
- package/clients/cli/commands/component-ownership.test.mjs +63 -3
- package/clients/cli/commands/theme-build.doc.mjs +1 -1
- package/foundation/agent-docs/agent-docs.d.mts +19 -3
- package/foundation/agent-docs/agent-docs.mjs +30 -11
- package/foundation/agent-docs/agent-docs.test.mjs +38 -0
- package/foundation/config/project.d.mts +16 -0
- package/foundation/config/project.mjs +65 -4
- package/foundation/config/project.test.mjs +66 -0
- package/foundation/discovery/docs-discovery.d.mts +185 -0
- package/foundation/discovery/docs-discovery.mjs +544 -0
- package/foundation/discovery/docs-discovery.test.mjs +341 -0
- package/foundation/integrations/integrations.d.mts +8 -6
- package/foundation/integrations/integrations.mjs +6 -4
- package/foundation/integrations/validate-contributions.mjs +30 -2
- package/package.json +9 -9
|
@@ -346,18 +346,27 @@ export default function transformer(file, api) {
|
|
|
346
346
|
}
|
|
347
347
|
// Don't double-rewrite the import specifier we already handled.
|
|
348
348
|
if (parent.type === 'ImportSpecifier') return;
|
|
349
|
+
// JSX element tag names are handled by a source-string splice after
|
|
350
|
+
// `toSource()` (see renameJsxElementNamesInSource). Mutating them here
|
|
351
|
+
// would dirty the enclosing JSXElement and make recast collapse whitespace
|
|
352
|
+
// around adjacent `{expressions}`. Under the tsx parser a JSX tag-name
|
|
353
|
+
// JSXIdentifier is a subtype of Identifier, so it surfaces in this
|
|
354
|
+
// `find(j.Identifier)` pass and must be explicitly skipped.
|
|
355
|
+
if (isJsxElementNameIdentifier(path)) return;
|
|
349
356
|
path.node.name = newName;
|
|
350
357
|
hasChanges = true;
|
|
351
358
|
});
|
|
352
359
|
|
|
353
|
-
// JSX element names
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
360
|
+
// JSX element names (`<XDSButton>` -> `<Button>`) are intentionally NOT
|
|
361
|
+
// renamed via AST mutation here. Mutating a JSXIdentifier in place dirties the
|
|
362
|
+
// enclosing JSXElement; when that element is the argument of a parenthesized
|
|
363
|
+
// `return ( <JSX/> )` (i.e. every React component body), recast declines a
|
|
364
|
+
// surgical child reprint and generically re-prints the whole return argument.
|
|
365
|
+
// Its generic JSXElement printer drops significant whitespace from JSXText
|
|
366
|
+
// that sits next to a `{expression}` -- so `hello {name} world` collapses to
|
|
367
|
+
// `hello {name}world`. Instead we splice the element-name renames directly
|
|
368
|
+
// into the emitted source string (below), which never re-prints the subtree.
|
|
369
|
+
// See renameJsxElementNamesInSource for the mechanism.
|
|
361
370
|
|
|
362
371
|
// 5. Rename TS type references in generic type-argument positions.
|
|
363
372
|
//
|
|
@@ -413,5 +422,98 @@ export default function transformer(file, api) {
|
|
|
413
422
|
};
|
|
414
423
|
renameTypeReferences(root.get().node);
|
|
415
424
|
|
|
416
|
-
|
|
425
|
+
if (!hasChanges) return undefined;
|
|
426
|
+
|
|
427
|
+
// Emit the AST-based renames, then splice the JSX element-name renames onto
|
|
428
|
+
// the emitted string (see the note in the JSX section above for why element
|
|
429
|
+
// names cannot be renamed through the AST without collapsing whitespace).
|
|
430
|
+
const emitted = root.toSource();
|
|
431
|
+
return renameJsxElementNamesInSource(j, emitted, localRenames);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Rename JSX element tag names (`<XDSButton>` / `</XDSButton>`) by splicing the
|
|
436
|
+
* new names directly into the already-emitted source, so recast never re-prints
|
|
437
|
+
* the surrounding JSX subtree (which would strip whitespace next to
|
|
438
|
+
* `{expressions}`).
|
|
439
|
+
*
|
|
440
|
+
* Spans are computed by re-parsing `source` itself, so the offsets are always
|
|
441
|
+
* valid against the string we splice into -- no dependency on the original
|
|
442
|
+
* pre-transform offsets. Splices are applied right-to-left (descending start
|
|
443
|
+
* offset) so earlier offsets stay valid as we go.
|
|
444
|
+
*
|
|
445
|
+
* Only the identifier that IS the element's tag name is renamed:
|
|
446
|
+
* - `<Foo attr />` -> the `Foo` opening-name only (not `attr`)
|
|
447
|
+
* - `<Ns.Foo />` / `<Foo.Bar>`-> the specific member-expression identifier that
|
|
448
|
+
* maps in `renames`; the other part of the member name is preserved
|
|
449
|
+
*
|
|
450
|
+
* @param {any} j jscodeshift instance (already bound to the tsx parser)
|
|
451
|
+
* @param {string} source emitted source to rewrite
|
|
452
|
+
* @param {Map<string, string>} renames localName -> newLocalName
|
|
453
|
+
* @returns {string}
|
|
454
|
+
*/
|
|
455
|
+
function renameJsxElementNamesInSource(j, source, renames) {
|
|
456
|
+
if (renames.size === 0) return source;
|
|
457
|
+
|
|
458
|
+
const root = j(source);
|
|
459
|
+
|
|
460
|
+
/** @type {Array<{start: number, end: number, name: string}>} */
|
|
461
|
+
const edits = [];
|
|
462
|
+
|
|
463
|
+
root.find(j.JSXIdentifier).forEach((/** @type {any} */ path) => {
|
|
464
|
+
const node = path.node;
|
|
465
|
+
const newName = renames.get(node.name);
|
|
466
|
+
if (!newName || newName === node.name) return;
|
|
467
|
+
|
|
468
|
+
// The identifier must BE an element tag name -- the `.name` of a
|
|
469
|
+
// JSXOpeningElement/JSXClosingElement, or the matching part of a
|
|
470
|
+
// JSXMemberExpression tag name. Attribute names, attribute values, and the
|
|
471
|
+
// non-matching half of a member-expression name must be left alone.
|
|
472
|
+
if (!isJsxElementNameIdentifier(path)) return;
|
|
473
|
+
|
|
474
|
+
if (typeof node.start !== 'number' || typeof node.end !== 'number') return;
|
|
475
|
+
edits.push({start: node.start, end: node.end, name: newName});
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
if (edits.length === 0) return source;
|
|
479
|
+
|
|
480
|
+
edits.sort((a, b) => b.start - a.start);
|
|
481
|
+
|
|
482
|
+
let out = source;
|
|
483
|
+
for (const {start, end, name} of edits) {
|
|
484
|
+
out = out.slice(0, start) + name + out.slice(end);
|
|
485
|
+
}
|
|
486
|
+
return out;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Is this JSXIdentifier path the tag name of its element (opening or closing),
|
|
491
|
+
* as opposed to an attribute name or a namespace/member-expression segment we
|
|
492
|
+
* should not rename? For member-expression tag names (`<Foo.Bar>`) we accept
|
|
493
|
+
* the identifier regardless of which segment it is -- the caller only reaches
|
|
494
|
+
* here for identifiers whose name is in the rename map, and each renamed
|
|
495
|
+
* identifier maps independently, so `<Foo.Bar>` renames only the segment(s)
|
|
496
|
+
* that actually map.
|
|
497
|
+
*
|
|
498
|
+
* @param {any} path jscodeshift path to a JSXIdentifier
|
|
499
|
+
* @returns {boolean}
|
|
500
|
+
*/
|
|
501
|
+
function isJsxElementNameIdentifier(path) {
|
|
502
|
+
const parent = path.parent && path.parent.node;
|
|
503
|
+
if (!parent) return false;
|
|
504
|
+
|
|
505
|
+
if (
|
|
506
|
+
parent.type === 'JSXOpeningElement' ||
|
|
507
|
+
parent.type === 'JSXClosingElement'
|
|
508
|
+
) {
|
|
509
|
+
return parent.name === path.node;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// `<Foo.Bar>` / `<A.B.C>`: the tag name is a JSXMemberExpression whose
|
|
513
|
+
// segments are JSXIdentifiers. Rename the specific matching segment only.
|
|
514
|
+
if (parent.type === 'JSXMemberExpression') {
|
|
515
|
+
return parent.object === path.node || parent.property === path.node;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return false;
|
|
417
519
|
}
|
|
@@ -16,7 +16,7 @@ export const docs = {
|
|
|
16
16
|
content: [
|
|
17
17
|
{
|
|
18
18
|
type: 'prose',
|
|
19
|
-
text: 'An integration is an npm package that contributes components, templates, and/or upgrade codemods to a consumer\'s design-system workflow. Consumers install the package and add it to their `astryx.config`; from then on the integration\'s contributions show up alongside core\'s in the same CLI commands.',
|
|
19
|
+
text: 'An integration is an npm package that contributes components, templates, doc topics, and/or upgrade codemods to a consumer\'s design-system workflow. Consumers install the package and add it to their `astryx.config`; from then on the integration\'s contributions show up alongside core\'s in the same CLI commands.',
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
type: 'prose',
|
|
@@ -48,12 +48,12 @@ export const docs = {
|
|
|
48
48
|
content: [
|
|
49
49
|
{
|
|
50
50
|
type: 'prose',
|
|
51
|
-
text: 'To register your package as an integration, add an `astryx.integration.{ts,mjs,js}` file as a sibling of your `package.json`. It tells the CLI where to find your components, templates, and codemods. Identity (name, version) comes from your `package.json`, not this file.',
|
|
51
|
+
text: 'To register your package as an integration, add an `astryx.integration.{ts,mjs,js}` file as a sibling of your `package.json`. It tells the CLI where to find your components, templates, doc topics, and codemods. Identity (name, version) comes from your `package.json`, not this file.',
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
type: 'code',
|
|
55
55
|
lang: 'typescript',
|
|
56
|
-
code: "// astryx.integration.ts\nexport default {\n components: './components',\n templates: './templates',\n codemods: './codemods',\n issuesUrl: 'https://github.com/acme/widgets/issues',\n};",
|
|
56
|
+
code: "// astryx.integration.ts\nexport default {\n components: './components',\n templates: './templates',\n codemods: './codemods',\n docs: './docs',\n issuesUrl: 'https://github.com/acme/widgets/issues',\n};",
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
type: 'prose',
|
|
@@ -109,6 +109,44 @@ export const docs = {
|
|
|
109
109
|
},
|
|
110
110
|
],
|
|
111
111
|
},
|
|
112
|
+
{
|
|
113
|
+
title: 'Docs',
|
|
114
|
+
category: 'guide',
|
|
115
|
+
content: [
|
|
116
|
+
{
|
|
117
|
+
type: 'prose',
|
|
118
|
+
text: "Point the integration file's `docs` field at a directory of reference docs and every `{topic}.doc.{ts,mjs,js}` under it becomes a topic the CLI serves — `astryx docs` lists it, `astryx docs <topic>` prints it, `astryx search` indexes it, and `astryx init` names it in the agent block. A topic is a plain object stamped `type: 'generic'`, the same shape core's own topics use.",
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'code',
|
|
122
|
+
lang: 'typescript',
|
|
123
|
+
code: "// docs/deploying.doc.ts\nexport default {\n type: 'generic',\n name: 'deploying',\n title: 'Deploying',\n description: 'Ship an app built with Acme widgets.',\n category: 'guide',\n sections: [\n {title: 'Overview', content: [{type: 'prose', text: '...'}]},\n ],\n};",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
type: 'prose',
|
|
127
|
+
text: "A topic can also speak about one that already exists. `replaces: 'x'` takes over topic x — core's, or another integration's — so a package whose consumers install it differently can serve its own Getting Started instead of the built-in one. Give the replacement a different `name` and the old name keeps resolving to it, so a link or an agent that learned the old topic still lands in the right place.",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
type: 'code',
|
|
131
|
+
lang: 'typescript',
|
|
132
|
+
code: "export default {\n type: 'generic',\n name: 'getting-started',\n replaces: 'getting-started',\n title: 'Getting started',\n description: 'Install Acme widgets and use your first component.',\n sections: [/* ... */],\n};",
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
type: 'prose',
|
|
136
|
+
text: "`extends: 'x'` merges onto a topic instead of owning it: a section whose title matches one in the base replaces that section, and a section the base does not have is appended. Reach for it to correct or add to a topic you do not want to fork — a fork of someone else's guide stops receiving their fixes the day you write it.",
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'list',
|
|
140
|
+
style: 'unordered',
|
|
141
|
+
items: [
|
|
142
|
+
'A topic name is a CLI argument and a docsite path, so it may hold only letters, digits, `_` and `-`.',
|
|
143
|
+
"A name that collides with an existing topic and declares neither `replaces` nor `extends` is an error, not a silent override — the CLI will not guess which one you meant.",
|
|
144
|
+
'`replaces` and `extends` are exclusive: a topic either takes another\'s place or merges onto it.',
|
|
145
|
+
'Two integrations replacing one topic is a warning, and the one configured later in `astryx.config` wins.',
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
],
|
|
149
|
+
},
|
|
112
150
|
{
|
|
113
151
|
title: 'Codemods',
|
|
114
152
|
category: 'guide',
|
|
@@ -152,7 +152,7 @@ function App() {
|
|
|
152
152
|
},
|
|
153
153
|
{
|
|
154
154
|
type: 'prose',
|
|
155
|
-
text: 'For an annotated map of the whole surface
|
|
155
|
+
text: 'For an annotated map of the whole surface (every defineTheme field, the token families, and the component override syntax, each with the CLI command that prints its reference), run `astryx theme template`. It writes `theme.template.ts` into your project to read and copy from (`astryx init --features theme` writes it as part of project setup).',
|
|
156
156
|
},
|
|
157
157
|
],
|
|
158
158
|
},
|
|
@@ -261,7 +261,7 @@ const brandTheme = defineTheme({
|
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
type: 'prose',
|
|
264
|
-
text: 'Inheritance is resolved when the theme is defined, so an extended theme is flat: `astryx theme build` emits one self-contained stylesheet holding everything the child inherited, and the base theme\'s CSS does not need to be loaded next to it. A base that is not a theme
|
|
264
|
+
text: 'Inheritance is resolved when the theme is defined, so an extended theme is flat: `astryx theme build` emits one self-contained stylesheet holding everything the child inherited, and the base theme\'s CSS does not need to be loaded next to it. A base that is not a theme (most often an import that missed) is a build error rather than a theme that silently inherits nothing.',
|
|
265
265
|
},
|
|
266
266
|
],
|
|
267
267
|
},
|
|
@@ -425,7 +425,7 @@ import './themes/ocean.css';
|
|
|
425
425
|
},
|
|
426
426
|
{
|
|
427
427
|
type: 'prose',
|
|
428
|
-
text: 'The build also warns when the theme names font families it does not load (webfonts like Fraunces) and prints the `<link>`/`@font-face` to add
|
|
428
|
+
text: 'The build also warns when the theme names font families it does not load (webfonts like Fraunces) and prints the `<link>`/`@font-face` to add. The built CSS only sets font-family, so loading the font files stays the app\'s job. See `astryx docs typography` for the full recipe.',
|
|
429
429
|
},
|
|
430
430
|
],
|
|
431
431
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').TemplateDoc} */
|
|
4
|
+
export const doc = {
|
|
5
|
+
type: 'block',
|
|
6
|
+
exampleFor: 'AvatarGroup',
|
|
7
|
+
name: 'AvatarGroup — Interactive',
|
|
8
|
+
displayName: 'Avatar Group — Interactive',
|
|
9
|
+
description:
|
|
10
|
+
'A facepile whose avatars link to profiles, with an overflow indicator that opens the full member list. The whole group is one Tab stop; arrow keys move between the avatars and the overflow button.',
|
|
11
|
+
isReady: true,
|
|
12
|
+
isShowcase: false,
|
|
13
|
+
aspectRatio: 16 / 9,
|
|
14
|
+
componentsUsed: ['AvatarGroup', 'Avatar', 'Layout', 'Text'],
|
|
15
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
'use client';
|
|
3
|
+
|
|
4
|
+
import {AvatarGroup, AvatarGroupOverflow} from '@astryxdesign/core/AvatarGroup';
|
|
5
|
+
import {Avatar} from '@astryxdesign/core/Avatar';
|
|
6
|
+
import {Stack} from '@astryxdesign/core/Layout';
|
|
7
|
+
import {Text} from '@astryxdesign/core/Text';
|
|
8
|
+
|
|
9
|
+
const MEMBERS = [
|
|
10
|
+
{name: 'Alex Daniels', key: 'alex', href: '/team/alex'},
|
|
11
|
+
{name: 'Ann Smith', key: 'ann', href: '/team/ann'},
|
|
12
|
+
{name: 'Carol Davis', key: 'carol', href: '/team/carol'},
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const TOTAL_MEMBERS = 18;
|
|
16
|
+
|
|
17
|
+
export default function AvatarGroupInteractive() {
|
|
18
|
+
return (
|
|
19
|
+
<Stack direction="vertical" gap={8}>
|
|
20
|
+
<Stack direction="vertical" gap={3}>
|
|
21
|
+
<Text type="supporting" color="secondary">
|
|
22
|
+
Reviewers
|
|
23
|
+
</Text>
|
|
24
|
+
<AvatarGroup size="lg" aria-label="Reviewers">
|
|
25
|
+
{MEMBERS.map(m => (
|
|
26
|
+
<Avatar key={m.key} name={m.name} href={m.href} />
|
|
27
|
+
))}
|
|
28
|
+
<AvatarGroupOverflow
|
|
29
|
+
count={TOTAL_MEMBERS - MEMBERS.length}
|
|
30
|
+
onClick={() => {}}
|
|
31
|
+
/>
|
|
32
|
+
</AvatarGroup>
|
|
33
|
+
</Stack>
|
|
34
|
+
<Stack direction="vertical" gap={3}>
|
|
35
|
+
<Text type="supporting" color="secondary">
|
|
36
|
+
Compact, static
|
|
37
|
+
</Text>
|
|
38
|
+
<AvatarGroup size="sm" aria-label="Attendees">
|
|
39
|
+
{MEMBERS.map(m => (
|
|
40
|
+
<Avatar key={m.key} name={m.name} />
|
|
41
|
+
))}
|
|
42
|
+
<AvatarGroupOverflow count={TOTAL_MEMBERS - MEMBERS.length} />
|
|
43
|
+
</AvatarGroup>
|
|
44
|
+
</Stack>
|
|
45
|
+
</Stack>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -6,7 +6,7 @@ export const doc = {
|
|
|
6
6
|
name: 'AvatarGroup',
|
|
7
7
|
displayName: 'Avatar Group',
|
|
8
8
|
description:
|
|
9
|
-
'Overlapping avatar rows with
|
|
9
|
+
'Overlapping avatar rows with a sliced visible set and a server-side overflow count. Shows team members in a compact facepile layout.',
|
|
10
10
|
isReady: true,
|
|
11
11
|
isShowcase: true,
|
|
12
12
|
aspectRatio: 16 / 9,
|
|
@@ -2,25 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
'use client';
|
|
4
4
|
|
|
5
|
+
import * as stylex from '@stylexjs/stylex';
|
|
5
6
|
import {Breadcrumbs, BreadcrumbItem} from '@astryxdesign/core/Breadcrumbs';
|
|
6
7
|
import {Stack} from '@astryxdesign/core/Layout';
|
|
7
8
|
import {Text} from '@astryxdesign/core/Text';
|
|
9
|
+
import {rtlStyles} from '@astryxdesign/core/utils';
|
|
8
10
|
|
|
11
|
+
// Only a glyph the bidi algorithm does not already flip needs rtlStyles.mirror.
|
|
12
|
+
// U+203A has Unicode Bidi_Mirrored=Yes, so the browser mirrors it under RTL on
|
|
13
|
+
// its own and a second flip would point it back the wrong way; U+2192 has
|
|
14
|
+
// Bidi_Mirrored=No and stays pointing left-to-right unless we mirror it.
|
|
9
15
|
const SEPARATORS = [
|
|
10
|
-
{char: '›', label: 'Chevron'},
|
|
11
|
-
{char: '→', label: 'Arrow'},
|
|
12
|
-
{char: '·', label: 'Dot'},
|
|
16
|
+
{char: '›', label: 'Chevron', needsRtlMirror: false},
|
|
17
|
+
{char: '→', label: 'Arrow', needsRtlMirror: true},
|
|
18
|
+
{char: '·', label: 'Dot', needsRtlMirror: false},
|
|
13
19
|
];
|
|
14
20
|
|
|
15
21
|
export default function BreadcrumbsCustomSeparator() {
|
|
16
22
|
return (
|
|
17
23
|
<Stack direction="vertical" gap={4}>
|
|
18
|
-
{SEPARATORS.map(({char, label}) => (
|
|
24
|
+
{SEPARATORS.map(({char, label, needsRtlMirror}) => (
|
|
19
25
|
<Stack key={label} direction="vertical" gap={1}>
|
|
20
26
|
<Text type="supporting" color="secondary">
|
|
21
27
|
{label}
|
|
22
28
|
</Text>
|
|
23
|
-
<Breadcrumbs
|
|
29
|
+
<Breadcrumbs
|
|
30
|
+
separator={
|
|
31
|
+
needsRtlMirror ? (
|
|
32
|
+
<span {...stylex.props(rtlStyles.mirror)}>{char}</span>
|
|
33
|
+
) : (
|
|
34
|
+
char
|
|
35
|
+
)
|
|
36
|
+
}>
|
|
24
37
|
<BreadcrumbItem href="/">Home</BreadcrumbItem>
|
|
25
38
|
<BreadcrumbItem href="/docs">Docs</BreadcrumbItem>
|
|
26
39
|
<BreadcrumbItem isCurrent>API Reference</BreadcrumbItem>
|
|
@@ -7,7 +7,7 @@ export const doc = {
|
|
|
7
7
|
name: 'Selector — Option descriptions',
|
|
8
8
|
displayName: 'Selector — Option descriptions',
|
|
9
9
|
description:
|
|
10
|
-
'Options carry a description, so the dropdown draws a two-line row. The closed trigger is sized by padding, so it is the size token for a one-line value and exactly one line taller for a two-line one
|
|
10
|
+
'Options carry a description, so the dropdown draws a two-line row. The closed trigger is sized by padding, so it is the size token for a one-line value and exactly one line taller for a two-line one; both on the 4px rhythm. An InputGroup pins the row, so the value folds back onto one line there.',
|
|
11
11
|
isReady: true,
|
|
12
12
|
aspectRatio: 16 / 9,
|
|
13
13
|
componentsUsed: ['Selector', 'SelectorOption', 'InputGroup', 'Button', 'Stack'],
|