@ponti-studios/ui 0.4.0 → 0.4.1

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 CHANGED
@@ -25,3 +25,17 @@ pnpm config set --location=user //registry.npmjs.org/:_authToken "$NODE_AUTH_TOK
25
25
  ```
26
26
 
27
27
  CI consumers should use a secret-backed npm token with read access.
28
+
29
+ ### Ships TypeScript source, not a bundle
30
+
31
+ 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). This is deliberate: Tailwind v4 class detection for consumers of `./styles.css` or `./tokens.css` requires an `@source` directive pointing at the installed package's `src/`, e.g.
32
+
33
+ ```css
34
+ @source "../../../node_modules/@ponti-studios/ui/src";
35
+ ```
36
+
37
+ 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.
38
+
39
+ ### `./native` subpath
40
+
41
+ `@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ponti-studios/ui",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "description": "Shared UI component library for ponti-studios labs",
6
6
  "license": "MIT",
@@ -14,13 +14,13 @@
14
14
  "access": "public"
15
15
  },
16
16
  "files": [
17
- "dist",
18
17
  "src",
19
18
  "THIRD_PARTY_NOTICES.md"
20
19
  ],
21
20
  "type": "module",
22
21
  "sideEffects": [
23
22
  "./src/styles.css",
23
+ "./src/tokens.css",
24
24
  "./src/animations.css"
25
25
  ],
26
26
  "exports": {
@@ -64,15 +64,24 @@
64
64
  "types": "./src/tokens/index.ts",
65
65
  "import": "./src/tokens/index.ts"
66
66
  },
67
+ "./tokens/css-vars": {
68
+ "types": "./src/tokens/css-vars.ts",
69
+ "import": "./src/tokens/css-vars.ts"
70
+ },
71
+ "./native": {
72
+ "types": "./src/native/index.ts",
73
+ "import": "./src/native/index.ts"
74
+ },
67
75
  "./hooks": {
68
76
  "types": "./src/hooks/index.ts",
69
77
  "import": "./src/hooks/index.ts"
70
78
  },
71
79
  "./styles.css": "./src/styles.css",
80
+ "./tokens.css": "./src/tokens.css",
72
81
  "./animations.css": "./src/animations.css"
73
82
  },
74
83
  "scripts": {
75
- "build": "vite build",
84
+ "build": "tsc --noEmit",
76
85
  "dev": "vite",
77
86
  "storybook": "style-dictionary build --config style-dictionary.dark-storybook.json && storybook dev -p 6006",
78
87
  "check:source": "node tokens/check-source.mjs",
@@ -82,7 +91,7 @@
82
91
  "typecheck": "tsc --noEmit",
83
92
  "lint": "oxlint src --fix",
84
93
  "format": "oxfmt src --write",
85
- "prepack": "pnpm run tokens:check && pnpm run check:source && pnpm run typecheck && pnpm run build"
94
+ "prepack": "pnpm run tokens:check && pnpm run check:source && pnpm run typecheck"
86
95
  },
87
96
  "dependencies": {
88
97
  "@base-ui/react": "^1.6.0",
@@ -115,6 +124,7 @@
115
124
  "playwright": "catalog:",
116
125
  "react": "^19.2.7",
117
126
  "react-dom": "^19.2.7",
127
+ "react-native": "0.86.0",
118
128
  "storybook": "^10.5.3",
119
129
  "style-dictionary": "5.5.0",
120
130
  "tailwindcss": "catalog:",
@@ -124,7 +134,16 @@
124
134
  },
125
135
  "peerDependencies": {
126
136
  "react": "^18.2.0 || ^19.0.0",
127
- "react-dom": "^18.2.0 || ^19.0.0"
137
+ "react-dom": "^18.2.0 || ^19.0.0",
138
+ "react-native": ">=0.74"
128
139
  },
129
- "packageManager": "pnpm@10.30.3"
140
+ "packageManager": "pnpm@10.30.3",
141
+ "peerDependenciesMeta": {
142
+ "react-dom": {
143
+ "optional": true
144
+ },
145
+ "react-native": {
146
+ "optional": true
147
+ }
148
+ }
130
149
  }
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 "./tokens/css-vars";
12
13
  export {
13
14
  animations,
14
- colors,
15
15
  colorThemes,
16
16
  colorTokenNames,
17
17
  containers,
@@ -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 "../tokens";
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 "../tokens";
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
+ }
package/src/styles.css CHANGED
@@ -1,7 +1,5 @@
1
1
  @import "tailwindcss";
2
- @import "./tokens/generated/foundations.css";
3
- @import "./tokens/generated/light.css";
4
- @import "./tokens/generated/dark.css" (prefers-color-scheme: dark);
2
+ @import "./tokens.css";
5
3
  @import "./animations.css";
6
4
 
7
5
  /* shadcn New York semantic contract. Values are generated from the DTCG
@@ -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 <code className="font-mono">
82
- chart-5
83
- </code>{" "}
84
- are an ordinal palette for arbitrary categorical series (e.g. one line per country) —
85
- there's no fixed meaning to a given slot, so they're assigned by index, not by what the
86
- data represents. That's different from{" "}
87
- <code className="font-mono">chart-positive</code>/<code className="font-mono">
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
- the palette stays side by side.
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) => (
@@ -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
+ );
@@ -26,4 +26,5 @@ export default {
26
26
  "chart-positive": "#30a46c",
27
27
  "chart-negative": "#e5484d",
28
28
  "chart-neutral": "#5a6169",
29
+ "overlay-scrim": "#000000",
29
30
  };
@@ -26,4 +26,5 @@
26
26
  --chart-positive: #30a46c;
27
27
  --chart-negative: #e5484d;
28
28
  --chart-neutral: #5a6169;
29
+ --overlay-scrim: #000000;
29
30
  }
@@ -26,4 +26,5 @@ export default {
26
26
  "chart-positive": "#30a46c",
27
27
  "chart-negative": "#e5484d",
28
28
  "chart-neutral": "#b9bbc6",
29
+ "overlay-scrim": "#000000",
29
30
  };
@@ -26,4 +26,5 @@
26
26
  --chart-positive: #30a46c;
27
27
  --chart-negative: #e5484d;
28
28
  --chart-neutral: #b9bbc6;
29
+ --overlay-scrim: #000000;
29
30
  }
@@ -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("-", "."),
package/src/tokens.css ADDED
@@ -0,0 +1,3 @@
1
+ @import "./tokens/generated/foundations.css";
2
+ @import "./tokens/generated/light.css";
3
+ @import "./tokens/generated/dark.css" (prefers-color-scheme: dark);