Skip to content

Commit 3f1e4b9

Browse files
authored
feat: Move StartupJS UI theme system to CSSX (#41)
## Summary - migrate StartupJS UI to the CSSX-first theme model backed by CSS variables, component tag selectors, and `:part()` overrides - compose `UiProvider` from CSSX Tailwind tokens, CSSX shadcn semantic tokens, and `startupjsUiTheme.cssx.css`, with app-provided `StartupjsProvider style` layered after UI defaults - replace the old palette/Colors/CssVariables theme runtime with normal CSSX `:root` variables and semantic `--color-*` tokens - remove public `useThemeColor()`, `getThemeColor()`, `getThemeColorVariableName()`, `Colors`, `Palette`, `CssVariables`, `generateColors`, and `useColors` APIs - keep `@startupjs-ui/core` as a tiny non-style package for the `UIRole` type only - refactor component JS color reads to CSSX `useCssColor()` / `useCssVariable()` primitives re-exported from `startupjs` - inline all component, demo, MDX renderer, and docs styles into local `css` templates; only the package-level `startupjsUiTheme.cssx.css` theme asset remains - replace Stylus helpers, `$UI`, `u`, `:export config`, and separate `.cssx.styl` files with CSS variables, `rem`, `color-mix()`, `oklch()`, and CSSX custom media - preserve component customization through `themed(name, Component)`, component tag selectors, semantic `part` props, and per-instance `style` / `*Style` props - update documentation to present `StartupjsProvider style`, CSSX tokens, tag overrides, and `:part()` as the customization model - keep the generic Styling guide as the canonical theming doc while component pages document only their own parts and variables/defaults - memoize the UI provider style layer array so provider context does not churn when `style` is unchanged - improve generated part reference extraction for conditional part arrays such as TextInput icon parts ## Why CSSX now owns provider-scoped `:root` variables, global utility classes, component tag selectors, `:part()` / `::part()` overrides, subscribed CSS variable reads, OKLCH evaluation, and `color-mix()`. Keeping palette generation, theme-color hooks, theme context, component override wiring, or style helpers inside startupjs-ui would duplicate that model and keep theming in the wrong layer. With this change, the StartupJS UI theme is normal CSSX CSS: ```css :root { --color-primary: oklch(0.55 0.22 260); --Button-radius: var(--radius-md); } Button { border-radius: 12px; } Button:part(text) { color: var(--color-primary-foreground); } ``` Component color props still accept tokens such as `primary`, which map to `--color-primary`. Raw CSS values and `var(--x)` remain valid where component props accept raw colors. ## Related PRs - startupjs/cssx#5 adds the unified CSSX compiler/runtime, `CssxProvider`, provider-scoped `:root`, component tag selectors, CSS variable/color hooks, OKLCH/color-mix evaluation, custom media, runtime CSS support, and source-condition types. - startupjs/startupjs#1327 wires `StartupjsProvider style` and `theme` into CSSX so framework users can configure this without importing CSSX directly. ## Validation - `node scripts/update-style-reference-docs.mjs` idempotency check - `yarn generate-package-dts` - `node scripts/check-startupjs-ui-exports.mjs` - `yarn build` for the docs app - `git diff --check` - CSSX docs/provider smoke with local source links: - generic `/docs/Styling` theming guide renders and remains the canonical styling model doc - `/docs/TextInput` renders and includes conditional `inputIconLeft` / `inputIconRight` parts - component docs for Button, Item, Div, and DateTimePicker render their generated Styling reference sections - component reference sections list parts and component-specific variables/defaults and link back to the generic Styling guide - no unknown CSS color token warnings or page errors observed - Storybook runtime smoke through `http://localhost:4400/iframe.html`: - Introduction/Overview - Actions/Button - Data/Item - Display/Badge - Feedback/Modal - Overlay/Popover - System/StartupJS UI - Inputs/TextInput - Inputs/DateTimePicker - Visual screenshots checked for Styling, Button, Item, DateTimePicker, provider, and popover pages/stories. - Known during DateTimePicker smoke: Moment Timezone logs its existing missing timezone-data warning; no CSS token, page, or provider errors were observed. - Previous validation in this draft also included `yarn install --immutable --mode=skip-build`, targeted eslint over migrated packages/docs slices, and direct CSS-template compile smoke over `packages/` plus `startupjsUiTheme.cssx.css`. ## Review Focus - token naming and override surface in `startupjsUiTheme.cssx.css` - remaining public API compatibility that should intentionally stay or be removed for the breaking release - `part` coverage on roots and semantically useful sub-elements - JS-only color bridge usage where CSS variables could replace code - docs clarity around `StartupjsProvider style`, CSSX variables, component tag selectors, and `:part()` overrides
1 parent b11b137 commit 3f1e4b9

397 files changed

Lines changed: 7682 additions & 5453 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ yarn-error.log
2626
# documentation
2727
doc_build
2828
docs/dist
29+
docs/teamplay-env.d.ts
30+
teamplay-env.d.ts
2931
storybook/dist
3032
storybook/storybook-static
3133
storybook/test-results

docs/Styling.mdx

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
# Styling and theming
2+
3+
StartupJS UI is styled through CSSX. The UI package provides default theme layers, and your app customizes them by passing CSSX styles to `StartupjsProvider`.
4+
5+
```jsx
6+
import { css, StartupjsProvider } from 'startupjs'
7+
8+
const appStyle = css`
9+
:root {
10+
--primary: oklch(0.58 0.22 260);
11+
--color-primary: var(--primary);
12+
--Button-radius: 9999px;
13+
}
14+
15+
Button {
16+
min-width: 10rem;
17+
}
18+
19+
Button:part(text) {
20+
font-weight: var(--font-weight-semibold);
21+
}
22+
`
23+
24+
export default function RootLayout ({ children }) {
25+
return (
26+
<StartupjsProvider style={appStyle}>
27+
{children}
28+
</StartupjsProvider>
29+
)
30+
}
31+
```
32+
33+
## Theme layers
34+
35+
When `startupjs-ui` is installed, its plugin adds the UI provider inside `StartupjsProvider`. The effective style order is:
36+
37+
1. CSSX Tailwind token theme
38+
2. CSSX shadcn semantic theme
39+
3. StartupJS UI component token theme
40+
4. your `StartupjsProvider style`
41+
5. component-local styles
42+
6. per-instance `style` and `*Style` props
43+
44+
Bare StartupJS does not include UI themes unless `startupjs-ui` is installed.
45+
46+
## Override order
47+
48+
Prefer customization in this order:
49+
50+
1. Change CSS variables in `:root`.
51+
2. Use component tag selectors such as `Button`, `Item`, or `TextInput`.
52+
3. Use `:part()` / `::part()` selectors for meaningful inner elements.
53+
4. Use per-instance `style` and `*Style` props when a single component instance needs an exception.
54+
55+
Component docs include a “Styling reference” section listing the available parts and component-specific variables with their defaults.
56+
57+
## CSS variables
58+
59+
Use CSS variables for theme values that should affect multiple components. StartupJS UI uses semantic color variables first, so brand changes usually start with `--primary`, `--primary-foreground`, and their `--color-*` mappings.
60+
61+
```jsx
62+
const appStyle = css`
63+
:root {
64+
--primary: oklch(0.55 0.2 250);
65+
--primary-foreground: oklch(0.98 0.02 250);
66+
--color-primary: var(--primary);
67+
--color-primary-foreground: var(--primary-foreground);
68+
}
69+
`
70+
```
71+
72+
Component-specific variables use the component name as a prefix:
73+
74+
```css
75+
:root {
76+
--Button-height-m: 2.25rem;
77+
--TextInput-border-color-focused: var(--color-primary);
78+
--User-color-online: var(--color-success);
79+
}
80+
```
81+
82+
Use the reference files when you need the full token list:
83+
84+
- CSSX Tailwind tokens: `cssxjs/themes/tailwind`
85+
- CSSX shadcn semantic tokens: `cssxjs/themes/shadcn`
86+
- StartupJS UI component tokens: `packages/startupjs-ui/startupjsUiTheme.cssx.css`
87+
88+
## Light and dark
89+
90+
`StartupjsProvider theme` is forwarded to CSSX. The default is `auto`, which uses the device color scheme when a dark theme is available.
91+
92+
```jsx
93+
<StartupjsProvider theme='auto' style={appStyle}>
94+
<App />
95+
</StartupjsProvider>
96+
```
97+
98+
Define default variables in `:root` and dark overrides in `:root.dark`:
99+
100+
```css
101+
:root {
102+
--card: oklch(1 0 0);
103+
--card-foreground: oklch(0.145 0 0);
104+
--color-card: var(--card);
105+
--color-card-foreground: var(--card-foreground);
106+
}
107+
108+
:root.dark {
109+
--card: oklch(0.205 0 0);
110+
--card-foreground: oklch(0.985 0 0);
111+
}
112+
```
113+
114+
Set `theme='default'` or `theme='light'` to disable automatic dark selection. Set `theme='dark'` to force the dark theme.
115+
116+
## Component selectors
117+
118+
Every themable component registers a component tag name. Use that tag in provider styles to change defaults for all instances.
119+
120+
```css
121+
Button {
122+
border-radius: 9999px;
123+
}
124+
125+
Item {
126+
padding-left: 1.25rem;
127+
padding-right: 1.25rem;
128+
}
129+
```
130+
131+
These selectors apply globally within the provider subtree. They do not require a class on each component instance.
132+
133+
## Parts
134+
135+
Parts expose semantic inner elements. Use `:part()` or `::part()` to customize them from provider styles.
136+
137+
```css
138+
Button:part(icon) {
139+
opacity: 0.8;
140+
}
141+
142+
TextInput:part(input) {
143+
color: var(--color-foreground);
144+
}
145+
```
146+
147+
In JSX, the same parts map to regular props:
148+
149+
- `part='root'` maps to `style`
150+
- `part='icon'` maps to `iconStyle`
151+
- `part='text'` maps to `textStyle`
152+
153+
Use those props for one-off instance overrides:
154+
155+
```jsx
156+
<Button textStyle={{ textTransform: 'uppercase' }}>
157+
Save
158+
</Button>
159+
```
160+
161+
## Color props
162+
163+
Color props accept CSSX color tokens, raw CSS colors, or `var(...)` expressions.
164+
165+
```jsx
166+
<Button color='primary'>Primary</Button>
167+
<Tag color='warning'>Warning</Tag>
168+
<Loader color='var(--color-muted-foreground)' />
169+
```
170+
171+
Token names resolve through `--color-*` variables. For example, `primary` resolves through `--color-primary`, and `primary-foreground` resolves through `--color-primary-foreground`.
172+
173+
## Media and responsive styles
174+
175+
StartupJS UI uses CSSX custom media aliases. The default breakpoint aliases are:
176+
177+
- `--breakpoint-mobile`
178+
- `--breakpoint-tablet`
179+
- `--breakpoint-desktop`
180+
- `--breakpoint-wide`
181+
182+
```css
183+
@media (--breakpoint-tablet) {
184+
Button {
185+
min-width: 12rem;
186+
}
187+
}
188+
```
189+
190+
Theme-specific styles use CSSX theme media aliases:
191+
192+
```css
193+
@media (--theme-dark) {
194+
Card {
195+
box-shadow: none;
196+
border-color: var(--color-border);
197+
}
198+
}
199+
```
200+
201+
## Runtime CSS
202+
203+
Provider `style` can be a single CSSX sheet or an array of sheets. This makes it possible to combine app tokens, feature-level overrides, and generated CSS.
204+
205+
```jsx
206+
<StartupjsProvider style={[brandStyle, featureStyle]}>
207+
<App />
208+
</StartupjsProvider>
209+
```
210+
211+
Later entries override earlier entries at the same priority layer.

docs/app/_layout.js

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
11
import { SafeAreaProvider, SafeAreaView } from 'react-native-safe-area-context'
2+
import { useColorScheme } from 'react-native'
23
import { StartupjsProvider } from 'startupjs'
3-
import Portal from '@startupjs-ui/portal'
4-
import { DialogsProvider } from '@startupjs-ui/dialogs'
5-
import { ToastProvider } from '@startupjs-ui/toast'
4+
import UiProvider from 'startupjs-ui/UiProvider'
65
import { Stack } from 'expo-router'
76

87
export default function RootLayout () {
8+
const colorScheme = useColorScheme()
9+
const backgroundColor = colorScheme === 'dark' ? '#171717' : '#ffffff'
10+
911
return (
1012
<SafeAreaProvider>
11-
<SafeAreaView style={{ flex: 1, backgroundColor: 'white' }}>
12-
<StartupjsProvider>
13-
<Portal.Provider>
14-
<ToastProvider />
13+
<SafeAreaView style={{ flex: 1, backgroundColor }}>
14+
<StartupjsProvider theme='auto'>
15+
<UiProvider theme='auto'>
1516
<Stack
1617
screenOptions={{
17-
contentStyle: { backgroundColor: 'white' },
18+
contentStyle: { backgroundColor },
1819
headerShown: false
1920
}}
2021
/>
21-
</Portal.Provider>
22-
<DialogsProvider />
22+
</UiProvider>
2323
</StartupjsProvider>
2424
</SafeAreaView>
2525
</SafeAreaProvider>

docs/app/docs/MigrationGuides04.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '../../migration-guides/0.4.md'

docs/app/docs/Styling.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default } from '../../Styling.mdx'

0 commit comments

Comments
 (0)