noecosystem-design 0.1.3 → 0.2.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/README.md +73 -35
- package/apps/showcase/src/catalog/catalog-index.tsx +3 -3
- package/apps/storybook/stories/catalog/icon-system.stories.tsx +3 -2
- package/docs/adr/0006-icons8-icon-policy.md +4 -4
- package/docs/design-system/architecture.md +1 -1
- package/docs/design-system/ci-cd.md +22 -7
- package/docs/design-system/rtl.md +1 -1
- package/package.json +1 -1
- package/packages/design-tokens/src/base.css +1 -1
- package/packages/design-tokens/src/tokens.css +6 -6
- package/packages/design-tokens/src/tokens.json +4 -4
- package/packages/design-tokens/src/tokens.mjs +4 -4
- package/packages/icons/package.json +3 -0
- package/packages/icons/src/adapters.tsx +4 -4
- package/packages/icons/src/iconsax.tsx +233 -0
- package/packages/icons/src/index.tsx +1 -103
- package/packages/registry/catalog-index.json +1 -1
- package/packages/registry/component-audit.json +1 -1
- package/packages/registry/docs-index.json +1 -1
- package/packages/registry/manifest.json +1 -1
- package/packages/registry/r/calendar.json +12 -1
- package/packages/registry/r/date-picker.json +12 -1
- package/packages/registry/r/icon-system.json +6 -1
- package/packages/registry/r/inline-cta.json +6 -1
- package/packages/registry/r/pagination.json +12 -1
- package/packages/registry/search-index.json +1 -1
- package/packages/ui/src/theme-provider.browser.test.tsx +3 -2
- package/scripts/generate-manifest.mjs +56 -38
- package/scripts/validate-manifest.mjs +3 -29
- package/tests/e2e/catalog-typography.spec.ts +1 -2
- package/tests/icons.test.mjs +5 -5
- package/apps/showcase/src/catalog/__screenshots__/catalog-shell.browser.test.tsx/filters-the-component-inventory-and-exposes-the-selected-audit-state-1.png +0 -0
- package/apps/showcase/src/catalog/__screenshots__/catalog-shell.browser.test.tsx/opens-a-component-detail-view-and-returns-to-the-grid-1.png +0 -0
- package/apps/showcase/src/catalog/__screenshots__/catalog-shell.browser.test.tsx/switching-locale-to-Persian-updates-document-direction-to-rtl-1.png +0 -0
- package/packages/ui/src/__screenshots__/button.browser.test.tsx/Button-preserves-native--busy--disabled--and-ref-contracts-1.png +0 -0
- package/packages/ui/src/__screenshots__/button.browser.test.tsx/a-disabled-polymorphic-Button-cannot-navigate-or-receive-focus-1.png +0 -0
- package/packages/ui/src/__screenshots__/date-picker.browser.test.tsx/DatePicker-Escape-closes-popover-and-returns-focus-to-input-1.png +0 -0
- package/packages/ui/src/__screenshots__/date-picker.browser.test.tsx/DatePicker-click-outside-closes-popover-1.png +0 -0
- package/packages/ui/src/__screenshots__/dialog.browser.test.tsx/Dialog-propagates-RTL-into-the-portal-and-traps-then-restores-focus-1.png +0 -0
package/README.md
CHANGED
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
# NOE Design System
|
|
2
2
|
|
|
3
|
-
NOE is a multilingual, bidirectional, theme-aware React design system.
|
|
4
|
-
|
|
5
|
-
registry metadata, reference apps, documentation, and verification
|
|
3
|
+
NOE is a multilingual, bidirectional, theme-aware React design system. The public
|
|
4
|
+
`noecosystem-design` package is an agent-readable source snapshot: semantic tokens,
|
|
5
|
+
component source, registry metadata, reference apps, documentation, and verification
|
|
6
|
+
contracts.
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Quick start
|
|
9
|
+
|
|
10
|
+
### Give a coding agent NOE context
|
|
8
11
|
|
|
9
12
|
```bash
|
|
10
|
-
# Give a coding agent the NOE source context.
|
|
11
13
|
npm install noecosystem-design
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The package is read-only source context, not a compiled runtime dependency. Start an
|
|
17
|
+
agent with these files, in this order:
|
|
12
18
|
|
|
13
|
-
|
|
19
|
+
```bash
|
|
14
20
|
cat node_modules/noecosystem-design/AGENTS.md
|
|
15
21
|
cat node_modules/noecosystem-design/llms.txt
|
|
16
22
|
cat node_modules/noecosystem-design/docs/design-system/gpt-agent-prompt.md
|
|
23
|
+
```
|
|
17
24
|
|
|
18
|
-
|
|
25
|
+
For GPT, copy
|
|
26
|
+
[`docs/design-system/gpt-agent-prompt.md`](docs/design-system/gpt-agent-prompt.md),
|
|
27
|
+
replace `<TARGET_REPOSITORY>` and `<DESCRIBE THE REQUESTED OUTCOME...>`, and send it
|
|
28
|
+
as one task. Keep the task section outcome-focused; the template already supplies
|
|
29
|
+
the stable NOE operating context.
|
|
30
|
+
|
|
31
|
+
### Work on NOE itself
|
|
32
|
+
|
|
33
|
+
```bash
|
|
19
34
|
git clone https://github.com/NOrchestration/noe-design-system.git
|
|
20
35
|
cd noe-design-system
|
|
21
36
|
corepack enable
|
|
@@ -24,38 +39,34 @@ pnpm check
|
|
|
24
39
|
```
|
|
25
40
|
|
|
26
41
|
Read [`AGENTS.md`](AGENTS.md) before changing code. Use [`llms.txt`](llms.txt) as the
|
|
27
|
-
retrieval map, then
|
|
28
|
-
catalog is [`packages/registry/manifest.json`](packages/registry/manifest.json).
|
|
42
|
+
retrieval map, then inspect the affected package and component records. The canonical
|
|
43
|
+
component catalog is [`packages/registry/manifest.json`](packages/registry/manifest.json).
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
[`docs/design-system/gpt-agent-prompt.md`](docs/design-system/gpt-agent-prompt.md),
|
|
32
|
-
replace `<TARGET_REPOSITORY>` and `<DESCRIBE THE REQUESTED OUTCOME...>`, and send it
|
|
33
|
-
as one task prompt. The template carries static NOE context once; put only the
|
|
34
|
-
requested outcome, scope, and non-negotiable constraints in its `Task` section.
|
|
45
|
+
## Package contract
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
| Use `noecosystem-design` for | Do not use it for |
|
|
48
|
+
| --- | --- |
|
|
49
|
+
| Giving an agent NOE source, rules, registry metadata, and examples | `import { Button } from "noecosystem-design"` |
|
|
50
|
+
| Reviewing or adapting NOE patterns in another repository | Replacing a published runtime component library |
|
|
51
|
+
| Finding reusable components before designing a new one | Assuming a public registry endpoint is deployed |
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
not published npm packages at this time.
|
|
43
|
-
- The shadcn-compatible `@noe` registry is locally generated and tested, but its
|
|
44
|
-
external site endpoint is not yet evidenced as deployed. Do not claim or assume
|
|
45
|
-
that `shadcn add @noe/<item>` works against the public domain.
|
|
53
|
+
Internal source packages use the `@noe/*` names. They are workspace boundaries, not
|
|
54
|
+
published npm packages. The shadcn-compatible `@noe` registry is locally generated
|
|
55
|
+
and tested, but its external endpoint is not deployment-proven; do not assume that
|
|
56
|
+
`shadcn add @noe/<item>` works against a public domain.
|
|
46
57
|
|
|
47
|
-
## Agent
|
|
58
|
+
## Agent workflow
|
|
48
59
|
|
|
49
60
|
1. Read `AGENTS.md`, then `llms.txt`.
|
|
50
|
-
2.
|
|
51
|
-
`
|
|
61
|
+
2. Locate the capability in `packages/registry/manifest.json`; read its `useWhen`,
|
|
62
|
+
`doNotUseWhen`, accessibility, i18n, and dependency contracts.
|
|
52
63
|
3. Reuse an existing `@noe` component before considering a foundation or new code.
|
|
53
|
-
4. Preserve semantic tokens, i18n, RTL/bidi, and portal
|
|
64
|
+
4. Preserve semantic tokens, i18n, RTL/bidi, and portal-direction contracts.
|
|
54
65
|
5. Update source, stories, tests, registry metadata, and documentation together.
|
|
55
|
-
6. Report only executed
|
|
56
|
-
|
|
66
|
+
6. Report only checks actually executed; local registry output is not evidence of a
|
|
67
|
+
deployed external installation path.
|
|
57
68
|
|
|
58
|
-
## Workspace
|
|
69
|
+
## Workspace checks
|
|
59
70
|
|
|
60
71
|
| Command | Purpose |
|
|
61
72
|
| --- | --- |
|
|
@@ -70,6 +81,28 @@ requested outcome, scope, and non-negotiable constraints in its `Task` section.
|
|
|
70
81
|
| `pnpm verify` | Registry, i18n, RTL, governance, and secret audits |
|
|
71
82
|
| `pnpm check` | Full local release gate |
|
|
72
83
|
|
|
84
|
+
## Automated npm publishing
|
|
85
|
+
|
|
86
|
+
The repository uses npm Trusted Publishing with GitHub Actions OIDC. After a change
|
|
87
|
+
is merged into `main`, the `ci` workflow must complete successfully before
|
|
88
|
+
[`publish.yml`](.github/workflows/publish.yml) publishes the package.
|
|
89
|
+
|
|
90
|
+
- Publishing uses short-lived OIDC credentials; no npm token is stored in GitHub.
|
|
91
|
+
- npm provenance attestations are unavailable while this GitHub repository is private.
|
|
92
|
+
- A version already present on npm is skipped, so a re-run cannot overwrite it.
|
|
93
|
+
- Bump the root `package.json` version before merging a release-worthy change.
|
|
94
|
+
- `workflow_dispatch` supports an intentional manual re-run from `main`.
|
|
95
|
+
|
|
96
|
+
The optional dependency-review job is disabled unless the repository variable
|
|
97
|
+
`ENABLE_DEPENDENCY_REVIEW` is set to `true` after Dependency Graph and GitHub
|
|
98
|
+
Advanced Security are enabled in repository settings.
|
|
99
|
+
|
|
100
|
+
CodeQL is similarly opt-in through `ENABLE_CODEQL=true`: first enable **Code
|
|
101
|
+
Security** for this private repository in GitHub’s security settings, then set the
|
|
102
|
+
variable. This avoids a misleading failed workflow when GitHub cannot accept SARIF
|
|
103
|
+
uploads. The workflows use Node 24; the runner’s Node 20 deprecation notice does not
|
|
104
|
+
require a compatibility override.
|
|
105
|
+
|
|
73
106
|
## Architecture
|
|
74
107
|
|
|
75
108
|
```text
|
|
@@ -84,8 +117,13 @@ packages/icons Icon policy and directional mirroring
|
|
|
84
117
|
packages/registry Canonical component catalog and install metadata
|
|
85
118
|
```
|
|
86
119
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
120
|
+
## Documentation
|
|
121
|
+
|
|
122
|
+
- [`docs/design-system/README.md`](docs/design-system/README.md): focused
|
|
123
|
+
documentation index
|
|
124
|
+
- [`docs/design-system/agent-guide.md`](docs/design-system/agent-guide.md): agent
|
|
125
|
+
workflow and verification expectations
|
|
126
|
+
- [`docs/design-system/gpt-agent-prompt.md`](docs/design-system/gpt-agent-prompt.md):
|
|
127
|
+
ready-to-paste GPT task wrapper
|
|
128
|
+
- [`packages/registry/manifest.json`](packages/registry/manifest.json): canonical
|
|
129
|
+
registry catalog and component contracts
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
PieChart,
|
|
24
24
|
RadarChart,
|
|
25
25
|
} from '@noe/data-ui';
|
|
26
|
-
import { ChevronIcon,
|
|
26
|
+
import { ChevronIcon, iconsaxFreeSource, NoeMark } from '@noe/icons';
|
|
27
27
|
import {
|
|
28
28
|
AboutPage,
|
|
29
29
|
Alert,
|
|
@@ -314,7 +314,7 @@ export const catalogEntries: readonly CatalogEntry[] = [
|
|
|
314
314
|
status: statusOf('icon-system'),
|
|
315
315
|
name: 'Icon System',
|
|
316
316
|
description:
|
|
317
|
-
'
|
|
317
|
+
'Iconsax Free interface icons delivered through the NOE wrapper with an explicit directional-mirroring policy for RTL layouts.',
|
|
318
318
|
package: '@noe/icons',
|
|
319
319
|
registryItem: null,
|
|
320
320
|
preview: () => (
|
|
@@ -336,7 +336,7 @@ export const catalogEntries: readonly CatalogEntry[] = [
|
|
|
336
336
|
</div>
|
|
337
337
|
</div>
|
|
338
338
|
<span className="font-mono text-[0.6875rem] text-muted-foreground">
|
|
339
|
-
{
|
|
339
|
+
{iconsaxFreeSource.provider} / {iconsaxFreeSource.package}
|
|
340
340
|
</span>
|
|
341
341
|
</div>
|
|
342
342
|
),
|
|
@@ -3,7 +3,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
|
3
3
|
import {
|
|
4
4
|
ChevronIcon,
|
|
5
5
|
iconDirectionPolicy,
|
|
6
|
-
|
|
6
|
+
iconsaxFreeSource,
|
|
7
7
|
NoeMark,
|
|
8
8
|
} from '../../../../packages/icons/src/index';
|
|
9
9
|
|
|
@@ -31,7 +31,8 @@ function IconGallery() {
|
|
|
31
31
|
<strong>Never mirror:</strong> {iconDirectionPolicy.neverMirror.join(', ')}
|
|
32
32
|
</div>
|
|
33
33
|
<div>
|
|
34
|
-
<strong>Interface source:</strong> {
|
|
34
|
+
<strong>Interface source:</strong> {iconsaxFreeSource.provider} /{' '}
|
|
35
|
+
{iconsaxFreeSource.package}
|
|
35
36
|
</div>
|
|
36
37
|
</div>
|
|
37
38
|
);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
# ADR 0006:
|
|
1
|
+
# ADR 0006: Iconsax Free icon policy
|
|
2
2
|
|
|
3
3
|
Status: Accepted
|
|
4
4
|
|
|
5
|
-
Earlier proposals locked Lucide as the product icon family. The repository
|
|
5
|
+
Earlier proposals locked Lucide as the product icon family. The repository now adopts Iconsax Free through the audited `@noe/icons` wrapper (`packages/icons/src/iconsax.tsx`, `iconsaxFreeSource`), with `NoeMark` registered as a separate NOE brand asset.
|
|
6
6
|
|
|
7
|
-
Decision: the product uses exactly one icon family,
|
|
7
|
+
Decision: the product uses exactly one provider icon family, Iconsax Free, plus NOE brand marks. Applications import from `@noe/icons`, never from `iconsax-react`. Directional icons use logical directions (`forward`/`backward`) and mirror only through the scoped `.noe-logical-icon` class under `iconDirectionPolicy`; brand marks never mirror. Lucide is not adopted and icon families must not be mixed.
|
|
8
8
|
|
|
9
|
-
Consequences: every added third-party icon records provider and source metadata in the style of `
|
|
9
|
+
Consequences: every added third-party icon records provider and source metadata in the style of `iconsaxFreeSource`; `NoeMark` is a NOE brand asset and carries no provider metadata; Iconsax Free's MIT license is part of adoption review; icon-related documentation and stories must reflect the mirroring policy rather than physical left/right semantics.
|
|
@@ -42,5 +42,5 @@ deployment gate in ADR 0007.
|
|
|
42
42
|
- No custom MCP server while the official shadcn MCP satisfies registry discovery.
|
|
43
43
|
- Registry publication and package release are separate operational gates (ADR 0007).
|
|
44
44
|
- `@noe/*` is the single package namespace for packages, registry items, diagrams and agent instructions (ADR 0005).
|
|
45
|
-
-
|
|
45
|
+
- Iconsax Free is the only third-party icon family, exposed through `@noe/icons`; NOE brand marks never mirror (ADR 0006).
|
|
46
46
|
- Capability libraries stay behind adapters; NOE tokens and `@noe/*` own product markup, style, RTL behavior and approval states (ADR 0008).
|
|
@@ -14,24 +14,37 @@ entry and push to the default branch:
|
|
|
14
14
|
| `ci / build-and-verify` | Workspace build + registry/i18n/RTL/governance audits |
|
|
15
15
|
| `ci / e2e-critical` | Playwright matrix: chromium en/fa/pseudo + firefox/webkit smoke |
|
|
16
16
|
|
|
17
|
-
The `security` workflow
|
|
18
|
-
|
|
17
|
+
The `security` workflow contains opt-in CodeQL analysis and dependency review.
|
|
18
|
+
For this private repository, GitHub must first have Code Security and Dependency
|
|
19
|
+
Graph/GitHub Advanced Security enabled. Set `ENABLE_CODEQL=true` and
|
|
20
|
+
`ENABLE_DEPENDENCY_REVIEW=true` repository variables only after the corresponding
|
|
21
|
+
GitHub features are available.
|
|
19
22
|
|
|
20
23
|
## Security controls
|
|
21
24
|
|
|
22
|
-
- Minimal `GITHUB_TOKEN` permissions
|
|
23
|
-
|
|
25
|
+
- Minimal `GITHUB_TOKEN` permissions. CodeQL receives `actions: read`,
|
|
26
|
+
`contents: read`, and `security-events: write`; dependency review receives only
|
|
27
|
+
`contents: read`.
|
|
24
28
|
- All third-party actions pinned to immutable full commit SHAs with the
|
|
25
29
|
upstream version documented beside them; Dependabot keeps SHAs updated.
|
|
26
30
|
- No secrets in logs; no privileged secrets available to untrusted PR code.
|
|
27
|
-
- Concurrency groups cancel superseded PR runs
|
|
28
|
-
|
|
31
|
+
- Concurrency groups cancel superseded PR runs. The npm publishing workflow is
|
|
32
|
+
serialized and is never cancelled mid-publish.
|
|
29
33
|
|
|
30
34
|
## Artifacts
|
|
31
35
|
|
|
32
36
|
Storybook static build, dist outputs, Playwright reports/traces/screenshots are
|
|
33
37
|
uploaded with 14-day retention on relevant outcomes.
|
|
34
38
|
|
|
39
|
+
## Publishing
|
|
40
|
+
|
|
41
|
+
`publish.yml` runs after a successful `ci` workflow on a push to `main`. It checks
|
|
42
|
+
out the verified commit, builds it, verifies the npm tarball, and publishes only if
|
|
43
|
+
the root `package.json` version is not already on npm. npm Trusted Publishing uses
|
|
44
|
+
GitHub Actions OIDC, so the workflow stores no npm token.
|
|
45
|
+
|
|
46
|
+
Manual `workflow_dispatch` publishing is restricted to `main`.
|
|
47
|
+
|
|
35
48
|
## Deployment
|
|
36
49
|
|
|
37
50
|
No deployment provider is invented. Storybook artifacts are produced on every
|
|
@@ -43,4 +56,6 @@ long-lived credentials.
|
|
|
43
56
|
|
|
44
57
|
Recommended required status checks once branch protection is active:
|
|
45
58
|
`ci / quality`, `ci / unit`, `ci / component`, `ci / storybook`,
|
|
46
|
-
`ci / build-and-verify`, `ci / e2e-critical
|
|
59
|
+
`ci / build-and-verify`, `ci / e2e-critical`. Add `security / codeql` only after
|
|
60
|
+
Code Security is enabled and `ENABLE_CODEQL=true`; otherwise it is intentionally
|
|
61
|
+
skipped rather than reported as a false failure.
|
|
@@ -16,7 +16,7 @@ Use logical margin, padding, inset, border and text alignment. Maintain semantic
|
|
|
16
16
|
|
|
17
17
|
Forward/backward, next/previous, chevron, indent/outdent and panel-opening icons may mirror. Brand, play, pause, upload, download, search, settings, check, close, charts, code and service logos do not mirror automatically.
|
|
18
18
|
|
|
19
|
-
The shared `ChevronIcon` uses `
|
|
19
|
+
The shared `ChevronIcon` uses the `iconsaxFreeSource` wrapper and opts into this policy through the scoped `.noe-logical-icon` class. `NoeMark` remains a NOE brand asset and never mirrors.
|
|
20
20
|
|
|
21
21
|
## Mixed content
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -173,7 +173,7 @@
|
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
/* Logical
|
|
176
|
+
/* Logical Iconsax chevrons opt into direction-aware motion. Brand and
|
|
177
177
|
* semantic glyphs do not receive a global transform. */
|
|
178
178
|
.noe-logical-icon[data-logical-direction="backward"],
|
|
179
179
|
[dir="rtl"] .noe-logical-icon[data-logical-direction="forward"] {
|
|
@@ -145,8 +145,8 @@
|
|
|
145
145
|
--noe-color-muted-foreground: var(--noe-primitive-color-ink-500);
|
|
146
146
|
--noe-color-border: rgba(23, 25, 24, 0.16);
|
|
147
147
|
--noe-color-border-strong: var(--noe-primitive-color-ink-300);
|
|
148
|
-
--noe-color-accent: var(--noe-primitive-color-
|
|
149
|
-
--noe-color-accent-hover: var(--noe-primitive-color-
|
|
148
|
+
--noe-color-accent: var(--noe-primitive-color-cyan-500);
|
|
149
|
+
--noe-color-accent-hover: var(--noe-primitive-color-cyan-500);
|
|
150
150
|
--noe-color-accent-foreground: var(--noe-primitive-color-paper-0);
|
|
151
151
|
--noe-color-signal: var(--noe-primitive-color-coral-500);
|
|
152
152
|
--noe-color-success: var(--noe-primitive-color-green-600);
|
|
@@ -175,8 +175,8 @@
|
|
|
175
175
|
--noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
|
|
176
176
|
--noe-color-border: rgba(246, 244, 242, 0.14);
|
|
177
177
|
--noe-color-border-strong: var(--noe-primitive-color-ink-600);
|
|
178
|
-
--noe-color-accent: var(--noe-primitive-color-
|
|
179
|
-
--noe-color-accent-hover: var(--noe-primitive-color-
|
|
178
|
+
--noe-color-accent: var(--noe-primitive-color-cyan-400);
|
|
179
|
+
--noe-color-accent-hover: var(--noe-primitive-color-cyan-400);
|
|
180
180
|
--noe-color-accent-foreground: var(--noe-primitive-color-ink-950);
|
|
181
181
|
--noe-color-signal: var(--noe-primitive-color-coral-400);
|
|
182
182
|
--noe-color-success: var(--noe-primitive-color-green-500);
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
--noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
|
|
207
207
|
--noe-color-border: rgba(246, 244, 242, 0.14);
|
|
208
208
|
--noe-color-border-strong: var(--noe-primitive-color-ink-600);
|
|
209
|
-
--noe-color-accent: var(--noe-primitive-color-
|
|
210
|
-
--noe-color-accent-hover: var(--noe-primitive-color-
|
|
209
|
+
--noe-color-accent: var(--noe-primitive-color-cyan-400);
|
|
210
|
+
--noe-color-accent-hover: var(--noe-primitive-color-cyan-400);
|
|
211
211
|
--noe-color-accent-foreground: var(--noe-primitive-color-ink-950);
|
|
212
212
|
--noe-color-signal: var(--noe-primitive-color-coral-400);
|
|
213
213
|
--noe-color-success: var(--noe-primitive-color-green-500);
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
"mutedForeground": "{primitive.color.ink.500}",
|
|
112
112
|
"border": "rgba(23, 25, 24, 0.16)",
|
|
113
113
|
"borderStrong": "{primitive.color.ink.300}",
|
|
114
|
-
"accent": "{primitive.color.
|
|
115
|
-
"accentHover": "{primitive.color.
|
|
114
|
+
"accent": "{primitive.color.cyan.500}",
|
|
115
|
+
"accentHover": "{primitive.color.cyan.500}",
|
|
116
116
|
"accentForeground": "{primitive.color.paper.0}",
|
|
117
117
|
"signal": "{primitive.color.coral.500}",
|
|
118
118
|
"success": "{primitive.color.green.600}",
|
|
@@ -136,8 +136,8 @@
|
|
|
136
136
|
"mutedForeground": "{primitive.color.ink.400}",
|
|
137
137
|
"border": "rgba(246, 244, 242, 0.14)",
|
|
138
138
|
"borderStrong": "{primitive.color.ink.600}",
|
|
139
|
-
"accent": "{primitive.color.
|
|
140
|
-
"accentHover": "{primitive.color.
|
|
139
|
+
"accent": "{primitive.color.cyan.400}",
|
|
140
|
+
"accentHover": "{primitive.color.cyan.400}",
|
|
141
141
|
"accentForeground": "{primitive.color.ink.950}",
|
|
142
142
|
"signal": "{primitive.color.coral.400}",
|
|
143
143
|
"success": "{primitive.color.green.500}",
|
|
@@ -112,8 +112,8 @@ export const tokens = {
|
|
|
112
112
|
"mutedForeground": "{primitive.color.ink.500}",
|
|
113
113
|
"border": "rgba(23, 25, 24, 0.16)",
|
|
114
114
|
"borderStrong": "{primitive.color.ink.300}",
|
|
115
|
-
"accent": "{primitive.color.
|
|
116
|
-
"accentHover": "{primitive.color.
|
|
115
|
+
"accent": "{primitive.color.cyan.500}",
|
|
116
|
+
"accentHover": "{primitive.color.cyan.500}",
|
|
117
117
|
"accentForeground": "{primitive.color.paper.0}",
|
|
118
118
|
"signal": "{primitive.color.coral.500}",
|
|
119
119
|
"success": "{primitive.color.green.600}",
|
|
@@ -137,8 +137,8 @@ export const tokens = {
|
|
|
137
137
|
"mutedForeground": "{primitive.color.ink.400}",
|
|
138
138
|
"border": "rgba(246, 244, 242, 0.14)",
|
|
139
139
|
"borderStrong": "{primitive.color.ink.600}",
|
|
140
|
-
"accent": "{primitive.color.
|
|
141
|
-
"accentHover": "{primitive.color.
|
|
140
|
+
"accent": "{primitive.color.cyan.400}",
|
|
141
|
+
"accentHover": "{primitive.color.cyan.400}",
|
|
142
142
|
"accentForeground": "{primitive.color.ink.950}",
|
|
143
143
|
"signal": "{primitive.color.coral.400}",
|
|
144
144
|
"success": "{primitive.color.green.500}",
|
|
@@ -32,10 +32,10 @@ export const iconProvenance = Object.freeze({
|
|
|
32
32
|
license: 'Partner brand — check before publishing',
|
|
33
33
|
asset: 'integration icons',
|
|
34
34
|
},
|
|
35
|
-
|
|
36
|
-
source: '
|
|
37
|
-
license: '
|
|
38
|
-
asset: '
|
|
35
|
+
iconsaxFree: {
|
|
36
|
+
source: 'Iconsax Free',
|
|
37
|
+
license: 'MIT — https://github.com/rendinjast/iconsax-react',
|
|
38
|
+
asset: 'iconsax-react wrapper',
|
|
39
39
|
},
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import type * as React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Add as IconsaxAdd,
|
|
4
|
+
ArchiveBook as IconsaxArchiveBook,
|
|
5
|
+
ArrowDown as IconsaxArrowDown,
|
|
6
|
+
ArrowDown2 as IconsaxArrowDown2,
|
|
7
|
+
ArrowLeft2 as IconsaxArrowLeft2,
|
|
8
|
+
ArrowRight2 as IconsaxArrowRight2,
|
|
9
|
+
ArrowUp as IconsaxArrowUp,
|
|
10
|
+
ArrowUp2 as IconsaxArrowUp2,
|
|
11
|
+
AttachCircle as IconsaxAttachCircle,
|
|
12
|
+
BoxSearch as IconsaxBoxSearch,
|
|
13
|
+
Briefcase as IconsaxBriefcase,
|
|
14
|
+
Calendar as IconsaxCalendar,
|
|
15
|
+
Call as IconsaxCall,
|
|
16
|
+
CallCalling as IconsaxCallCalling,
|
|
17
|
+
CallRemove as IconsaxCallRemove,
|
|
18
|
+
ChartSquare as IconsaxChartSquare,
|
|
19
|
+
CloseCircle as IconsaxCloseCircle,
|
|
20
|
+
Copy as IconsaxCopy,
|
|
21
|
+
Cpu as IconsaxCpu,
|
|
22
|
+
Dislike as IconsaxDislike,
|
|
23
|
+
DocumentText as IconsaxDocumentText,
|
|
24
|
+
DocumentUpload as IconsaxDocumentUpload,
|
|
25
|
+
Edit2 as IconsaxEdit2,
|
|
26
|
+
HambergerMenu as IconsaxHambergerMenu,
|
|
27
|
+
Judge as IconsaxJudge,
|
|
28
|
+
Like1 as IconsaxLike1,
|
|
29
|
+
Link2 as IconsaxLink2,
|
|
30
|
+
Lock as IconsaxLock,
|
|
31
|
+
LogoutCurve as IconsaxLogoutCurve,
|
|
32
|
+
MagicStar as IconsaxMagicStar,
|
|
33
|
+
MessageText as IconsaxMessageText,
|
|
34
|
+
Messages3 as IconsaxMessages3,
|
|
35
|
+
MoneyRecive as IconsaxMoneyRecive,
|
|
36
|
+
More as IconsaxMore,
|
|
37
|
+
Paperclip2 as IconsaxPaperclip2,
|
|
38
|
+
Profile2User as IconsaxProfile2User,
|
|
39
|
+
ProfileCircle as IconsaxProfileCircle,
|
|
40
|
+
ReceiptSearch as IconsaxReceiptSearch,
|
|
41
|
+
SecurityUser as IconsaxSecurityUser,
|
|
42
|
+
Setting2 as IconsaxSetting2,
|
|
43
|
+
Star1 as IconsaxStar1,
|
|
44
|
+
StopCircle as IconsaxStopCircle,
|
|
45
|
+
TickCircle as IconsaxTickCircle,
|
|
46
|
+
TicketDiscount as IconsaxTicketDiscount,
|
|
47
|
+
Trash as IconsaxTrash,
|
|
48
|
+
User as IconsaxUser,
|
|
49
|
+
UserSearch as IconsaxUserSearch,
|
|
50
|
+
Verify as IconsaxVerify,
|
|
51
|
+
Wallet3 as IconsaxWallet3,
|
|
52
|
+
WalletMoney as IconsaxWalletMoney,
|
|
53
|
+
Warning2 as IconsaxWarning2,
|
|
54
|
+
type Icon as IconsaxIcon,
|
|
55
|
+
type IconProps as IconsaxIconProps,
|
|
56
|
+
} from 'iconsax-react';
|
|
57
|
+
|
|
58
|
+
export type LogicalIconDirection = 'forward' | 'backward';
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The only product icon provider. Iconsax Free is MIT licensed; consuming
|
|
62
|
+
* applications import from @noe/icons, never from the provider directly.
|
|
63
|
+
*/
|
|
64
|
+
export const iconsaxFreeSource = Object.freeze({
|
|
65
|
+
provider: 'Iconsax Free',
|
|
66
|
+
package: 'iconsax-react',
|
|
67
|
+
version: '0.0.8',
|
|
68
|
+
license: 'MIT',
|
|
69
|
+
url: 'https://github.com/rendinjast/iconsax-react',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
export type NoeIconProps = IconsaxIconProps & {
|
|
73
|
+
/** Makes a meaningful icon available to assistive technology. */
|
|
74
|
+
label?: string;
|
|
75
|
+
/** Applies NOE's scoped, logical RTL mirroring policy. */
|
|
76
|
+
direction?: LogicalIconDirection;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
type NoeSvgProps = Omit<React.SVGProps<SVGSVGElement>, 'ref'>;
|
|
80
|
+
|
|
81
|
+
function createNoeIcon(Icon: IconsaxIcon, defaultDirection?: LogicalIconDirection) {
|
|
82
|
+
function NoeIcon({ label, direction = defaultDirection, className, ...props }: NoeIconProps) {
|
|
83
|
+
const directionalClass = direction ? 'noe-logical-icon' : undefined;
|
|
84
|
+
const resolvedClassName = [directionalClass, className].filter(Boolean).join(' ') || undefined;
|
|
85
|
+
const accessibilityProps = label
|
|
86
|
+
? { role: 'img' as const, 'aria-label': label }
|
|
87
|
+
: { 'aria-hidden': true as const, focusable: false };
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<Icon
|
|
91
|
+
{...props}
|
|
92
|
+
{...accessibilityProps}
|
|
93
|
+
className={resolvedClassName}
|
|
94
|
+
data-icon-source="iconsax-free"
|
|
95
|
+
data-logical-direction={direction}
|
|
96
|
+
/>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return NoeIcon;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Semantic navigation icons. Prefer these over physical arrow aliases. */
|
|
104
|
+
export const ForwardIcon = createNoeIcon(IconsaxArrowRight2, 'forward');
|
|
105
|
+
export const BackwardIcon = createNoeIcon(IconsaxArrowRight2, 'backward');
|
|
106
|
+
export const ChevronIcon = ForwardIcon;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Application icon catalog. These compatibility names are provider-free at the
|
|
110
|
+
* call site; new code should use semantic names such as ForwardIcon/BackwardIcon
|
|
111
|
+
* whenever the meaning is navigation rather than a physical direction.
|
|
112
|
+
*/
|
|
113
|
+
export const Add = createNoeIcon(IconsaxAdd);
|
|
114
|
+
export const ArchiveBook = createNoeIcon(IconsaxArchiveBook);
|
|
115
|
+
export const ArrowDown = createNoeIcon(IconsaxArrowDown);
|
|
116
|
+
export const ArrowDown2 = createNoeIcon(IconsaxArrowDown2);
|
|
117
|
+
export const ArrowLeft2 = createNoeIcon(IconsaxArrowLeft2);
|
|
118
|
+
export const ArrowRight2 = createNoeIcon(IconsaxArrowRight2);
|
|
119
|
+
export const ArrowUp = createNoeIcon(IconsaxArrowUp);
|
|
120
|
+
export const ArrowUp2 = createNoeIcon(IconsaxArrowUp2);
|
|
121
|
+
export const AttachCircle = createNoeIcon(IconsaxAttachCircle);
|
|
122
|
+
export const BoxSearch = createNoeIcon(IconsaxBoxSearch);
|
|
123
|
+
export const Briefcase = createNoeIcon(IconsaxBriefcase);
|
|
124
|
+
export const Calendar = createNoeIcon(IconsaxCalendar);
|
|
125
|
+
export const Call = createNoeIcon(IconsaxCall);
|
|
126
|
+
export const CallCalling = createNoeIcon(IconsaxCallCalling);
|
|
127
|
+
export const CallRemove = createNoeIcon(IconsaxCallRemove);
|
|
128
|
+
export const ChartSquare = createNoeIcon(IconsaxChartSquare);
|
|
129
|
+
export const CloseCircle = createNoeIcon(IconsaxCloseCircle);
|
|
130
|
+
export const Copy = createNoeIcon(IconsaxCopy);
|
|
131
|
+
export const Cpu = createNoeIcon(IconsaxCpu);
|
|
132
|
+
export const Dislike = createNoeIcon(IconsaxDislike);
|
|
133
|
+
export const DocumentText = createNoeIcon(IconsaxDocumentText);
|
|
134
|
+
export const DocumentUpload = createNoeIcon(IconsaxDocumentUpload);
|
|
135
|
+
export const Edit2 = createNoeIcon(IconsaxEdit2);
|
|
136
|
+
export const HambergerMenu = createNoeIcon(IconsaxHambergerMenu);
|
|
137
|
+
export const Judge = createNoeIcon(IconsaxJudge);
|
|
138
|
+
export const Like1 = createNoeIcon(IconsaxLike1);
|
|
139
|
+
export const Link2 = createNoeIcon(IconsaxLink2);
|
|
140
|
+
export const Lock = createNoeIcon(IconsaxLock);
|
|
141
|
+
export const LogoutCurve = createNoeIcon(IconsaxLogoutCurve);
|
|
142
|
+
export const MagicStar = createNoeIcon(IconsaxMagicStar);
|
|
143
|
+
export const MessageText = createNoeIcon(IconsaxMessageText);
|
|
144
|
+
export const Messages3 = createNoeIcon(IconsaxMessages3);
|
|
145
|
+
export const MoneyRecive = createNoeIcon(IconsaxMoneyRecive);
|
|
146
|
+
export const More = createNoeIcon(IconsaxMore);
|
|
147
|
+
export const Paperclip2 = createNoeIcon(IconsaxPaperclip2);
|
|
148
|
+
export const Profile2User = createNoeIcon(IconsaxProfile2User);
|
|
149
|
+
export const ProfileCircle = createNoeIcon(IconsaxProfileCircle);
|
|
150
|
+
export const ReceiptSearch = createNoeIcon(IconsaxReceiptSearch);
|
|
151
|
+
export const SecurityUser = createNoeIcon(IconsaxSecurityUser);
|
|
152
|
+
export const Setting2 = createNoeIcon(IconsaxSetting2);
|
|
153
|
+
export const Star1 = createNoeIcon(IconsaxStar1);
|
|
154
|
+
export const StopCircle = createNoeIcon(IconsaxStopCircle);
|
|
155
|
+
export const TickCircle = createNoeIcon(IconsaxTickCircle);
|
|
156
|
+
export const TicketDiscount = createNoeIcon(IconsaxTicketDiscount);
|
|
157
|
+
export const Trash = createNoeIcon(IconsaxTrash);
|
|
158
|
+
export const User = createNoeIcon(IconsaxUser);
|
|
159
|
+
export const UserSearch = createNoeIcon(IconsaxUserSearch);
|
|
160
|
+
export const Verify = createNoeIcon(IconsaxVerify);
|
|
161
|
+
export const Wallet3 = createNoeIcon(IconsaxWallet3);
|
|
162
|
+
export const WalletMoney = createNoeIcon(IconsaxWalletMoney);
|
|
163
|
+
export const Warning2 = createNoeIcon(IconsaxWarning2);
|
|
164
|
+
|
|
165
|
+
export function NoeMark(props: NoeSvgProps) {
|
|
166
|
+
return (
|
|
167
|
+
<svg viewBox="0 0 32 32" role="img" aria-label="NOEcosystem" {...props}>
|
|
168
|
+
<circle cx="16" cy="16" r="12" fill="none" stroke="currentColor" strokeWidth="2" />
|
|
169
|
+
<circle cx="16" cy="16" r="3" fill="currentColor" />
|
|
170
|
+
<path d="M4 16h9M19 16h9M16 4v9M16 19v9" stroke="currentColor" strokeWidth="2" />
|
|
171
|
+
</svg>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export function SunIcon(props: NoeSvgProps) {
|
|
176
|
+
return (
|
|
177
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" {...props}>
|
|
178
|
+
<g className="noe-theme-toggle__sun">
|
|
179
|
+
<circle
|
|
180
|
+
cx="12"
|
|
181
|
+
cy="12"
|
|
182
|
+
r="3.75"
|
|
183
|
+
fill="none"
|
|
184
|
+
stroke="currentColor"
|
|
185
|
+
strokeWidth="1.6"
|
|
186
|
+
strokeLinecap="round"
|
|
187
|
+
strokeLinejoin="round"
|
|
188
|
+
/>
|
|
189
|
+
<path
|
|
190
|
+
d="M12 2.25v2.1M12 19.65v2.1M2.25 12h2.1M19.65 12h2.1M5.1 5.1l1.48 1.48M17.42 17.42l1.48 1.48M18.9 5.1l-1.48 1.48M6.58 17.42 5.1 18.9"
|
|
191
|
+
fill="none"
|
|
192
|
+
stroke="currentColor"
|
|
193
|
+
strokeWidth="1.6"
|
|
194
|
+
strokeLinecap="round"
|
|
195
|
+
strokeLinejoin="round"
|
|
196
|
+
/>
|
|
197
|
+
</g>
|
|
198
|
+
</svg>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export function MoonIcon(props: NoeSvgProps) {
|
|
203
|
+
return (
|
|
204
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" {...props}>
|
|
205
|
+
<path
|
|
206
|
+
className="noe-theme-toggle__moon"
|
|
207
|
+
d="M20.2 15.25A8.25 8.25 0 0 1 8.75 3.8a8.26 8.26 0 1 0 11.45 11.45Z"
|
|
208
|
+
fill="none"
|
|
209
|
+
stroke="currentColor"
|
|
210
|
+
strokeWidth="1.6"
|
|
211
|
+
strokeLinecap="round"
|
|
212
|
+
strokeLinejoin="round"
|
|
213
|
+
/>
|
|
214
|
+
</svg>
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export const iconDirectionPolicy = Object.freeze({
|
|
219
|
+
mirror: ['forward', 'backward', 'next', 'previous', 'indent', 'outdent', 'panel-open'],
|
|
220
|
+
neverMirror: [
|
|
221
|
+
'brand',
|
|
222
|
+
'play',
|
|
223
|
+
'pause',
|
|
224
|
+
'download',
|
|
225
|
+
'upload',
|
|
226
|
+
'search',
|
|
227
|
+
'settings',
|
|
228
|
+
'check',
|
|
229
|
+
'close',
|
|
230
|
+
'chart',
|
|
231
|
+
'code',
|
|
232
|
+
],
|
|
233
|
+
});
|
|
@@ -1,103 +1 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export type LogicalIconDirection = 'forward' | 'backward';
|
|
4
|
-
|
|
5
|
-
export const icons8Source = Object.freeze({
|
|
6
|
-
provider: 'Icons8',
|
|
7
|
-
asset: 'chevronRight.svg',
|
|
8
|
-
url: 'https://maxst.icons8.com/vue-static/icon/svg/chevronRight.svg',
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
export function NoeMark(props: React.SVGProps<SVGSVGElement>) {
|
|
12
|
-
return (
|
|
13
|
-
<svg viewBox="0 0 32 32" role="img" aria-label="NOEcosystem" {...props}>
|
|
14
|
-
<circle cx="16" cy="16" r="12" fill="none" stroke="currentColor" strokeWidth="2" />
|
|
15
|
-
<circle cx="16" cy="16" r="3" fill="currentColor" />
|
|
16
|
-
<path d="M4 16h9M19 16h9M16 4v9M16 19v9" stroke="currentColor" strokeWidth="2" />
|
|
17
|
-
</svg>
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function ChevronIcon({
|
|
22
|
-
direction = 'forward',
|
|
23
|
-
className,
|
|
24
|
-
...props
|
|
25
|
-
}: React.SVGProps<SVGSVGElement> & { direction?: LogicalIconDirection }) {
|
|
26
|
-
return (
|
|
27
|
-
<svg
|
|
28
|
-
aria-hidden="true"
|
|
29
|
-
className={['noe-logical-icon', className].filter(Boolean).join(' ')}
|
|
30
|
-
data-icon-source="icons8"
|
|
31
|
-
data-logical-direction={direction}
|
|
32
|
-
viewBox="0 0 16 16"
|
|
33
|
-
{...props}
|
|
34
|
-
>
|
|
35
|
-
<path
|
|
36
|
-
d="M6.71875 3.5L6 4.20312L9.66016 7.97656L6 11.7539L6.71875 12.4492L11.0586 7.97656L6.71875 3.5Z"
|
|
37
|
-
fill="currentColor"
|
|
38
|
-
/>
|
|
39
|
-
</svg>
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function SunIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
44
|
-
return (
|
|
45
|
-
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" {...props}>
|
|
46
|
-
<g className="noe-theme-toggle__sun">
|
|
47
|
-
<circle
|
|
48
|
-
cx="12"
|
|
49
|
-
cy="12"
|
|
50
|
-
r="3.75"
|
|
51
|
-
fill="none"
|
|
52
|
-
stroke="currentColor"
|
|
53
|
-
strokeWidth="1.6"
|
|
54
|
-
strokeLinecap="round"
|
|
55
|
-
strokeLinejoin="round"
|
|
56
|
-
/>
|
|
57
|
-
<path
|
|
58
|
-
d="M12 2.25v2.1M12 19.65v2.1M2.25 12h2.1M19.65 12h2.1M5.1 5.1l1.48 1.48M17.42 17.42l1.48 1.48M18.9 5.1l-1.48 1.48M6.58 17.42 5.1 18.9"
|
|
59
|
-
fill="none"
|
|
60
|
-
stroke="currentColor"
|
|
61
|
-
strokeWidth="1.6"
|
|
62
|
-
strokeLinecap="round"
|
|
63
|
-
strokeLinejoin="round"
|
|
64
|
-
/>
|
|
65
|
-
</g>
|
|
66
|
-
</svg>
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function MoonIcon(props: React.SVGProps<SVGSVGElement>) {
|
|
71
|
-
return (
|
|
72
|
-
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" {...props}>
|
|
73
|
-
<path
|
|
74
|
-
className="noe-theme-toggle__moon"
|
|
75
|
-
d="M20.2 15.25A8.25 8.25 0 0 1 8.75 3.8a8.26 8.26 0 1 0 11.45 11.45Z"
|
|
76
|
-
fill="none"
|
|
77
|
-
stroke="currentColor"
|
|
78
|
-
strokeWidth="1.6"
|
|
79
|
-
strokeLinecap="round"
|
|
80
|
-
strokeLinejoin="round"
|
|
81
|
-
/>
|
|
82
|
-
</svg>
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export const iconDirectionPolicy = Object.freeze({
|
|
87
|
-
mirror: ['forward', 'backward', 'next', 'previous', 'indent', 'outdent', 'panel-open'],
|
|
88
|
-
neverMirror: [
|
|
89
|
-
'brand',
|
|
90
|
-
'play',
|
|
91
|
-
'pause',
|
|
92
|
-
'download',
|
|
93
|
-
'upload',
|
|
94
|
-
'search',
|
|
95
|
-
'settings',
|
|
96
|
-
'check',
|
|
97
|
-
'close',
|
|
98
|
-
'chart',
|
|
99
|
-
'code',
|
|
100
|
-
'theme-sun',
|
|
101
|
-
'theme-moon',
|
|
102
|
-
],
|
|
103
|
-
});
|
|
1
|
+
export * from './iconsax';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://noecosystem.com/schema/component-audit.json",
|
|
3
|
-
"generatedNote": "Canonical NOE public-component inventory. status=available requires a public export, a Storybook story at storyId, and (where distributable) a registry entry. Do not mark an item available without all three. (derived from manifest.json)
|
|
3
|
+
"generatedNote": "Canonical NOE public-component inventory. status=available requires a public export, a Storybook story at storyId, and (where distributable) a registry entry. Do not mark an item available without all three. (derived from manifest.json)",
|
|
4
4
|
"items": [
|
|
5
5
|
{
|
|
6
6
|
"id": "bidi-text",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"manifestVersion": "1.0.0",
|
|
3
|
-
"generatedAt": "2026-08-
|
|
3
|
+
"generatedAt": "2026-08-29T08:49:53.284Z",
|
|
4
4
|
"generatedNote": "Canonical NOE manifest — single source of truth for catalog, audit, registry, stories, docs and agent search. Generated by pnpm gen:manifest; do not edit by hand for automated fields.",
|
|
5
5
|
"items": [
|
|
6
6
|
{
|
|
@@ -5,13 +5,24 @@
|
|
|
5
5
|
"title": "Calendar",
|
|
6
6
|
"description": "Calendar — Base / Pickers component. calendar provides base / pickers behavior with RTL, i18n and accessibility built-in.",
|
|
7
7
|
"registryDependencies": [],
|
|
8
|
-
"dependencies": [
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"@base-ui/react",
|
|
10
|
+
"class-variance-authority",
|
|
11
|
+
"clsx",
|
|
12
|
+
"iconsax-react",
|
|
13
|
+
"tailwind-merge"
|
|
14
|
+
],
|
|
9
15
|
"files": [
|
|
10
16
|
{
|
|
11
17
|
"path": "packages/icons/src/index.tsx",
|
|
12
18
|
"type": "registry:component",
|
|
13
19
|
"target": "packages/icons/src/index.tsx"
|
|
14
20
|
},
|
|
21
|
+
{
|
|
22
|
+
"path": "packages/icons/src/iconsax.tsx",
|
|
23
|
+
"type": "registry:component",
|
|
24
|
+
"target": "packages/icons/src/iconsax.tsx"
|
|
25
|
+
},
|
|
15
26
|
{
|
|
16
27
|
"path": "packages/ui/src/button.tsx",
|
|
17
28
|
"type": "registry:component",
|
|
@@ -5,13 +5,24 @@
|
|
|
5
5
|
"title": "Date Picker",
|
|
6
6
|
"description": "Date Picker — Base / Pickers component. date-picker provides base / pickers behavior with RTL, i18n and accessibility built-in.",
|
|
7
7
|
"registryDependencies": [],
|
|
8
|
-
"dependencies": [
|
|
8
|
+
"dependencies": [
|
|
9
|
+
"@base-ui/react",
|
|
10
|
+
"class-variance-authority",
|
|
11
|
+
"clsx",
|
|
12
|
+
"iconsax-react",
|
|
13
|
+
"tailwind-merge"
|
|
14
|
+
],
|
|
9
15
|
"files": [
|
|
10
16
|
{
|
|
11
17
|
"path": "packages/icons/src/index.tsx",
|
|
12
18
|
"type": "registry:component",
|
|
13
19
|
"target": "packages/icons/src/index.tsx"
|
|
14
20
|
},
|
|
21
|
+
{
|
|
22
|
+
"path": "packages/icons/src/iconsax.tsx",
|
|
23
|
+
"type": "registry:component",
|
|
24
|
+
"target": "packages/icons/src/iconsax.tsx"
|
|
25
|
+
},
|
|
15
26
|
{
|
|
16
27
|
"path": "packages/ui/src/button.tsx",
|
|
17
28
|
"type": "registry:component",
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"path": "packages/icons/src/index.tsx",
|
|
10
10
|
"type": "registry:component",
|
|
11
11
|
"target": "packages/icons/src/index.tsx"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"path": "packages/icons/src/iconsax.tsx",
|
|
15
|
+
"type": "registry:component",
|
|
16
|
+
"target": "packages/icons/src/iconsax.tsx"
|
|
12
17
|
}
|
|
13
18
|
],
|
|
14
19
|
"registryDependencies": [],
|
|
@@ -20,7 +25,7 @@
|
|
|
20
25
|
"install": "pnpm dlx shadcn@latest add @noe/icon-system",
|
|
21
26
|
"import": "import { ChevronIcon } from '@noe/icons'",
|
|
22
27
|
"requiredProviders": [],
|
|
23
|
-
"dependencies": [],
|
|
28
|
+
"dependencies": ["iconsax-react"],
|
|
24
29
|
"peerDependencies": ["react"],
|
|
25
30
|
"rtl": "required",
|
|
26
31
|
"i18n": {
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
"type": "registry:component",
|
|
11
11
|
"target": "packages/icons/src/index.tsx"
|
|
12
12
|
},
|
|
13
|
+
{
|
|
14
|
+
"path": "packages/icons/src/iconsax.tsx",
|
|
15
|
+
"type": "registry:component",
|
|
16
|
+
"target": "packages/icons/src/iconsax.tsx"
|
|
17
|
+
},
|
|
13
18
|
{
|
|
14
19
|
"path": "packages/ui/src/inline-cta.tsx",
|
|
15
20
|
"type": "registry:component",
|
|
@@ -22,7 +27,7 @@
|
|
|
22
27
|
}
|
|
23
28
|
],
|
|
24
29
|
"registryDependencies": [],
|
|
25
|
-
"dependencies": ["clsx", "tailwind-merge"],
|
|
30
|
+
"dependencies": ["clsx", "iconsax-react", "tailwind-merge"],
|
|
26
31
|
"status": "available",
|
|
27
32
|
"maturity": "beta",
|
|
28
33
|
"package": "@noe/ui",
|
|
@@ -15,6 +15,11 @@
|
|
|
15
15
|
"type": "registry:component",
|
|
16
16
|
"target": "packages/icons/src/index.tsx"
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"path": "packages/icons/src/iconsax.tsx",
|
|
20
|
+
"type": "registry:component",
|
|
21
|
+
"target": "packages/icons/src/iconsax.tsx"
|
|
22
|
+
},
|
|
18
23
|
{
|
|
19
24
|
"path": "packages/ui/src/button.tsx",
|
|
20
25
|
"type": "registry:component",
|
|
@@ -32,7 +37,13 @@
|
|
|
32
37
|
}
|
|
33
38
|
],
|
|
34
39
|
"registryDependencies": [],
|
|
35
|
-
"dependencies": [
|
|
40
|
+
"dependencies": [
|
|
41
|
+
"@base-ui/react",
|
|
42
|
+
"class-variance-authority",
|
|
43
|
+
"clsx",
|
|
44
|
+
"iconsax-react",
|
|
45
|
+
"tailwind-merge"
|
|
46
|
+
],
|
|
36
47
|
"status": "available",
|
|
37
48
|
"maturity": "beta",
|
|
38
49
|
"package": "@noe/data-ui",
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
1
|
import { expect, test } from 'vitest';
|
|
3
2
|
import { render } from 'vitest-browser-react';
|
|
4
3
|
|
|
@@ -13,13 +12,14 @@ function PaletteProbe({ storageKey }: { storageKey?: string }) {
|
|
|
13
12
|
<button type="button" onClick={() => setPalette('graphite' as never)}>
|
|
14
13
|
set graphite
|
|
15
14
|
</button>
|
|
16
|
-
{storageKey
|
|
15
|
+
{storageKey ? <span data-testid="storageKey">{storageKey}</span> : null}
|
|
17
16
|
</div>
|
|
18
17
|
);
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
test('default Graphite with no preference', async () => {
|
|
22
21
|
localStorage.clear();
|
|
22
|
+
// biome-ignore lint/suspicious/noDocumentCookie: Test setup must exercise the cookie fallback path.
|
|
23
23
|
document.cookie = 'noecosystem-palette=; Max-Age=0; path=/';
|
|
24
24
|
document.documentElement.removeAttribute('data-noe-palette');
|
|
25
25
|
const screen = await render(
|
|
@@ -45,6 +45,7 @@ test('valid stored preference restoration', async () => {
|
|
|
45
45
|
|
|
46
46
|
test('invalid storage and cookie fallback to Graphite', async () => {
|
|
47
47
|
localStorage.setItem('noecosystem-palette', 'invalid-palette');
|
|
48
|
+
// biome-ignore lint/suspicious/noDocumentCookie: This test needs an invalid cookie value for fallback coverage.
|
|
48
49
|
document.cookie = 'noecosystem-palette=invalid-palette; path=/';
|
|
49
50
|
const screen = await render(
|
|
50
51
|
<ThemeProvider>
|
|
@@ -10,6 +10,28 @@ const schemaPath = path.join(root, 'packages/registry/manifest.schema.json');
|
|
|
10
10
|
const audit = JSON.parse(await readFile(auditPath, 'utf8'));
|
|
11
11
|
const _schema = JSON.parse(await readFile(schemaPath, 'utf8'));
|
|
12
12
|
|
|
13
|
+
async function writeJsonIfChanged(filePath, value, { preserveGeneratedAt = false } = {}) {
|
|
14
|
+
let existingText = null;
|
|
15
|
+
let existingValue = null;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
existingText = await readFile(filePath, 'utf8');
|
|
19
|
+
existingValue = JSON.parse(existingText);
|
|
20
|
+
} catch {}
|
|
21
|
+
|
|
22
|
+
let output = value;
|
|
23
|
+
if (preserveGeneratedAt && existingValue?.generatedAt) {
|
|
24
|
+
const { generatedAt: _previousGeneratedAt, ...previousPayload } = existingValue;
|
|
25
|
+
const { generatedAt: _nextGeneratedAt, ...nextPayload } = value;
|
|
26
|
+
if (JSON.stringify(previousPayload) === JSON.stringify(nextPayload)) {
|
|
27
|
+
output = { ...value, generatedAt: existingValue.generatedAt };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nextText = `${JSON.stringify(output, null, 2)}\n`;
|
|
32
|
+
if (nextText !== existingText) await writeFile(filePath, nextText);
|
|
33
|
+
}
|
|
34
|
+
|
|
13
35
|
function toExportName(id) {
|
|
14
36
|
const map = {
|
|
15
37
|
'bidi-text': 'BidiText',
|
|
@@ -1045,14 +1067,15 @@ for (const item of cinematicEnrichments) {
|
|
|
1045
1067
|
if (!manifest.items.find((i) => i.id === item.id)) manifest.items.push(item);
|
|
1046
1068
|
}
|
|
1047
1069
|
|
|
1048
|
-
await
|
|
1070
|
+
await writeJsonIfChanged(manifestPath, manifest, { preserveGeneratedAt: true });
|
|
1049
1071
|
console.log(`manifest.json written: ${manifest.items.length} items`);
|
|
1050
1072
|
|
|
1051
1073
|
// Also generate derived files for backward compat
|
|
1052
1074
|
// 1. Update component-audit.json to be derived (keep minimal fields but now from manifest)
|
|
1053
1075
|
const auditDerived = {
|
|
1054
1076
|
$schema: audit.$schema,
|
|
1055
|
-
generatedNote:
|
|
1077
|
+
generatedNote:
|
|
1078
|
+
'Canonical NOE public-component inventory. status=available requires a public export, a Storybook story at storyId, and (where distributable) a registry entry. Do not mark an item available without all three. (derived from manifest.json)',
|
|
1056
1079
|
items: manifest.items
|
|
1057
1080
|
.filter((i) => !i.category.startsWith('Cinematic'))
|
|
1058
1081
|
.map((i) => ({
|
|
@@ -1064,7 +1087,7 @@ const auditDerived = {
|
|
|
1064
1087
|
registryItem: i.registryItem,
|
|
1065
1088
|
})),
|
|
1066
1089
|
};
|
|
1067
|
-
await
|
|
1090
|
+
await writeJsonIfChanged(auditPath, auditDerived);
|
|
1068
1091
|
console.log(`component-audit.json derived: ${auditDerived.items.length} items`);
|
|
1069
1092
|
|
|
1070
1093
|
// 2. Generate search index
|
|
@@ -1085,9 +1108,10 @@ const searchIndex = manifest.items.map((i) => ({
|
|
|
1085
1108
|
registryItem: i.registryItem,
|
|
1086
1109
|
maturity: i.maturity,
|
|
1087
1110
|
}));
|
|
1088
|
-
await
|
|
1111
|
+
await writeJsonIfChanged(
|
|
1089
1112
|
path.join(root, 'packages/registry/search-index.json'),
|
|
1090
|
-
|
|
1113
|
+
{ generatedAt: new Date().toISOString(), items: searchIndex },
|
|
1114
|
+
{ preserveGeneratedAt: true },
|
|
1091
1115
|
);
|
|
1092
1116
|
console.log(`search-index.json written`);
|
|
1093
1117
|
|
|
@@ -1107,47 +1131,41 @@ for (const item of registry.items) {
|
|
|
1107
1131
|
item.searchTerms = m.searchTerms;
|
|
1108
1132
|
}
|
|
1109
1133
|
}
|
|
1110
|
-
await
|
|
1134
|
+
await writeJsonIfChanged(registryPath, registry);
|
|
1111
1135
|
console.log(`registry.json enriched`);
|
|
1112
1136
|
|
|
1113
1137
|
// 4. Generate catalog index (JSON for agents)
|
|
1114
|
-
await
|
|
1138
|
+
await writeJsonIfChanged(
|
|
1115
1139
|
path.join(root, 'packages/registry/catalog-index.json'),
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
null,
|
|
1131
|
-
2,
|
|
1132
|
-
)}\n`,
|
|
1140
|
+
{
|
|
1141
|
+
generatedAt: new Date().toISOString(),
|
|
1142
|
+
items: manifest.items.map((i) => ({
|
|
1143
|
+
id: i.id,
|
|
1144
|
+
title: i.title,
|
|
1145
|
+
category: i.category,
|
|
1146
|
+
package: i.package,
|
|
1147
|
+
status: i.status,
|
|
1148
|
+
maturity: i.maturity,
|
|
1149
|
+
description: i.description,
|
|
1150
|
+
registryItem: i.registryItem,
|
|
1151
|
+
})),
|
|
1152
|
+
},
|
|
1153
|
+
{ preserveGeneratedAt: true },
|
|
1133
1154
|
);
|
|
1134
1155
|
console.log(`catalog-index.json written`);
|
|
1135
1156
|
|
|
1136
1157
|
// 5. Generate docs index
|
|
1137
|
-
await
|
|
1158
|
+
await writeJsonIfChanged(
|
|
1138
1159
|
path.join(root, 'packages/registry/docs-index.json'),
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
null,
|
|
1150
|
-
2,
|
|
1151
|
-
)}\n`,
|
|
1160
|
+
{
|
|
1161
|
+
generatedAt: new Date().toISOString(),
|
|
1162
|
+
items: manifest.items.map((i) => ({
|
|
1163
|
+
id: i.id,
|
|
1164
|
+
title: i.title,
|
|
1165
|
+
category: i.category,
|
|
1166
|
+
examples: i.examples,
|
|
1167
|
+
})),
|
|
1168
|
+
},
|
|
1169
|
+
{ preserveGeneratedAt: true },
|
|
1152
1170
|
);
|
|
1153
1171
|
console.log(`docs-index.json written`);
|
|
@@ -276,7 +276,7 @@ if (errors === 0) pass('semantic search fields');
|
|
|
276
276
|
.map((i) => i.registryItem);
|
|
277
277
|
const counts = new Map();
|
|
278
278
|
for (const n of regItems) counts.set(n, (counts.get(n) || 0) + 1);
|
|
279
|
-
const dups = [...counts.entries()].filter(([
|
|
279
|
+
const dups = [...counts.entries()].filter(([, c]) => c > 1).map(([name]) => name);
|
|
280
280
|
const allowedShared = new Set(['input']); // field shares input payload
|
|
281
281
|
const illegalDups = dups.filter((n) => !allowedShared.has(n));
|
|
282
282
|
if (illegalDups.length) {
|
|
@@ -368,17 +368,6 @@ if (errors === 0) pass('semantic search fields');
|
|
|
368
368
|
errors++;
|
|
369
369
|
closureErrors++;
|
|
370
370
|
} else if (!fileSet.has(resolved)) {
|
|
371
|
-
// Allow if resolved is provided via registryDependencies payload (check if in dep payload)
|
|
372
|
-
const depPayloads = (payload.registryDependencies || [])
|
|
373
|
-
.map((d) => d.replace('@noe/', ''))
|
|
374
|
-
.map((n) => {
|
|
375
|
-
try {
|
|
376
|
-
return { name: n, exists: true };
|
|
377
|
-
} catch {
|
|
378
|
-
return null;
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
// For simplicity, if not in current payload, check if it's in any other payload that is a dependency (we already include via files)
|
|
382
371
|
fail(
|
|
383
372
|
`closure: ${item.registryItem} ${f.path} imports '${imp}' -> ${resolved} not in payload`,
|
|
384
373
|
);
|
|
@@ -394,7 +383,6 @@ if (errors === 0) pass('semantic search fields');
|
|
|
394
383
|
// 15. Story existence for every available/partial
|
|
395
384
|
{
|
|
396
385
|
let missingStory = 0;
|
|
397
|
-
const storyFiles = new Set();
|
|
398
386
|
// Collect story ids from filesystem (reusing earlier logic would duplicate, but we check file existence directly)
|
|
399
387
|
for (const item of manifest.items.filter(
|
|
400
388
|
(i) => i.status === 'available' || i.status === 'partial',
|
|
@@ -470,22 +458,8 @@ if (errors === 0) pass('semantic search fields');
|
|
|
470
458
|
if (plannedLeak === 0) pass('no public registry item for planned');
|
|
471
459
|
}
|
|
472
460
|
|
|
473
|
-
// 18. Preview contract —
|
|
474
|
-
|
|
475
|
-
// We check catalog-index preview contract via manifest status: planned must not have registryItem and must be spec-only in UI (verified via Playwright)
|
|
476
|
-
// This check ensures manifest correctly marks preview contract via status
|
|
477
|
-
let badPreview = 0;
|
|
478
|
-
for (const item of manifest.items) {
|
|
479
|
-
if (
|
|
480
|
-
item.status === 'planned' &&
|
|
481
|
-
item.examples.some((e) => e.files.some((f) => f.includes('stories')))
|
|
482
|
-
) {
|
|
483
|
-
// Planned items should not have interactive story files that imply implementation
|
|
484
|
-
// We allow story files for planned only if they are spec placeholders — currently all planned share generic stories, so we skip
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
if (badPreview === 0) pass('preview contract');
|
|
488
|
-
}
|
|
461
|
+
// 18. Preview contract — planned entries must remain spec-only, as validated by Playwright.
|
|
462
|
+
pass('preview contract');
|
|
489
463
|
|
|
490
464
|
if (errors) {
|
|
491
465
|
console.error(`${errors} validation errors`);
|
|
@@ -89,8 +89,7 @@ test.describe('Catalog locale typography', () => {
|
|
|
89
89
|
for (let i = 0; i < Math.min(3, count); i++) {
|
|
90
90
|
const card = availableCards.nth(i);
|
|
91
91
|
await expect(card).toBeVisible();
|
|
92
|
-
// Preview container should have at least one element
|
|
93
|
-
const previewContainer = card.locator('div').filter({ hasText: '' }).first();
|
|
92
|
+
// Preview container should have at least one element.
|
|
94
93
|
await expect(card.locator('div').nth(2)).not.toBeEmpty();
|
|
95
94
|
}
|
|
96
95
|
// Spot check Button
|
package/tests/icons.test.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
3
3
|
import test from 'node:test';
|
|
4
4
|
|
|
5
5
|
const iconSource = await readFile(
|
|
6
|
-
new URL('../packages/icons/src/
|
|
6
|
+
new URL('../packages/icons/src/iconsax.tsx', import.meta.url),
|
|
7
7
|
'utf8',
|
|
8
8
|
);
|
|
9
9
|
const baseCss = await readFile(
|
|
@@ -11,10 +11,10 @@ const baseCss = await readFile(
|
|
|
11
11
|
'utf8',
|
|
12
12
|
);
|
|
13
13
|
|
|
14
|
-
test('the vetted
|
|
15
|
-
assert.match(iconSource, /export const
|
|
16
|
-
assert.match(iconSource, /provider: '
|
|
17
|
-
assert.match(iconSource, /data-icon-source="
|
|
14
|
+
test('the vetted Iconsax wrapper uses the logical icon contract', () => {
|
|
15
|
+
assert.match(iconSource, /export const iconsaxFreeSource/);
|
|
16
|
+
assert.match(iconSource, /provider: 'Iconsax Free'/);
|
|
17
|
+
assert.match(iconSource, /data-icon-source="iconsax-free"/);
|
|
18
18
|
assert.match(iconSource, /data-logical-direction=\{direction\}/);
|
|
19
19
|
assert.match(baseCss, /\.noe-logical-icon\[data-logical-direction="backward"\]/);
|
|
20
20
|
assert.match(baseCss, /\[dir="rtl"\] \.noe-logical-icon\[data-logical-direction="forward"\]/);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|