@stealthscale/component-navigation 0.1.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Stealth Scale B.V.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,108 @@
1
+ # @stealthscale/component-navigation
2
+
3
+ Draws the ways a person moves between places: the link, and the trail of crumbs from the front of a
4
+ site. Every component binds a recipe and draws nothing of its own, so a theme restyles all of them
5
+ by extending the recipe. The preset under `./theme` registers the recipes with an application's
6
+ compiler.
7
+
8
+ Every value a theme can change on a component is an axis of its recipe, so a caller sets it as a
9
+ prop and writes no style. A caller changes the element a component draws with `as`.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @stealthscale/component-navigation
15
+ ```
16
+
17
+ The package peers on `react` and `@stealthscale/theme`. An application lists the preset under
18
+ `./theme` among the presets its compiler installs.
19
+
20
+ ## Link
21
+
22
+ Draws words a person follows to somewhere else. The ink, the visited ink, the cursor and the focus
23
+ ring come from the theme, so a theme decides what a link looks like once for every link.
24
+
25
+ ```tsx
26
+ import { Link } from "@stealthscale/component-navigation";
27
+
28
+ <Link href="/invoices">Invoices</Link>;
29
+ <Link href="/terms" variant="underline">
30
+ Terms
31
+ </Link>;
32
+ <Link as={RouterLink} to="/invoices">
33
+ Invoices
34
+ </Link>;
35
+ ```
36
+
37
+ | Axis | Values | Default |
38
+ | --------- | -------------------- | ------- |
39
+ | `variant` | `plain`, `underline` | `plain` |
40
+
41
+ Both looks underline under a pointer. The axis decides whether the underline is there at rest as
42
+ well. A link inside a paragraph is found by its underline as much as by its colour, so a reader who
43
+ cannot tell the two inks apart has nothing else to go on.
44
+
45
+ The element is `a` and takes an `href`. A link with no address is not a link to anything, and a
46
+ browser gives it no focus, no Enter and no offer to open elsewhere, so a control that acts rather
47
+ than navigates is a button. A router's own link goes in through `as`, which keeps the routing and
48
+ leaves the drawing here.
49
+
50
+ ## Breadcrumb
51
+
52
+ Draws the path from the front of a site to the page a person is on, composed as `Breadcrumb.Root`
53
+ holding a list of crumbs.
54
+
55
+ ```tsx
56
+ import { Breadcrumb } from "@stealthscale/component-navigation";
57
+
58
+ <Breadcrumb.Root>
59
+ <Breadcrumb.List>
60
+ <Breadcrumb.Item>
61
+ <Breadcrumb.Link href="/">Home</Breadcrumb.Link>
62
+ </Breadcrumb.Item>
63
+ <Breadcrumb.Separator>/</Breadcrumb.Separator>
64
+ <Breadcrumb.Item>
65
+ <Breadcrumb.Link href="/invoices">Invoices</Breadcrumb.Link>
66
+ </Breadcrumb.Item>
67
+ <Breadcrumb.Separator>/</Breadcrumb.Separator>
68
+ <Breadcrumb.Item>
69
+ <Breadcrumb.CurrentLink>April</Breadcrumb.CurrentLink>
70
+ </Breadcrumb.Item>
71
+ </Breadcrumb.List>
72
+ </Breadcrumb.Root>;
73
+ ```
74
+
75
+ | Axis | Values | Default |
76
+ | --------- | ---------------------------- | ------- |
77
+ | `size` | `xs`, `sm`, `md`, `lg`, `xl` | `md` |
78
+ | `variant` | `plain`, `underline` | `plain` |
79
+
80
+ The size sets the text on the root and the gap on the list, so every part reads at one size by
81
+ inheriting it. It stops at `xl` because it reads the body role, a trail being read at the size of
82
+ the page around it rather than as a heading.
83
+
84
+ The last crumb is `Breadcrumb.CurrentLink` and not a link. It draws a `span` carrying
85
+ `aria-current="page"`, which is what tells a screen reader which crumb is where the reader is, and a
86
+ link to the page already open would be a control that does nothing. It is the one crumb at full
87
+ strength and the crumbs above it are muted, because the crumb naming where you are is the one worth
88
+ reading first.
89
+
90
+ The trail does four more things for accessibility:
91
+
92
+ - **The landmark is named.** The root is a `nav` carrying `aria-label="Breadcrumb"` by default,
93
+ because a page usually holds more than one navigation landmark and an unnamed one is announced
94
+ with nothing to tell it from the others. State your own to override it.
95
+ - **The list keeps its role.** The list is an `ol` stating `role="list"`, because a list drawn with
96
+ no marker loses its role in Safari and a reader is then told neither how many crumbs there are nor
97
+ which one they are on.
98
+ - **The separator is a row, not a crumb.** It sits between two items as a row of the list rather
99
+ than inside one, so a screen reader counting the list counts the crumbs.
100
+ - **The separator is silent.** It carries `aria-hidden` and a presentation role, because the list
101
+ already carries the order and a mark read out between every pair adds nothing.
102
+
103
+ The separator turns around where the line runs right to left, so a chevron pointing forwards keeps
104
+ pointing forwards.
105
+
106
+ ## Licence
107
+
108
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1 @@
1
+ {"$schema":"http://cyclonedx.org/schema/bom-1.7.schema.json","bomFormat":"CycloneDX","specVersion":"1.7","version":1,"metadata":{"lifecycles":[{"phase":"build"}],"tools":{"components":[{"type":"application","name":"rolldown","version":"1.2.7"},{"type":"application","name":"vite","version":"8.2.2"},{"type":"library","name":"core","group":"@arethetypeswrong","version":"0.18.5","bom-ref":"pkg:npm/%40arethetypeswrong/core@0.18.5","author":"Andrew Branch","description":"The API behind arethetypeswrong.github.io","hashes":[{"alg":"SHA-512","content":"f72b63cc6c318e6f54cfb23d6af7dbb79be542deae93db914444b3263aabce797a58abc8e9dc184e8faf277534d96aeafe64778aa97f3f386f1e129d2480230d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40arethetypeswrong/core@0.18.5","externalReferences":[{"url":"https://github.com/arethetypeswrong/arethetypeswrong.github.io.git#packages/core","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=20"}]},{"type":"library","name":"theme","group":"@stealthscale","version":"0.3.0","bom-ref":"pkg:npm/%40stealthscale/theme@0.3.0","description":"Publishes the foundation every recipe is written against, the runtime every component binds its recipe with, and the vocabulary a theme is written in.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/theme@0.3.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#foundations/theme","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/foundations/theme#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-react","group":"@stealthscale","version":"0.8.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-react@0.8.0","description":"Adds the JSX transform, the React lint rules, a test document and an MDX compiler to a package that renders.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-react@0.8.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-react#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-theme","group":"@stealthscale","version":"0.1.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-theme@0.1.0","description":"Adds the runtime generator to a design-system package and the stylesheet compiler to an application.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-theme@0.1.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-theme","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-theme#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config-typescript","group":"@stealthscale","version":"0.3.0","bom-ref":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","description":"Sets the TypeScript options every stealthscale package compiles with.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config-typescript@0.3.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config-typescript","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config-typescript#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"vite-config","group":"@stealthscale","version":"0.6.0","bom-ref":"pkg:npm/%40stealthscale/vite-config@0.6.0","description":"Configures a package from five Vite+ tiers and a named layer for every block.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/vite-config@0.6.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#packages/vite-config","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/packages/vite-config#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}]},{"type":"library","name":"react","group":"@testing-library","version":"16.3.3","bom-ref":"pkg:npm/%40testing-library/react@16.3.3","author":"Kent C. Dodds <me@kentcdodds.com> (https://kentcdodds.com)","description":"Simple and complete React DOM testing utilities that encourage good testing practices.","hashes":[{"alg":"SHA-512","content":"528d7ddcd8106cf333ea5aeb86d45040570c0ba45efc42cb15b6ee54bdf4583959c65a597fdfe53074c05713d12f0d6ad62bbd389991d5cd812e210d46cb5d06"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40testing-library/react@16.3.3","externalReferences":[{"url":"https://github.com/testing-library/react-testing-library/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/testing-library/react-testing-library","type":"vcs","comment":"as detected from PackageJson property \"repository.url\""},{"url":"https://github.com/testing-library/react-testing-library#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=18"}]},{"type":"library","name":"react-dom","group":"@types","version":"19.3.0","bom-ref":"pkg:npm/%40types/react-dom@19.3.0","description":"TypeScript definitions for react-dom","hashes":[{"alg":"SHA-512","content":"648edb538da66575ca1e7fea34b130d88adb888354ed7e7ebdf81d8b10479023696165e32a07d0fcffaec866f90a338b07d51c9d37a0debca542d5768729b8e1"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40types/react-dom@19.3.0","externalReferences":[{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react-dom","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react-dom","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"react","group":"@types","version":"19.3.0","bom-ref":"pkg:npm/%40types/react@19.3.0","description":"TypeScript definitions for react","hashes":[{"alg":"SHA-512","content":"374ac50ae1fd628c46f7f9bad65f4c7e924a7e62ce554d1e9bb8a9233e9346049292f45e2c1f6f2fce4607ecabf01b3995eaa9be0f7a252c05e194b5b38be242"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40types/react@19.3.0","externalReferences":[{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped.git#types/react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/react","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"babel-plugin-react-compiler","version":"1.0.0","bom-ref":"pkg:npm/babel-plugin-react-compiler@1.0.0","description":"Babel plugin for React Compiler.","hashes":[{"alg":"SHA-512","content":"2319bcb457e828a20f61d0822984ecaaff85778209d0342a3321229a8fa9c5438c511f5c54f9704eb16df40beefb771be99a77980ce5fadd4cac6d9fc712acc7"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/babel-plugin-react-compiler@1.0.0","externalReferences":[{"url":"git+https://github.com/facebook/react.git#compiler/packages/babel-plugin-react-compiler","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""}]},{"type":"library","name":"happy-dom","version":"20.14.5","bom-ref":"pkg:npm/happy-dom@20.14.5","author":"David Ortner","description":"Happy DOM is a JavaScript implementation of a web browser without its graphical user interface. It includes many web standards from WHATWG DOM and HTML.","hashes":[{"alg":"SHA-512","content":"c7f47392958ee346d38c8a13df4890b6deb814b2e61ff89171408dd97fff6cbc7b1f789f91d7cf5f2aacae8fce62dab32008622cc300ee69a2391f42dcd38a8d"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/happy-dom@20.14.5","externalReferences":[{"url":"https://github.com/capricorn86/happy-dom","type":"vcs","comment":"as detected from PackageJson property \"repository\""},{"url":"https://github.com/capricorn86/happy-dom","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=20.0.0"}]},{"type":"library","name":"oxc-transform-react","version":"0.149.0","bom-ref":"pkg:npm/oxc-transform-react@0.149.0","author":"Boshen and oxc contributors","description":"Oxc React Compiler Node API","hashes":[{"alg":"SHA-512","content":"69bdddc5c3ed90349f211f6d35dc4752d9a974bd258479ddb010fd214014e41c6d26dc061bff39367473b367f62b0a2e3641e5edc692b3030b41e52fc3714447"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/oxc-transform-react@0.149.0","externalReferences":[{"url":"https://github.com/oxc-project/oxc/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/oxc-project/oxc.git#napi/transform-react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://oxc.rs","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^20.19.0 || >=22.12.0"}]},{"type":"library","name":"react-dom","version":"19.3.0","bom-ref":"pkg:npm/react-dom@19.3.0","description":"React package for working with the DOM.","hashes":[{"alg":"SHA-512","content":"24393c76089fe753a31680c4ef4f8e4fd202c98afeebd1e59a284dc29d7e36c7db9dadede6c22209af59264b43990e3fd5bfeb9f6ea1200476b985c332be6bd1"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/react-dom@19.3.0","externalReferences":[{"url":"https://github.com/react/react/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/react/react.git#packages/react-dom","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://react.dev/","type":"website","comment":"as detected from PackageJson property \"homepage\""}]},{"type":"library","name":"react","version":"19.3.0","bom-ref":"pkg:npm/react@19.3.0","description":"React is a JavaScript library for building user interfaces.","hashes":[{"alg":"SHA-512","content":"13c2d471bb415addb46db9766281dfc7864305dc554df38ab42667f5c0d227897afe7ba8029729201723e3bb76c19a155fc836647b8c1db892860091d53e52a2"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/react@19.3.0","externalReferences":[{"url":"https://github.com/react/react/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"https://github.com/react/react.git#packages/react","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://react.dev/","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=0.10.0"}]},{"type":"library","name":"typescript","version":"7.0.2","bom-ref":"pkg:npm/typescript@7.0.2","author":"Microsoft Corp.","description":"TypeScript is a language for application scale JavaScript development","hashes":[{"alg":"SHA-512","content":"f0561abbdea8dcd28e85b8ca8bfa8dbc6fd6e63873c646dd9b9b23f406d9ff94f9b16aa7de12602df1b1dbbb11299593bf2cc23fc74b4414dfe6d05349155434"}],"licenses":[{"license":{"id":"Apache-2.0"}}],"purl":"pkg:npm/typescript@7.0.2","externalReferences":[{"url":"https://github.com/microsoft/TypeScript/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"https://github.com/microsoft/TypeScript.git","type":"vcs","comment":"as detected from PackageJson property \"repository.url\""},{"url":"https://www.typescriptlang.org/","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=16.20.0"}]},{"type":"library","name":"vitest","version":"4.1.11","bom-ref":"pkg:npm/vitest@4.1.11","author":"Anthony Fu <anthonyfu117@hotmail.com>","description":"Next generation testing framework powered by Vite","hashes":[{"alg":"SHA-512","content":"7e1002acd5d489d21b19206be4595bb8190eed5582d59c8b9740cee025360eb42800fc57f3862cc6cf877864298a2e656565754388016534eee3d985f80e8477"}],"licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/vitest@4.1.11","externalReferences":[{"url":"https://github.com/vitest-dev/vitest/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs.url\""},{"url":"git+https://github.com/vitest-dev/vitest.git#packages/vitest","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://vitest.dev","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":"^20.0.0 || ^22.0.0 || >=24.0.0"}]}]},"component":{"type":"library","name":"component-navigation","group":"@stealthscale","version":"0.1.0","bom-ref":"pkg:npm/%40stealthscale/component-navigation@0.1.0","description":"Draws the ways a person moves between places: links, tabs, breadcrumbs and rails.","licenses":[{"license":{"id":"MIT"}}],"purl":"pkg:npm/%40stealthscale/component-navigation@0.1.0","externalReferences":[{"url":"https://github.com/stealth-scale/config/issues","type":"issue-tracker","comment":"as detected from PackageJson property \"bugs\""},{"url":"git+https://github.com/stealth-scale/config.git#components/navigation","type":"vcs","comment":"as detected from PackageJson property \"repository.url\" and \"repository.directory\""},{"url":"https://github.com/stealth-scale/config/tree/main/components/navigation#readme","type":"website","comment":"as detected from PackageJson property \"homepage\""}],"properties":[{"name":"cdx:npm:package:constraint:engine:node","value":">=26.0.0"}],"evidence":{"licenses":[{"license":{"name":"file: LICENSE","text":{"content":"TUlUIExpY2Vuc2UKCkNvcHlyaWdodCAoYykgMjAyNiBTdGVhbHRoIFNjYWxlIEIuVi4KClBlcm1pc3Npb24gaXMgaGVyZWJ5IGdyYW50ZWQsIGZyZWUgb2YgY2hhcmdlLCB0byBhbnkgcGVyc29uIG9idGFpbmluZyBhIGNvcHkKb2YgdGhpcyBzb2Z0d2FyZSBhbmQgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uIGZpbGVzICh0aGUgIlNvZnR3YXJlIiksIHRvIGRlYWwKaW4gdGhlIFNvZnR3YXJlIHdpdGhvdXQgcmVzdHJpY3Rpb24sIGluY2x1ZGluZyB3aXRob3V0IGxpbWl0YXRpb24gdGhlIHJpZ2h0cwp0byB1c2UsIGNvcHksIG1vZGlmeSwgbWVyZ2UsIHB1Ymxpc2gsIGRpc3RyaWJ1dGUsIHN1YmxpY2Vuc2UsIGFuZC9vciBzZWxsCmNvcGllcyBvZiB0aGUgU29mdHdhcmUsIGFuZCB0byBwZXJtaXQgcGVyc29ucyB0byB3aG9tIHRoZSBTb2Z0d2FyZSBpcwpmdXJuaXNoZWQgdG8gZG8gc28sIHN1YmplY3QgdG8gdGhlIGZvbGxvd2luZyBjb25kaXRpb25zOgoKVGhlIGFib3ZlIGNvcHlyaWdodCBub3RpY2UgYW5kIHRoaXMgcGVybWlzc2lvbiBub3RpY2Ugc2hhbGwgYmUgaW5jbHVkZWQgaW4gYWxsCmNvcGllcyBvciBzdWJzdGFudGlhbCBwb3J0aW9ucyBvZiB0aGUgU29mdHdhcmUuCgpUSEUgU09GVFdBUkUgSVMgUFJPVklERUQgIkFTIElTIiwgV0lUSE9VVCBXQVJSQU5UWSBPRiBBTlkgS0lORCwgRVhQUkVTUyBPUgpJTVBMSUVELCBJTkNMVURJTkcgQlVUIE5PVCBMSU1JVEVEIFRPIFRIRSBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSwKRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UgQU5EIE5PTklORlJJTkdFTUVOVC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFCkFVVEhPUlMgT1IgQ09QWVJJR0hUIEhPTERFUlMgQkUgTElBQkxFIEZPUiBBTlkgQ0xBSU0sIERBTUFHRVMgT1IgT1RIRVIKTElBQklMSVRZLCBXSEVUSEVSIElOIEFOIEFDVElPTiBPRiBDT05UUkFDVCwgVE9SVCBPUiBPVEhFUldJU0UsIEFSSVNJTkcgRlJPTSwKT1VUIE9GIE9SIElOIENPTk5FQ1RJT04gV0lUSCBUSEUgU09GVFdBUkUgT1IgVEhFIFVTRSBPUiBPVEhFUiBERUFMSU5HUyBJTiBUSEUKU09GVFdBUkUuCg==","contentType":"text/plain","encoding":"base64"}}}]}},"supplier":{"name":"Stealth Scale B.V.","url":["https://stealthscale.io"]}},"components":[],"dependencies":[{"ref":"pkg:npm/%40stealthscale/component-navigation@0.1.0"}]}
@@ -0,0 +1,101 @@
1
+ import { ComponentProps } from "react";
2
+ //#region src/breadcrumb/current-link.d.ts
3
+ /**
4
+ * Draws the name of the page itself, at full strength against the muted crumbs above it.
5
+ */
6
+ declare const CurrentLink: import("@stealthscale/theme").StyledComponent<"span", {}>;
7
+ /**
8
+ * Describes what the current crumb takes.
9
+ */
10
+ type CurrentLinkProps = ComponentProps<typeof CurrentLink>;
11
+ //#endregion
12
+ //#region src/breadcrumb/item.d.ts
13
+ /**
14
+ * Draws one row of the trail, carrying either a link to somewhere above or the name of the page
15
+ * itself.
16
+ */
17
+ declare const Item: import("@stealthscale/theme").StyledComponent<"li", {}>;
18
+ /**
19
+ * Describes what a crumb takes.
20
+ */
21
+ type ItemProps = ComponentProps<typeof Item>;
22
+ //#endregion
23
+ //#region src/breadcrumb/link.d.ts
24
+ /**
25
+ * Draws a link to somewhere above the page.
26
+ */
27
+ declare const Link$1: import("@stealthscale/theme").StyledComponent<"a", {}>;
28
+ /**
29
+ * Describes what a crumb's link takes.
30
+ */
31
+ type LinkProps$1 = ComponentProps<typeof Link$1>;
32
+ //#endregion
33
+ //#region src/breadcrumb/list.d.ts
34
+ /**
35
+ * Draws the crumbs in order, at the gap its size states.
36
+ */
37
+ declare const List: import("@stealthscale/theme").StyledComponent<"ol", {}>;
38
+ /**
39
+ * Describes what the list takes.
40
+ */
41
+ type ListProps = ComponentProps<typeof List>;
42
+ //#endregion
43
+ //#region src/breadcrumb/root.d.ts
44
+ /**
45
+ * Draws the landmark and sets the size and look every part below it reads.
46
+ */
47
+ declare const Root: import("@stealthscale/theme").StyledComponent<"nav", {
48
+ readonly size?: "lg" | "md" | "sm" | "xl" | "xs";
49
+ readonly variant?: "plain" | "underline";
50
+ }>;
51
+ /**
52
+ * Describes what the landmark takes.
53
+ */
54
+ type RootProps = ComponentProps<typeof Root>;
55
+ //#endregion
56
+ //#region src/breadcrumb/separator.d.ts
57
+ /**
58
+ * Draws the mark between two crumbs, saying nothing to a screen reader.
59
+ */
60
+ declare const Separator: import("@stealthscale/theme").StyledComponent<"li", {}>;
61
+ /**
62
+ * Describes what a separator takes.
63
+ */
64
+ type SeparatorProps = ComponentProps<typeof Separator>;
65
+ declare namespace index_d_exports {
66
+ export { CurrentLink, CurrentLinkProps, Item, ItemProps, Link$1 as Link, LinkProps$1 as LinkProps, List, ListProps, Root, RootProps, Separator, SeparatorProps };
67
+ }
68
+ //#endregion
69
+ //#region src/link/context.d.ts
70
+ /**
71
+ * Binds the link's recipe to the element that draws it.
72
+ *
73
+ * @remarks
74
+ * Apart from the recipe, because an application's compiler reads the recipe at build time and
75
+ * the binding needs the runtime.
76
+ */
77
+ /**
78
+ * Binds the recipe once, for the link and for whatever sets its variants from above.
79
+ */
80
+ declare const PropsProvider: import("react").Provider<import("@stealthscale/theme").DataAttrs & Partial<{
81
+ readonly variant?: "plain" | "underline";
82
+ }>>, withContext: <Tag extends import("react").ElementType>(Component: Tag, options?: import("@stealthscale/theme").JsxFactoryOptions<import("@stealthscale/theme").ComponentProps<Tag> & Partial<{
83
+ readonly variant?: "plain" | "underline";
84
+ }>> | undefined) => import("@stealthscale/theme").StyledComponent<Tag, {
85
+ readonly variant?: "plain" | "underline";
86
+ }>;
87
+ //#endregion
88
+ //#region src/link/link.d.ts
89
+ /**
90
+ * Draws words a person follows to somewhere else.
91
+ */
92
+ export declare const Link: import("@stealthscale/theme").StyledComponent<"a", {
93
+ readonly variant?: "plain" | "underline";
94
+ }>;
95
+ /**
96
+ * Describes what a link takes: the variants its recipe offers, and everything a styled anchor
97
+ * takes.
98
+ */
99
+ type LinkProps = ComponentProps<typeof Link>;
100
+ //#endregion
101
+ export { index_d_exports as Breadcrumb, type LinkProps, PropsProvider as LinkPropsProvider };
package/dist/index.js ADDED
@@ -0,0 +1,87 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { n as recipe, t as recipe$1 } from "./recipe-TfPpeGkM.js";
3
+ import "react";
4
+ import { createRecipeContext, createSlotRecipeContext } from "@stealthscale/theme";
5
+ //#region src/breadcrumb/context.ts
6
+ /**
7
+ * Binds the breadcrumb's recipe to the elements that draw its parts.
8
+ *
9
+ * @remarks
10
+ * Apart from the recipe, because an application's compiler reads the recipe at build time and
11
+ * the binding needs the runtime.
12
+ */
13
+ /**
14
+ * Binds the recipe once. The root provides the variants and every other part reads them.
15
+ */
16
+ const { withContext: withContext$1, withProvider } = createSlotRecipeContext(recipe);
17
+ //#endregion
18
+ //#region src/breadcrumb/current-link.ts
19
+ /**
20
+ * Draws the name of the page itself, at full strength against the muted crumbs above it.
21
+ */
22
+ const CurrentLink = withContext$1("span", "currentLink", { defaultProps: { "aria-current": "page" } });
23
+ //#endregion
24
+ //#region src/breadcrumb/item.ts
25
+ /**
26
+ * Draws one row of the trail, carrying either a link to somewhere above or the name of the page
27
+ * itself.
28
+ */
29
+ const Item = withContext$1("li", "item");
30
+ //#endregion
31
+ //#region src/breadcrumb/link.ts
32
+ /**
33
+ * Draws a link to somewhere above the page.
34
+ */
35
+ const Link$1 = withContext$1("a", "link");
36
+ //#endregion
37
+ //#region src/breadcrumb/list.ts
38
+ /**
39
+ * Draws the crumbs in order, at the gap its size states.
40
+ */
41
+ const List = withContext$1("ol", "list", { defaultProps: { role: "list" } });
42
+ //#endregion
43
+ //#region src/breadcrumb/root.ts
44
+ /**
45
+ * Draws the landmark and sets the size and look every part below it reads.
46
+ */
47
+ const Root = withProvider("nav", "root", { defaultProps: { "aria-label": "Breadcrumb" } });
48
+ //#endregion
49
+ //#region src/breadcrumb/separator.ts
50
+ /**
51
+ * Draws the mark between two crumbs, saying nothing to a screen reader.
52
+ */
53
+ const Separator = withContext$1("li", "separator", { defaultProps: {
54
+ "aria-hidden": true,
55
+ role: "presentation"
56
+ } });
57
+ //#endregion
58
+ //#region src/breadcrumb/index.ts
59
+ var breadcrumb_exports = /* @__PURE__ */ __exportAll({
60
+ CurrentLink: () => CurrentLink,
61
+ Item: () => Item,
62
+ Link: () => Link$1,
63
+ List: () => List,
64
+ Root: () => Root,
65
+ Separator: () => Separator
66
+ });
67
+ //#endregion
68
+ //#region src/link/context.ts
69
+ /**
70
+ * Binds the link's recipe to the element that draws it.
71
+ *
72
+ * @remarks
73
+ * Apart from the recipe, because an application's compiler reads the recipe at build time and
74
+ * the binding needs the runtime.
75
+ */
76
+ /**
77
+ * Binds the recipe once, for the link and for whatever sets its variants from above.
78
+ */
79
+ const { PropsProvider, withContext } = createRecipeContext(recipe$1);
80
+ //#endregion
81
+ //#region src/link/link.ts
82
+ /**
83
+ * Draws words a person follows to somewhere else.
84
+ */
85
+ const Link = withContext("a");
86
+ //#endregion
87
+ export { breadcrumb_exports as Breadcrumb, Link, PropsProvider as LinkPropsProvider };
@@ -0,0 +1,150 @@
1
+ import { defineRecipe, defineSlotRecipe, gapSizes, link, onSlots, textSizes } from "@stealthscale/theme/authoring";
2
+ //#region src/breadcrumb/recipe.ts
3
+ /**
4
+ * States what a breadcrumb trail is: the path from the front of a site to the page a person is on,
5
+ * drawn as a list of links with a mark between each pair.
6
+ *
7
+ * @remarks
8
+ * Six parts. The root is the landmark, the list holds the trail, and each crumb is an item
9
+ * holding either a link to somewhere above or the name of the page itself. A separator sits
10
+ * between two crumbs as a row of the list rather than inside one, which is how a screen reader
11
+ * counts the crumbs rather than counting them twice.
12
+ * The size axis sets the text on the root and the gap on the list, so every part reads at one
13
+ * size by inheriting it and only the spacing is stated twice. It stops at `xl` because it reads
14
+ * the body role, and a trail is read at the size the page around it is read at rather than as a
15
+ * heading.
16
+ * The trail is quieter than the page it sits above: the links are muted and darken under a
17
+ * pointer, and the crumb for the page itself is the one at full strength. That is the way round
18
+ * a reader needs, because the one crumb that is not a link is the one naming where they are.
19
+ */
20
+ /**
21
+ * Draws a muted trail at the middle size until a caller says otherwise.
22
+ */
23
+ const recipe$1 = defineSlotRecipe({
24
+ base: {
25
+ currentLink: {
26
+ alignItems: "center",
27
+ display: "inline-flex",
28
+ gap: "gap.xs"
29
+ },
30
+ item: {
31
+ alignItems: "center",
32
+ display: "inline-flex"
33
+ },
34
+ link: {
35
+ alignItems: "center",
36
+ borderRadius: "l1",
37
+ cursor: "button",
38
+ display: "inline-flex",
39
+ focusRingColor: "colorPalette.focusRing",
40
+ focusVisibleRing: "outside",
41
+ gap: "gap.xs",
42
+ textDecoration: "none"
43
+ },
44
+ list: {
45
+ alignItems: "center",
46
+ display: "flex",
47
+ flexWrap: "wrap",
48
+ listStyle: "none",
49
+ margin: "0",
50
+ padding: "0",
51
+ wordBreak: "break-word"
52
+ },
53
+ separator: {
54
+ _rtl: { rotate: "180deg" },
55
+ color: "fg.muted"
56
+ }
57
+ },
58
+ className: "breadcrumb",
59
+ defaultVariants: {
60
+ size: "md",
61
+ variant: "plain"
62
+ },
63
+ jsx: [/^Breadcrumb(\.\w+)?$/u],
64
+ slots: [
65
+ "root",
66
+ "list",
67
+ "item",
68
+ "link",
69
+ "currentLink",
70
+ "separator"
71
+ ],
72
+ variants: {
73
+ /**
74
+ * How big the trail is read at, which every part inherits from the root.
75
+ */
76
+ size: onSlots({
77
+ list: gapSizes([
78
+ "xs",
79
+ "sm",
80
+ "md",
81
+ "lg",
82
+ "xl"
83
+ ]),
84
+ root: textSizes("body")
85
+ }),
86
+ /**
87
+ * Whether a crumb above the page is underlined at rest or only under a pointer.
88
+ */
89
+ variant: {
90
+ plain: {
91
+ currentLink: { color: "fg" },
92
+ link: {
93
+ _hover: {
94
+ color: "fg",
95
+ textDecoration: "underline"
96
+ },
97
+ color: "fg.muted"
98
+ }
99
+ },
100
+ underline: {
101
+ currentLink: { color: "fg" },
102
+ link: {
103
+ _hover: { color: "fg" },
104
+ color: "fg.muted",
105
+ textDecoration: "underline"
106
+ }
107
+ }
108
+ }
109
+ }
110
+ });
111
+ //#endregion
112
+ //#region src/link/recipe.ts
113
+ /**
114
+ * States what a link is: words a person follows to somewhere else, drawn in the theme's link ink
115
+ * and underlined either always or under a pointer.
116
+ *
117
+ * @remarks
118
+ * The ink, the visited ink, the cursor and the focus ring all come from the theme's own link
119
+ * fragment, so a theme that decides what a link looks like decides it once for every link. The
120
+ * box is inline so a link sits in a line of words, and it lays its content out in a row so a
121
+ * mark beside the words is centred on them rather than sitting on the baseline.
122
+ * The underline is the one thing this recipe adds. A link inside a paragraph is found by its
123
+ * underline as much as by its colour, and a reader who cannot tell the two inks apart has
124
+ * nothing else to go on, which is why colour alone is not enough to mark a link.
125
+ */
126
+ /**
127
+ * Draws a link underlined under a pointer until a caller asks for one that always is.
128
+ */
129
+ const recipe = defineRecipe({
130
+ base: {
131
+ ...link(),
132
+ alignItems: "center",
133
+ borderRadius: "l1",
134
+ display: "inline-flex",
135
+ gap: "gap.xs"
136
+ },
137
+ className: "link",
138
+ defaultVariants: { variant: "plain" },
139
+ jsx: [/^Link$/u],
140
+ variants: {
141
+ /**
142
+ * Whether the underline is drawn at rest or only under a pointer.
143
+ */
144
+ variant: {
145
+ plain: { textDecoration: "none" },
146
+ underline: { textDecoration: "underline" }
147
+ } }
148
+ });
149
+ //#endregion
150
+ export { recipe$1 as n, recipe as t };
@@ -0,0 +1,13 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __defProp = Object.defineProperty;
3
+ var __exportAll = (all, no_symbols) => {
4
+ let target = {};
5
+ for (var name in all) __defProp(target, name, {
6
+ get: all[name],
7
+ enumerable: true
8
+ });
9
+ if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
10
+ return target;
11
+ };
12
+ //#endregion
13
+ export { __exportAll as t };
@@ -0,0 +1,12 @@
1
+ //#region src/theme.d.ts
2
+ /**
3
+ * Publishes the preset that registers every recipe in this package, for an application's compiler
4
+ * to install.
5
+ *
6
+ * @remarks
7
+ * The list is written by hand. The package's own specification reports a recipe file the list
8
+ * leaves out, so no generator runs here.
9
+ */
10
+ declare const _default: import("@stealthscale/theme/authoring").Preset;
11
+ //#endregion
12
+ export { _default as default };
package/dist/theme.js ADDED
@@ -0,0 +1,20 @@
1
+ import { n as recipe$1, t as recipe } from "./recipe-TfPpeGkM.js";
2
+ import { definePreset } from "@stealthscale/theme/authoring";
3
+ //#region src/theme.ts
4
+ /**
5
+ * Publishes the preset that registers every recipe in this package, for an application's compiler
6
+ * to install.
7
+ *
8
+ * @remarks
9
+ * The list is written by hand. The package's own specification reports a recipe file the list
10
+ * leaves out, so no generator runs here.
11
+ */
12
+ var theme_default = definePreset({
13
+ name: "@stealthscale/component-navigation",
14
+ theme: { extend: {
15
+ recipes: { link: recipe },
16
+ slotRecipes: { breadcrumb: recipe$1 }
17
+ } }
18
+ });
19
+ //#endregion
20
+ export { theme_default as default };
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@stealthscale/component-navigation",
3
+ "version": "0.1.0",
4
+ "description": "Draws the ways a person moves between places: links, tabs, breadcrumbs and rails.",
5
+ "keywords": [
6
+ "components",
7
+ "navigation",
8
+ "react",
9
+ "tabs"
10
+ ],
11
+ "homepage": "https://github.com/stealth-scale/config/tree/main/components/navigation#readme",
12
+ "bugs": "https://github.com/stealth-scale/config/issues",
13
+ "license": "MIT",
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/stealth-scale/config.git",
17
+ "directory": "components/navigation"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "LICENSE",
22
+ "README.md"
23
+ ],
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "imports": {
27
+ "#*": "./src/*"
28
+ },
29
+ "exports": {
30
+ ".": "./dist/index.js",
31
+ "./theme": "./dist/theme.js",
32
+ "./package.json": "./package.json"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "devDependencies": {
38
+ "@arethetypeswrong/core": "^0.18.5",
39
+ "@rolldown/plugin-babel": "^0.2.4",
40
+ "@stealthscale/theme": "^0.3.0",
41
+ "@stealthscale/vite-config": "^0.6.0",
42
+ "@stealthscale/vite-config-react": "^0.8.0",
43
+ "@stealthscale/vite-config-theme": "^0.1.0",
44
+ "@stealthscale/vite-config-typescript": "^0.3.0",
45
+ "@testing-library/react": "^16.3.0",
46
+ "@types/react": "^19.3.0",
47
+ "@types/react-dom": "^19.3.0",
48
+ "babel-plugin-react-compiler": "^1.0.0",
49
+ "happy-dom": "^20.14.3",
50
+ "oxc-transform-react": "^0.149.0",
51
+ "publint": "^0.3.24",
52
+ "react": "^19.3.0",
53
+ "react-dom": "^19.3.0",
54
+ "typescript": "^7.0.2",
55
+ "vitest": "4.1.11"
56
+ },
57
+ "peerDependencies": {
58
+ "@stealthscale/theme": "^0.3.0",
59
+ "react": "^19.3.0"
60
+ },
61
+ "engines": {
62
+ "node": ">=26.0.0"
63
+ },
64
+ "scripts": {
65
+ "build": "vp pack",
66
+ "check": "vp check",
67
+ "test": "vp test"
68
+ }
69
+ }