@ponti-studios/ui 0.4.0 → 0.4.2
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 +30 -4
- package/THIRD_PARTY_NOTICES.md +3 -3
- package/package.json +36 -19
- package/src/components/feedback/alert.tsx +1 -1
- package/src/components/feedback/file-upload-status-badge.tsx +1 -1
- package/src/components/feedback/file-upload-status.tsx +2 -2
- package/src/components/forms/field.tsx +2 -2
- package/src/components/forms/passkey-management.tsx +2 -2
- package/src/components/overlays/dropdown-menu.tsx +1 -1
- package/src/components/primitives/button.tsx +6 -1
- package/src/index.ts +3 -3
- package/src/native/index.ts +11 -0
- package/src/native/make-styles.ts +32 -0
- package/src/native/shadows.ts +38 -0
- package/src/native/use-theme-colors.ts +19 -0
- package/src/{styles.css → styles/index.css} +5 -0
- package/src/{tokens → styles/tokens}/chart-colors.stories.tsx +10 -13
- package/src/{tokens → styles/tokens}/color-systems.stories.tsx +1 -1
- package/src/styles/tokens/css-vars.ts +15 -0
- package/src/{tokens → styles/tokens}/generated/dark.colors.generated.ts +3 -1
- package/src/{tokens → styles/tokens}/generated/dark.css +3 -1
- package/src/{tokens → styles/tokens}/generated/light.colors.generated.ts +3 -1
- package/src/{tokens → styles/tokens}/generated/light.css +3 -1
- package/src/{tokens → styles/tokens}/index.ts +0 -8
- package/src/styles/tokens/scripts/check-foundations.mjs +126 -0
- package/src/styles/tokens/scripts/check-source.mjs +43 -0
- package/src/styles/tokens/source/dark.tokens.json +105 -0
- package/src/styles/tokens/source/foundations.tokens.json +341 -0
- package/src/styles/tokens/source/light.tokens.json +105 -0
- package/src/styles/tokens/style-dictionary.dark-storybook.json +16 -0
- package/src/styles/tokens/style-dictionary.dark.json +21 -0
- package/src/styles/tokens/style-dictionary.foundations.json +21 -0
- package/src/styles/tokens/style-dictionary.light.json +21 -0
- package/dist/index.js +0 -2466
- package/dist/index.js.map +0 -1
- /package/src/{animations.css → styles/animations.css} +0 -0
- /package/src/{tokens → styles/tokens}/foundations.stories.tsx +0 -0
- /package/src/{tokens → styles/tokens}/generated/foundations.css +0 -0
- /package/src/{tokens → styles/tokens}/generated/foundations.generated.ts +0 -0
- /package/src/{tokens → styles/tokens}/layout.stories.tsx +0 -0
package/README.md
CHANGED
|
@@ -4,11 +4,23 @@ The canonical Ponti Studios UI package. It is published from this repository to
|
|
|
4
4
|
|
|
5
5
|
## Release
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
2. Merge the change to `main`.
|
|
9
|
-
3. Create and push a matching `ui-v<version>` tag, for example `ui-v0.1.0`.
|
|
7
|
+
Releases are owned by [release-please](https://github.com/googleapis/release-please), not by hand-edited version numbers or manually pushed tags. The workflow is defined once, in `.github/workflows/publish-ui.yml`, and has three jobs:
|
|
10
8
|
|
|
11
|
-
|
|
9
|
+
1. **`release-ui`** — runs on every push to `main`. It scans commits under `packages/ui/` since the last release and keeps a standing pull request, `chore(main): release ui X.Y.Z`, up to date: `packages/ui/package.json`'s `version`, `.release-please-manifest.json`, and `packages/ui/CHANGELOG.md` are all written by this PR, never by a person. **Never hand-edit the `version` field in `packages/ui/package.json`** — the next push will regenerate the release PR from the real commit history and overwrite whatever you set.
|
|
10
|
+
2. **`publish-ui`** — gated on `release-ui`'s `release_created` output, i.e. it only runs the push immediately after the release PR is *merged*. Merging that PR is what creates the `ui-v<version>` tag and triggers this job. It checks out that exact tag, re-verifies the tag matches `package.json`'s version and `HEAD`, runs `npm publish --provenance --access public`, then confirms the version resolves on the registry.
|
|
11
|
+
3. **`publish-ui-manual`** — a `workflow_dispatch` break-glass path that checks out an arbitrary ref and publishes whatever version is in `package.json` there, with none of `publish-ui`'s tag/version consistency checks. Only use this to recover from a `publish-ui` run that failed for a reason unrelated to versioning (e.g. a broken `prepack` step) after a real release PR already merged — never as a way to skip release-please.
|
|
12
|
+
|
|
13
|
+
**So the actual release flow is:**
|
|
14
|
+
|
|
15
|
+
1. Land commits under `packages/ui/` using [Conventional Commits](https://www.conventionalcommits.org/) — the version bump is computed from the commit type: `fix:` → patch, `feat:` → minor, `feat!:`/a `BREAKING CHANGE:` footer → major. This is the *only* way to control the bump; there is no manual override.
|
|
16
|
+
2. Once on `main`, find or wait for the `chore(main): release ui X.Y.Z` PR that `release-ui` opens/updates.
|
|
17
|
+
3. Review and merge it. That merge creates the `ui-v<version>` tag and triggers `publish-ui` automatically. Nothing further to push or tag by hand.
|
|
18
|
+
|
|
19
|
+
`prepack` (`tokens:check && check:source && typecheck`) runs as part of `pnpm install`/publish tooling and is the last gate before anything reaches npm — see "Generated files" below for the failure mode that most commonly trips it.
|
|
20
|
+
|
|
21
|
+
### Generated files must never be hand-edited
|
|
22
|
+
|
|
23
|
+
`src/styles/tokens/generated/**` and `.storybook/generated/**` are produced only by `pnpm run tokens:build` (style-dictionary) from the DTCG source in `src/styles/tokens/source/*.tokens.json`. `tokens:check` enforces this with `git diff --exit-code -- src/styles/tokens/generated` after a fresh rebuild — if the committed file doesn't byte-for-byte match what the tool just produced, the check (and therefore `prepack` and the publish) fails. This has happened in practice when a repo-wide formatter (oxfmt) reformatted a generated CSS file directly; `.oxfmtrc.json`'s `ignorePatterns` now excludes both generated directories specifically to prevent that recurring. If `tokens:check` fails on a file you didn't intend to touch, run `pnpm run tokens:build` and commit the regenerated output — don't hand-format it back into place.
|
|
12
24
|
|
|
13
25
|
## Consumers
|
|
14
26
|
|
|
@@ -25,3 +37,17 @@ pnpm config set --location=user //registry.npmjs.org/:_authToken "$NODE_AUTH_TOK
|
|
|
25
37
|
```
|
|
26
38
|
|
|
27
39
|
CI consumers should use a secret-backed npm token with read access.
|
|
40
|
+
|
|
41
|
+
### Ships TypeScript source, not a bundle
|
|
42
|
+
|
|
43
|
+
This package has no build output — every `exports` entry resolves to a file under `src/`. Consumers are expected to transpile the package themselves (Vite, Metro, Next, etc. all do this for workspace/npm packages by default). Tailwind v4 class detection for consumers of `./styles.css` requires an `@source` directive pointing at the installed package's `src/`, e.g.
|
|
44
|
+
|
|
45
|
+
```css
|
|
46
|
+
@source "../../../node_modules/@ponti-studios/ui/src";
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Bundling the package would break that detection, since Tailwind can't scan generated/minified output for class names. `pnpm run build` only type-checks; it does not emit JS.
|
|
50
|
+
|
|
51
|
+
### `./native` subpath
|
|
52
|
+
|
|
53
|
+
`@ponti-studios/ui/native` is the only part of the package that imports `react-native`. Web consumers never pull it in; native consumers get `createMakeStyles`, `nativeShadows`, `useColorMode`, and `useThemeColors`. `react-native` is an optional peer dependency — only declare it in your own `package.json` if you actually import from this subpath.
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Adobe Spectrum Tokens
|
|
4
4
|
|
|
5
|
-
The light and dark color snapshots in `src/tokens/generated/*colors.generated.ts`
|
|
5
|
+
The light and dark color snapshots in `src/styles/tokens/generated/*colors.generated.ts`
|
|
6
6
|
are derived from Adobe Spectrum Tokens 14.15.0. Spectrum Tokens is distributed
|
|
7
7
|
under the Apache License 2.0.
|
|
8
8
|
|
|
@@ -10,8 +10,8 @@ Source: https://github.com/adobe/spectrum-tokens
|
|
|
10
10
|
|
|
11
11
|
## Tailwind CSS
|
|
12
12
|
|
|
13
|
-
The non-color foundations in `src/tokens/generated/foundations.generated.ts`
|
|
14
|
-
and `src/tokens/generated/foundations.css` match the Tailwind CSS 4.3.3 default
|
|
13
|
+
The non-color foundations in `src/styles/tokens/generated/foundations.generated.ts`
|
|
14
|
+
and `src/styles/tokens/generated/foundations.css` match the Tailwind CSS 4.3.3 default
|
|
15
15
|
theme. Tailwind CSS is distributed under the MIT License.
|
|
16
16
|
|
|
17
17
|
Source: https://github.com/tailwindlabs/tailwindcss
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ponti-studios/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared UI component library for ponti-studios labs",
|
|
6
6
|
"license": "MIT",
|
|
@@ -9,19 +9,14 @@
|
|
|
9
9
|
"url": "git+https://github.com/ponti-studios/labs.git",
|
|
10
10
|
"directory": "packages/ui"
|
|
11
11
|
},
|
|
12
|
-
"publishConfig": {
|
|
13
|
-
"registry": "https://registry.npmjs.org",
|
|
14
|
-
"access": "public"
|
|
15
|
-
},
|
|
16
12
|
"files": [
|
|
17
|
-
"dist",
|
|
18
13
|
"src",
|
|
19
14
|
"THIRD_PARTY_NOTICES.md"
|
|
20
15
|
],
|
|
21
16
|
"type": "module",
|
|
22
17
|
"sideEffects": [
|
|
23
|
-
"./src/styles.css",
|
|
24
|
-
"./src/animations.css"
|
|
18
|
+
"./src/styles/index.css",
|
|
19
|
+
"./src/styles/animations.css"
|
|
25
20
|
],
|
|
26
21
|
"exports": {
|
|
27
22
|
".": {
|
|
@@ -61,28 +56,40 @@
|
|
|
61
56
|
"import": "./src/lib/index.ts"
|
|
62
57
|
},
|
|
63
58
|
"./tokens": {
|
|
64
|
-
"types": "./src/tokens/index.ts",
|
|
65
|
-
"import": "./src/tokens/index.ts"
|
|
59
|
+
"types": "./src/styles/tokens/index.ts",
|
|
60
|
+
"import": "./src/styles/tokens/index.ts"
|
|
61
|
+
},
|
|
62
|
+
"./tokens/css-vars": {
|
|
63
|
+
"types": "./src/styles/tokens/css-vars.ts",
|
|
64
|
+
"import": "./src/styles/tokens/css-vars.ts"
|
|
65
|
+
},
|
|
66
|
+
"./native": {
|
|
67
|
+
"types": "./src/native/index.ts",
|
|
68
|
+
"import": "./src/native/index.ts"
|
|
66
69
|
},
|
|
67
70
|
"./hooks": {
|
|
68
71
|
"types": "./src/hooks/index.ts",
|
|
69
72
|
"import": "./src/hooks/index.ts"
|
|
70
73
|
},
|
|
71
|
-
"./styles.css": "./src/styles.css",
|
|
72
|
-
"./animations.css": "./src/animations.css"
|
|
74
|
+
"./styles.css": "./src/styles/index.css",
|
|
75
|
+
"./animations.css": "./src/styles/animations.css"
|
|
76
|
+
},
|
|
77
|
+
"publishConfig": {
|
|
78
|
+
"access": "public",
|
|
79
|
+
"registry": "https://registry.npmjs.org"
|
|
73
80
|
},
|
|
74
81
|
"scripts": {
|
|
75
|
-
"build": "
|
|
82
|
+
"build": "tsc --noEmit",
|
|
76
83
|
"dev": "vite",
|
|
77
|
-
"storybook": "style-dictionary build --config style-dictionary.dark-storybook.json && storybook dev -p 6006",
|
|
78
|
-
"check:source": "node tokens/check-source.mjs",
|
|
84
|
+
"storybook": "style-dictionary build --config src/styles/tokens/style-dictionary.dark-storybook.json && storybook dev -p 6006",
|
|
85
|
+
"check:source": "node src/styles/tokens/scripts/check-source.mjs",
|
|
79
86
|
"test:a11y": "vitest --run --config vitest.config.ts",
|
|
80
|
-
"tokens:build": "style-dictionary build --config style-dictionary.foundations.json && style-dictionary build --config style-dictionary.light.json && style-dictionary build --config style-dictionary.dark.json && style-dictionary build --config style-dictionary.dark-storybook.json",
|
|
81
|
-
"tokens:check": "pnpm run tokens:build && node tokens/check-foundations.mjs && git diff --exit-code -- src/tokens/generated",
|
|
87
|
+
"tokens:build": "style-dictionary build --config src/styles/tokens/style-dictionary.foundations.json && style-dictionary build --config src/styles/tokens/style-dictionary.light.json && style-dictionary build --config src/styles/tokens/style-dictionary.dark.json && style-dictionary build --config src/styles/tokens/style-dictionary.dark-storybook.json",
|
|
88
|
+
"tokens:check": "pnpm run tokens:build && node src/styles/tokens/scripts/check-foundations.mjs && git diff --exit-code -- src/styles/tokens/generated",
|
|
82
89
|
"typecheck": "tsc --noEmit",
|
|
83
90
|
"lint": "oxlint src --fix",
|
|
84
91
|
"format": "oxfmt src --write",
|
|
85
|
-
"prepack": "pnpm run tokens:check && pnpm run check:source && pnpm run typecheck
|
|
92
|
+
"prepack": "pnpm run tokens:check && pnpm run check:source && pnpm run typecheck"
|
|
86
93
|
},
|
|
87
94
|
"dependencies": {
|
|
88
95
|
"@base-ui/react": "^1.6.0",
|
|
@@ -115,6 +122,7 @@
|
|
|
115
122
|
"playwright": "catalog:",
|
|
116
123
|
"react": "^19.2.7",
|
|
117
124
|
"react-dom": "^19.2.7",
|
|
125
|
+
"react-native": "0.86.0",
|
|
118
126
|
"storybook": "^10.5.3",
|
|
119
127
|
"style-dictionary": "5.5.0",
|
|
120
128
|
"tailwindcss": "catalog:",
|
|
@@ -124,7 +132,16 @@
|
|
|
124
132
|
},
|
|
125
133
|
"peerDependencies": {
|
|
126
134
|
"react": "^18.2.0 || ^19.0.0",
|
|
127
|
-
"react-dom": "^18.2.0 || ^19.0.0"
|
|
135
|
+
"react-dom": "^18.2.0 || ^19.0.0",
|
|
136
|
+
"react-native": ">=0.74"
|
|
137
|
+
},
|
|
138
|
+
"peerDependenciesMeta": {
|
|
139
|
+
"react-dom": {
|
|
140
|
+
"optional": true
|
|
141
|
+
},
|
|
142
|
+
"react-native": {
|
|
143
|
+
"optional": true
|
|
144
|
+
}
|
|
128
145
|
},
|
|
129
146
|
"packageManager": "pnpm@10.30.3"
|
|
130
147
|
}
|
|
@@ -10,7 +10,7 @@ const alertVariants = cva(
|
|
|
10
10
|
variant: {
|
|
11
11
|
default: "bg-card text-card-foreground",
|
|
12
12
|
destructive:
|
|
13
|
-
"border-destructive/50 text-destructive bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive
|
|
13
|
+
"border-destructive/50 text-destructive-text bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-destructive-text",
|
|
14
14
|
},
|
|
15
15
|
},
|
|
16
16
|
defaultVariants: {
|
|
@@ -9,7 +9,7 @@ const STATUS_CONFIG: Record<FileUploadStatusValue, { bg: string; text: string; l
|
|
|
9
9
|
processing: { bg: "bg-muted", text: "text-foreground", label: "Processing" },
|
|
10
10
|
queued: { bg: "bg-secondary", text: "text-secondary-foreground", label: "Queued" },
|
|
11
11
|
done: { bg: "bg-muted", text: "text-muted-foreground", label: "Complete" },
|
|
12
|
-
error: { bg: "bg-destructive/10", text: "text-destructive", label: "Error" },
|
|
12
|
+
error: { bg: "bg-destructive/10", text: "text-destructive-text", label: "Error" },
|
|
13
13
|
};
|
|
14
14
|
|
|
15
15
|
export const FileUploadStatusBadge = memo(function FileUploadStatusBadge({
|
|
@@ -86,7 +86,7 @@ export const FileUploadStatus = memo(function FileUploadStatus({
|
|
|
86
86
|
if (status === "error") {
|
|
87
87
|
return (
|
|
88
88
|
<output className="mt-2" role="alert">
|
|
89
|
-
<div className="bg-destructive/10 text-destructive flex items-start gap-2 p-3">
|
|
89
|
+
<div className="bg-destructive/10 text-destructive-text flex items-start gap-2 p-3">
|
|
90
90
|
<XIcon className="mt-0.5 h-5 w-5 flex-shrink-0" aria-hidden="true" />
|
|
91
91
|
<span className="text-sm">{error}</span>
|
|
92
92
|
</div>
|
|
@@ -125,7 +125,7 @@ const ProcessingStats = memo(function ProcessingStats({ stats }: { stats: FileUp
|
|
|
125
125
|
<div className="py-2">
|
|
126
126
|
<dt className="text-foreground text-sm font-medium">Errors</dt>
|
|
127
127
|
<dd className="mt-1">
|
|
128
|
-
<span className="bg-destructive/10 text-destructive inline-flex items-center px-2 py-1 text-xs font-medium">
|
|
128
|
+
<span className="bg-destructive/10 text-destructive-text inline-flex items-center px-2 py-1 text-xs font-medium">
|
|
129
129
|
{stats.errors.length} error{stats.errors.length > 1 ? "s" : ""}
|
|
130
130
|
</span>
|
|
131
131
|
</dd>
|
|
@@ -30,7 +30,7 @@ function Field({ label, helpText, error, required, children, id: externalId }: F
|
|
|
30
30
|
htmlFor={id}
|
|
31
31
|
className={cn(
|
|
32
32
|
"text-foreground text-sm font-medium",
|
|
33
|
-
required && "after:text-destructive after:ml-0.5 after:content-['*']",
|
|
33
|
+
required && "after:text-destructive-text after:ml-0.5 after:content-['*']",
|
|
34
34
|
)}
|
|
35
35
|
>
|
|
36
36
|
{label}
|
|
@@ -50,7 +50,7 @@ function Field({ label, helpText, error, required, children, id: externalId }: F
|
|
|
50
50
|
: children}
|
|
51
51
|
|
|
52
52
|
{error ? (
|
|
53
|
-
<p id={errorId} role="alert" className="text-destructive text-sm">
|
|
53
|
+
<p id={errorId} role="alert" className="text-destructive-text text-sm">
|
|
54
54
|
{error}
|
|
55
55
|
</p>
|
|
56
56
|
) : helpText ? (
|
|
@@ -90,7 +90,7 @@ export function PasskeyManagement({
|
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
{error ? (
|
|
93
|
-
<p role="alert" className="text-destructive text-sm">
|
|
93
|
+
<p role="alert" className="text-destructive-text text-sm">
|
|
94
94
|
{error}
|
|
95
95
|
</p>
|
|
96
96
|
) : null}
|
|
@@ -125,7 +125,7 @@ export function PasskeyManagement({
|
|
|
125
125
|
onClick={() => void handleDelete(pk.id)}
|
|
126
126
|
disabled={deletingId === pk.id}
|
|
127
127
|
aria-label={`Remove passkey ${pk.name ?? pk.id}`}
|
|
128
|
-
className="hover:text-destructive text-muted-foreground focus-visible:ring-ring rounded-sm focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
|
|
128
|
+
className="hover:text-destructive-text text-muted-foreground focus-visible:ring-ring rounded-sm focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50"
|
|
129
129
|
>
|
|
130
130
|
<Trash2 className="size-4" aria-hidden />
|
|
131
131
|
</button>
|
|
@@ -88,7 +88,7 @@ function DropdownMenuItem({
|
|
|
88
88
|
data-inset={inset}
|
|
89
89
|
data-variant={variant}
|
|
90
90
|
className={cn(
|
|
91
|
-
"data-highlighted:bg-muted data-highlighted:text-muted-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:data-highlighted:bg-destructive/10 relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
91
|
+
"data-highlighted:bg-muted data-highlighted:text-muted-foreground data-[variant=destructive]:text-destructive-text data-[variant=destructive]:data-highlighted:bg-destructive/10 relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
|
92
92
|
className,
|
|
93
93
|
)}
|
|
94
94
|
{...props}
|
|
@@ -49,6 +49,10 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
49
49
|
variant,
|
|
50
50
|
size,
|
|
51
51
|
asChild = false,
|
|
52
|
+
// Defaults to "button" — same safe default the native element loses
|
|
53
|
+
// once it's rendered inside a <form> — but must stay overridable so
|
|
54
|
+
// consumers can pass type="submit"/"reset".
|
|
55
|
+
type = "button",
|
|
52
56
|
"aria-label": ariaLabel,
|
|
53
57
|
...props
|
|
54
58
|
},
|
|
@@ -75,6 +79,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
75
79
|
return (
|
|
76
80
|
<BaseButton
|
|
77
81
|
{...props}
|
|
82
|
+
type={type}
|
|
78
83
|
render={renderedChild}
|
|
79
84
|
nativeButton={false}
|
|
80
85
|
className={cn(buttonVariants({ variant, size, className }), isLoading && "relative")}
|
|
@@ -93,7 +98,7 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
|
93
98
|
...props.style,
|
|
94
99
|
cursor: disabled || isLoading ? "default" : "pointer",
|
|
95
100
|
}}
|
|
96
|
-
type=
|
|
101
|
+
type={type}
|
|
97
102
|
ref={ref}
|
|
98
103
|
disabled={disabled || isLoading}
|
|
99
104
|
aria-busy={isLoading}
|
package/src/index.ts
CHANGED
|
@@ -9,9 +9,9 @@ export { normalizeOtp, OTP_LENGTH } from "./lib/auth";
|
|
|
9
9
|
export { copyToClipboard } from "./lib/clipboard";
|
|
10
10
|
export { createMemoryStorage } from "./lib/create-memory-storage";
|
|
11
11
|
export { cn } from "./lib/utils";
|
|
12
|
+
export { colors } from "./styles/tokens/css-vars";
|
|
12
13
|
export {
|
|
13
14
|
animations,
|
|
14
|
-
colors,
|
|
15
15
|
colorThemes,
|
|
16
16
|
colorTokenNames,
|
|
17
17
|
containers,
|
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
transitionDurations,
|
|
30
30
|
breakpoints,
|
|
31
31
|
zIndices,
|
|
32
|
-
} from "./tokens/index";
|
|
32
|
+
} from "./styles/tokens/index";
|
|
33
33
|
export type {
|
|
34
34
|
ColorMode,
|
|
35
35
|
ColorTheme,
|
|
@@ -41,4 +41,4 @@ export type {
|
|
|
41
41
|
SpacingToken,
|
|
42
42
|
TextSizeToken,
|
|
43
43
|
TrackingToken,
|
|
44
|
-
} from "./tokens/index";
|
|
44
|
+
} from "./styles/tokens/index";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React Native entry point.
|
|
3
|
+
*
|
|
4
|
+
* Kept as its own subpath (`@ponti-studios/ui/native`) because it is the only
|
|
5
|
+
* part of the package that imports `react-native`. Nothing here may import web
|
|
6
|
+
* code, so web consumers never pull React Native into their graph and native
|
|
7
|
+
* consumers never pull in react-dom.
|
|
8
|
+
*/
|
|
9
|
+
export { createMakeStyles, type NamedStyles } from "./make-styles";
|
|
10
|
+
export { nativeShadows, type NativeShadowLayer } from "./shadows";
|
|
11
|
+
export { useColorMode, useThemeColors } from "./use-theme-colors";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { ImageStyle, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
|
|
3
|
+
export type NamedStyles = Record<string, ViewStyle | TextStyle | ImageStyle>;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Builds a `makeStyles` helper bound to a consuming app's own theme hook.
|
|
7
|
+
*
|
|
8
|
+
* The theme *shape* stays owned by the app — this package supplies only the
|
|
9
|
+
* mechanism — so `Theme` is a type parameter rather than a type exported from
|
|
10
|
+
* here. An app calls this once with its `useTheme` and uses the result
|
|
11
|
+
* everywhere:
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* export const makeStyles = createMakeStyles(useTheme);
|
|
15
|
+
*
|
|
16
|
+
* const useStyles = makeStyles((theme) => ({
|
|
17
|
+
* card: { backgroundColor: theme.colors.card },
|
|
18
|
+
* }));
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The returned `useStyles` is a hook: it calls `useTheme` on every render, so
|
|
22
|
+
* styles re-resolve when the color mode changes. Styles are returned as plain
|
|
23
|
+
* objects rather than passed through `StyleSheet.create`, since they are
|
|
24
|
+
* rebuilt per render and React Native accepts plain style objects directly.
|
|
25
|
+
*/
|
|
26
|
+
export function createMakeStyles<Theme>(useTheme: () => Theme) {
|
|
27
|
+
return function makeStyles<T extends NamedStyles>(factory: (theme: Theme) => T & NamedStyles) {
|
|
28
|
+
return function useStyles(): T {
|
|
29
|
+
return factory(useTheme());
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { shadows, type ShadowToken } from "../styles/tokens/index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* One layer of a shadow in the shape React Native's `boxShadow` expects.
|
|
5
|
+
*
|
|
6
|
+
* The shared shadow tokens are authored for CSS, where each dimension carries
|
|
7
|
+
* its own unit. React Native takes bare numbers (density-independent pixels),
|
|
8
|
+
* so the unit is dropped here rather than at every call site.
|
|
9
|
+
*/
|
|
10
|
+
export interface NativeShadowLayer {
|
|
11
|
+
color: string;
|
|
12
|
+
offsetX: number;
|
|
13
|
+
offsetY: number;
|
|
14
|
+
blurRadius: number;
|
|
15
|
+
spreadDistance: number;
|
|
16
|
+
inset: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The shared shadow scale translated to React Native's `boxShadow` shape.
|
|
21
|
+
*
|
|
22
|
+
* Every token maps to an array of layers, including `none`, which is a single
|
|
23
|
+
* fully transparent layer rather than an empty array — that keeps the value
|
|
24
|
+
* assignable wherever a real shadow is expected.
|
|
25
|
+
*/
|
|
26
|
+
export const nativeShadows = Object.fromEntries(
|
|
27
|
+
Object.entries(shadows).map(([name, layers]) => [
|
|
28
|
+
name,
|
|
29
|
+
layers.map((layer) => ({
|
|
30
|
+
color: layer.color,
|
|
31
|
+
offsetX: layer.offsetX.value,
|
|
32
|
+
offsetY: layer.offsetY.value,
|
|
33
|
+
blurRadius: layer.blur.value,
|
|
34
|
+
spreadDistance: layer.spread.value,
|
|
35
|
+
inset: false,
|
|
36
|
+
})),
|
|
37
|
+
]),
|
|
38
|
+
) as Record<ShadowToken, NativeShadowLayer[]>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useColorScheme } from "react-native";
|
|
2
|
+
|
|
3
|
+
import { colorThemes, type ColorMode, type ColorTheme } from "../styles/tokens/index";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Resolves the active color mode from the OS appearance setting.
|
|
7
|
+
*
|
|
8
|
+
* React Native's `useColorScheme` reports `null` before the appearance module
|
|
9
|
+
* has settled, so anything that isn't explicitly "dark" resolves to light —
|
|
10
|
+
* callers always receive a concrete mode rather than having to handle null.
|
|
11
|
+
*/
|
|
12
|
+
export function useColorMode(): ColorMode {
|
|
13
|
+
return useColorScheme() === "dark" ? "dark" : "light";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** The full color token set for the active OS appearance. */
|
|
17
|
+
export function useThemeColors(): ColorTheme {
|
|
18
|
+
return colorThemes[useColorMode()];
|
|
19
|
+
}
|
|
@@ -23,6 +23,11 @@
|
|
|
23
23
|
--color-accent-foreground: var(--primary-foreground);
|
|
24
24
|
--color-destructive: var(--destructive);
|
|
25
25
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
26
|
+
/* Distinct from --color-destructive: that pairs with white for solid
|
|
27
|
+
* backgrounds (buttons, badges) and is the same value in both themes.
|
|
28
|
+
* This pairs with the page background for standalone text (error
|
|
29
|
+
* messages, alert copy) and must differ per theme to stay AA-compliant. */
|
|
30
|
+
--color-destructive-text: var(--text-destructive);
|
|
26
31
|
--color-success: var(--success);
|
|
27
32
|
--color-warning: var(--warning);
|
|
28
33
|
--color-border: var(--border-default);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
|
|
3
|
-
import { CHART_CSS_VARS } from "
|
|
3
|
+
import { CHART_CSS_VARS } from "../../constants/chart-colors";
|
|
4
4
|
|
|
5
5
|
const meta: Meta = { title: "Foundations/Charts", parameters: { layout: "fullscreen" } };
|
|
6
6
|
export default meta;
|
|
@@ -78,16 +78,13 @@ export const CategoricalPalette: Story = {
|
|
|
78
78
|
<p className="text-muted-foreground text-sm">Foundations / charts</p>
|
|
79
79
|
<h1 className="text-4xl font-semibold tracking-tight">Chart colors</h1>
|
|
80
80
|
<p className="text-muted-foreground max-w-2xl text-base">
|
|
81
|
-
<code className="font-mono">chart-1</code> through
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<code className="font-mono">chart-
|
|
88
|
-
chart-negative
|
|
89
|
-
</code>
|
|
90
|
-
/<code className="font-mono">chart-neutral</code> below, which carry real meaning
|
|
81
|
+
<code className="font-mono">chart-1</code> through{" "}
|
|
82
|
+
<code className="font-mono">chart-5</code> are an ordinal palette for arbitrary
|
|
83
|
+
categorical series (e.g. one line per country) — there's no fixed meaning to a given slot,
|
|
84
|
+
so they're assigned by index, not by what the data represents. That's different from{" "}
|
|
85
|
+
<code className="font-mono">chart-positive</code>/
|
|
86
|
+
<code className="font-mono">chart-negative</code>/
|
|
87
|
+
<code className="font-mono">chart-neutral</code> below, which carry real meaning
|
|
91
88
|
(up/down/flat) and should stay semantic in any chart that shows a directional delta.
|
|
92
89
|
</p>
|
|
93
90
|
</header>
|
|
@@ -112,8 +109,8 @@ export const CategoricalPalette: Story = {
|
|
|
112
109
|
<section className="border-border bg-card grid gap-5 rounded-xl border p-6">
|
|
113
110
|
<h2 className="text-2xl font-semibold">Grouped bar chart (mock)</h2>
|
|
114
111
|
<p className="text-muted-foreground max-w-2xl text-sm">
|
|
115
|
-
Three unrelated categories, five series each — a stress test for how distinguishable
|
|
116
|
-
|
|
112
|
+
Three unrelated categories, five series each — a stress test for how distinguishable the
|
|
113
|
+
palette stays side by side.
|
|
117
114
|
</p>
|
|
118
115
|
<div className="flex items-end gap-8" style={{ height: 220 }}>
|
|
119
116
|
{BAR_DATA.map((group, groupIndex) => (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
|
|
3
|
-
import { Button } from "
|
|
3
|
+
import { Button } from "../../components/primitives/button";
|
|
4
4
|
import { colorTokenNames } from "./index";
|
|
5
5
|
|
|
6
6
|
const meta: Meta = { title: "Foundations/Colors", parameters: { layout: "fullscreen" } };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { colorTokenNames, type ColorToken } from "./index";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Color tokens as CSS `var()` references, for consumers whose runtime resolves
|
|
5
|
+
* the CSS cascade (i.e. web). Kept out of `./tokens` because React Native has
|
|
6
|
+
* no cascade — a `var(--token)` string there fails silently, rendering as an
|
|
7
|
+
* unparseable color rather than throwing.
|
|
8
|
+
*/
|
|
9
|
+
export const colors = colorTokenNames.reduce(
|
|
10
|
+
(accumulator, token) => {
|
|
11
|
+
accumulator[token] = `var(--${token})`;
|
|
12
|
+
return accumulator;
|
|
13
|
+
},
|
|
14
|
+
{} as Record<ColorToken, `var(--${string})`>,
|
|
15
|
+
);
|
|
@@ -11,11 +11,12 @@ export default {
|
|
|
11
11
|
"text-secondary": "#b0b4ba",
|
|
12
12
|
tertiary: "#777b84",
|
|
13
13
|
primary: "#3e63dd",
|
|
14
|
-
destructive: "#
|
|
14
|
+
destructive: "#ce2c31",
|
|
15
15
|
success: "#30a46c",
|
|
16
16
|
warning: "#ffc53d",
|
|
17
17
|
"primary-foreground": "#ffffff",
|
|
18
18
|
"destructive-foreground": "#ffffff",
|
|
19
|
+
"text-destructive": "#ff9592",
|
|
19
20
|
"border-default": "#43484e",
|
|
20
21
|
"focus-ring": "#3e63dd",
|
|
21
22
|
"chart-1": "#3e63dd",
|
|
@@ -26,4 +27,5 @@ export default {
|
|
|
26
27
|
"chart-positive": "#30a46c",
|
|
27
28
|
"chart-negative": "#e5484d",
|
|
28
29
|
"chart-neutral": "#5a6169",
|
|
30
|
+
"overlay-scrim": "#000000",
|
|
29
31
|
};
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
--text-secondary: #b0b4ba;
|
|
12
12
|
--tertiary: #777b84;
|
|
13
13
|
--primary: #3e63dd;
|
|
14
|
-
--destructive: #
|
|
14
|
+
--destructive: #ce2c31;
|
|
15
15
|
--success: #30a46c;
|
|
16
16
|
--warning: #ffc53d;
|
|
17
17
|
--primary-foreground: #ffffff;
|
|
18
18
|
--destructive-foreground: #ffffff;
|
|
19
|
+
--text-destructive: #ff9592;
|
|
19
20
|
--border-default: #43484e;
|
|
20
21
|
--focus-ring: #3e63dd;
|
|
21
22
|
--chart-1: #3e63dd;
|
|
@@ -26,4 +27,5 @@
|
|
|
26
27
|
--chart-positive: #30a46c;
|
|
27
28
|
--chart-negative: #e5484d;
|
|
28
29
|
--chart-neutral: #5a6169;
|
|
30
|
+
--overlay-scrim: #000000;
|
|
29
31
|
}
|
|
@@ -11,11 +11,12 @@ export default {
|
|
|
11
11
|
"text-secondary": "#60646c",
|
|
12
12
|
tertiary: "#80838d",
|
|
13
13
|
primary: "#3e63dd",
|
|
14
|
-
destructive: "#
|
|
14
|
+
destructive: "#ce2c31",
|
|
15
15
|
success: "#30a46c",
|
|
16
16
|
warning: "#ffc53d",
|
|
17
17
|
"primary-foreground": "#ffffff",
|
|
18
18
|
"destructive-foreground": "#ffffff",
|
|
19
|
+
"text-destructive": "#c82c31",
|
|
19
20
|
"border-default": "#cdced6",
|
|
20
21
|
"focus-ring": "#3e63dd",
|
|
21
22
|
"chart-1": "#3e63dd",
|
|
@@ -26,4 +27,5 @@ export default {
|
|
|
26
27
|
"chart-positive": "#30a46c",
|
|
27
28
|
"chart-negative": "#e5484d",
|
|
28
29
|
"chart-neutral": "#b9bbc6",
|
|
30
|
+
"overlay-scrim": "#000000",
|
|
29
31
|
};
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
--text-secondary: #60646c;
|
|
12
12
|
--tertiary: #80838d;
|
|
13
13
|
--primary: #3e63dd;
|
|
14
|
-
--destructive: #
|
|
14
|
+
--destructive: #ce2c31;
|
|
15
15
|
--success: #30a46c;
|
|
16
16
|
--warning: #ffc53d;
|
|
17
17
|
--primary-foreground: #ffffff;
|
|
18
18
|
--destructive-foreground: #ffffff;
|
|
19
|
+
--text-destructive: #c82c31;
|
|
19
20
|
--border-default: #cdced6;
|
|
20
21
|
--focus-ring: #3e63dd;
|
|
21
22
|
--chart-1: #3e63dd;
|
|
@@ -26,4 +27,5 @@
|
|
|
26
27
|
--chart-positive: #30a46c;
|
|
27
28
|
--chart-negative: #e5484d;
|
|
28
29
|
--chart-neutral: #b9bbc6;
|
|
30
|
+
--overlay-scrim: #000000;
|
|
29
31
|
}
|
|
@@ -27,14 +27,6 @@ export const colorThemes = {
|
|
|
27
27
|
dark: darkTokens,
|
|
28
28
|
} as const satisfies Record<ColorMode, ColorTheme>;
|
|
29
29
|
|
|
30
|
-
export const colors = colorTokenNames.reduce(
|
|
31
|
-
(accumulator, token) => {
|
|
32
|
-
accumulator[token] = `var(--${token})`;
|
|
33
|
-
return accumulator;
|
|
34
|
-
},
|
|
35
|
-
{} as Record<ColorToken, `var(--${string})`>,
|
|
36
|
-
);
|
|
37
|
-
|
|
38
30
|
export const spacing = Object.fromEntries(
|
|
39
31
|
Object.entries(foundations["spacing-scale"]).map(([key, value]) => [
|
|
40
32
|
key.replace("-", "."),
|