Skip to content

Commit a8537f8

Browse files
authored
Merge branch 'main' into fix/circular-reference-protection
2 parents be12df2 + 0437762 commit a8537f8

48 files changed

Lines changed: 677 additions & 1002 deletions

Some content is hidden

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

.changeset/tailwind-v4.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
"@geajs/ui": minor
3+
---
4+
5+
### @geajs/ui (minor)
6+
7+
- **Tailwind CSS v4**: Upgraded from Tailwind CSS v3 to v4. The CSS-based configuration system replaces the old JavaScript preset.
8+
- Migration steps:
9+
1. **Update dependencies**
10+
```bash
11+
npm install -D tailwindcss@4 @tailwindcss/vite
12+
npm uninstall autoprefixer postcss
13+
```
14+
15+
2. **Update `vite.config.ts`** — add `@tailwindcss/vite` as a plugin and remove any inline PostCSS Tailwind config
16+
```ts
17+
// vite.config.ts
18+
import tailwindcss from '@tailwindcss/vite'
19+
20+
export default defineConfig({
21+
plugins: [tailwindcss()],
22+
})
23+
```
24+
25+
3. **Remove the old `@geajs/ui` preset/PostCSS wiring** — delete `postcss.config.js` if it only existed for Tailwind v3, and only remove `tailwind.config.js` if it contained no project-specific configuration
26+
27+
4. **Remove `@tailwind` directives** from any CSS files you own (e.g. `@tailwind base; @tailwind components; @tailwind utilities;`). You don't have to `@import "tailwindcss";` in your own CSS, just make sure the theme file is imported first.
28+
```ts
29+
// main.ts
30+
import '@geajs/ui/style.css'
31+
import './style.css'
32+
```
33+
34+
5. **Remove manual CSS resets** — if your project has a global reset like `* { margin: 0; padding: 0; }` outside a CSS layer, it will silently override Tailwind v4 utilities. In v4, all utilities live inside `@layer utilities`, and unlayered styles always win over layered styles regardless of specificity. Tailwind v4's Preflight already handles these resets inside `@layer base`, so the manual reset is redundant and should be removed
35+
```css
36+
/* Remove this — Tailwind v4 Preflight already does it */
37+
* {
38+
margin: 0;
39+
padding: 0;
40+
box-sizing: border-box;
41+
}
42+
```
43+
44+
6. **Check [Tailwind CSS upgrade guide](https://tailwindcss.com/docs/upgrade-guide#changes-from-v3)** for needed changes in your custom components

.cursor/skills/gea-ui-components/SKILL.md

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,34 +15,21 @@ Read `reference.md` in this skill directory for the full component API with prop
1515

1616
```bash
1717
npm install @geajs/core @geajs/ui
18-
npm install -D vite @geajs/vite-plugin
18+
npm install -D vite @geajs/vite-plugin tailwindcss @tailwindcss/vite
1919
```
2020

2121
### Vite config
2222

2323
```js
2424
import { defineConfig } from 'vite'
2525
import { geaPlugin } from '@geajs/vite-plugin'
26+
import tailwindcss from '@tailwindcss/vite'
2627

2728
export default defineConfig({
28-
plugins: [geaPlugin()],
29+
plugins: [geaPlugin(), tailwindcss()],
2930
})
3031
```
3132

32-
### Tailwind config
33-
34-
```js
35-
import geaPreset from '@geajs/ui/tailwind-preset'
36-
37-
export default {
38-
presets: [geaPreset],
39-
content: [
40-
'./src/**/*.{tsx,ts,jsx,js}',
41-
'./node_modules/@geajs/ui/dist/**/*.js',
42-
],
43-
}
44-
```
45-
4633
### Import styles
4734

4835
```ts

.cursor/skills/react-to-gea-migration/reference.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ Complete side-by-side conversion reference with real examples from the Jira clon
5151
"type": "module",
5252
"dependencies": {
5353
"@geajs/core": "^1.0.0",
54-
"@geajs/ui": "^0.1.0"
54+
"@geajs/ui": "^0.2.0"
5555
},
5656
"devDependencies": {
5757
"@geajs/vite-plugin": "^1.0.0",
58+
"@tailwindcss/vite": "^4.0.0",
5859
"vite": "^8.0.0",
60+
"tailwindcss": "^4.0.0",
5961
"typescript": "~5.8.0"
6062
}
6163
}
@@ -92,6 +94,7 @@ import { dirname, resolve } from 'node:path'
9294
import { fileURLToPath } from 'node:url'
9395
import { defineConfig } from 'vite'
9496
import { geaPlugin } from '@geajs/vite-plugin'
97+
import tailwindcss from '@tailwindcss/vite'
9598
import { mockApiMiddleware } from './mock-api.ts'
9699

97100
const __dirname = dirname(fileURLToPath(import.meta.url))
@@ -100,6 +103,7 @@ export default defineConfig({
100103
root: __dirname,
101104
plugins: [
102105
geaPlugin(),
106+
tailwindcss(),
103107
{
104108
name: 'mock-api',
105109
configureServer(server) {
@@ -151,7 +155,7 @@ ReactDOM.render(<App />, document.getElementById('root'))
151155

152156
```ts
153157
import App from './App'
154-
import '../../../packages/gea-ui/src/styles/theme.css'
158+
import '@geajs/ui/style.css'
155159
import './styles.css'
156160

157161
const root = document.getElementById('app')
@@ -507,7 +511,7 @@ export default class Board extends Component {
507511
class={`board-filters-avatar ${filtersStore.userIds.includes(user.id) ? 'active' : ''}`}
508512
click={() => filtersStore.toggleUserId(user.id)}
509513
>
510-
<Avatar src={user.avatarUrl} name={user.name} class="!h-8 !w-8" />
514+
<Avatar src={user.avatarUrl} name={user.name} class="h-8! w-8!" />
511515
</div>
512516
))}
513517
</div>
@@ -555,7 +559,7 @@ Key differences:
555559
- Store is imported directly — no prop drilling or context consumers.
556560
- `<Fragment>` replaced with a wrapper `<div>`.
557561
- Route-based modal replaced with route params check in the parent `Project` layout.
558-
- `@geajs/ui` `Avatar` replaces custom Avatar with `src`, `name`, and CSS override `class="!h-8 !w-8"`.
562+
- `@geajs/ui` `Avatar` replaces custom Avatar with `src`, `name`, and CSS override `class="h-8! w-8!"`.
559563
- **Filter function is pure** — defined outside the component, takes all parameters explicitly. Store values are passed in from `template()` to ensure the compiler tracks them.
560564

561565
### Gea: Project as a router layout with `page` prop
@@ -814,10 +818,10 @@ Important: `@geajs/ui` Select always uses array values. For single select, wrap
814818
```tsx
815819
import { Avatar } from '@geajs/ui'
816820

817-
<Avatar src={user.avatarUrl} name={user.name} class="!h-8 !w-8" />
821+
<Avatar src={user.avatarUrl} name={user.name} class="h-8! w-8!" />
818822
```
819823

820-
Use CSS `!important` (or Tailwind's `!` prefix if using Tailwind) to override default Avatar dimensions.
824+
Use CSS `!important` (or Tailwind's `!` suffix if using Tailwind) to override default Avatar dimensions.
821825

822826
### Using Link with onNavigate
823827

@@ -1103,10 +1107,10 @@ export const StyledButton = styled(Button)`
11031107
.avatar-sm { width: 32px !important; height: 32px !important; }
11041108
```
11051109

1106-
Or if using Tailwind, use the `!` prefix shorthand:
1110+
Or if using Tailwind, use the `!` suffix shorthand:
11071111

11081112
```tsx
1109-
<Avatar src={user.avatarUrl} name={user.name} class="!h-8 !w-8" />
1113+
<Avatar src={user.avatarUrl} name={user.name} class="h-8! w-8!" />
11101114
```
11111115

11121116
---
@@ -1508,7 +1512,7 @@ export default class IssueCard extends Component {
15081512
</div>
15091513
<div class="issue-card-footer-right">
15101514
{assignees.map((user: any) => (
1511-
<Avatar key={user.id} src={user.avatarUrl} name={user.name} class="!h-6 !w-6" />
1515+
<Avatar key={user.id} src={user.avatarUrl} name={user.name} class="h-6! w-6!" />
15121516
))}
15131517
</div>
15141518
</div>
@@ -1715,7 +1719,7 @@ export default class CommentCreate extends Component {
17151719
{!this.isFormOpen && (
17161720
<div class="comment-create-collapsed">
17171721
<div class="comment-create-fake" click={() => this.openForm()}>
1718-
<Avatar src={user?.avatarUrl} name={user?.name || ''} class="!h-8 !w-8" />
1722+
<Avatar src={user?.avatarUrl} name={user?.name || ''} class="h-8! w-8!" />
17191723
<span class="comment-create-placeholder">Add a comment...</span>
17201724
</div>
17211725
<p class="comment-pro-tip">
@@ -2093,7 +2097,7 @@ When porting styled-components to plain CSS, copy every value verbatim — `17px
20932097

20942098
### 13. Overriding `@geajs/ui` component dimensions
20952099

2096-
`@geajs/ui` components have default dimensions. Override them with `!important` in your CSS, or with Tailwind's `!` prefix if using Tailwind:
2100+
`@geajs/ui` components have default dimensions. Override them with `!important` in your CSS, or with Tailwind's `!` suffix if using Tailwind:
20972101

20982102
```css
20992103
.avatar-sm { width: 32px !important; height: 32px !important; }

docs/gea-ui/getting-started.md

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,78 +14,51 @@ npm install @geajs/core
1414

1515
## Tailwind CSS Setup
1616

17-
@geajs/ui uses [Tailwind CSS](https://tailwindcss.com/) **v3** for styling and ships a preset that defines the design token system (colors, border radius, etc.).
17+
@geajs/ui uses [Tailwind CSS](https://tailwindcss.com/) **v4** for styling. The theme CSS shipped with the library sets up Tailwind and all design tokens.
1818

19-
::: warning
20-
Tailwind CSS v4 is **not yet supported**. Make sure you install Tailwind CSS v3.
21-
:::
22-
23-
### 1. Install Tailwind CSS v3
19+
### 1. Install Tailwind and its Vite plugin
2420

2521
```bash
26-
npm install -D tailwindcss@3 postcss autoprefixer
27-
```
28-
29-
Create a `postcss.config.js` in your project root:
30-
31-
```js
32-
// postcss.config.js
33-
export default {
34-
plugins: {
35-
tailwindcss: {},
36-
autoprefixer: {},
37-
},
38-
}
22+
npm install -D tailwindcss @tailwindcss/vite
3923
```
4024

41-
### 2. Add the Tailwind Preset
25+
Add the plugin to your `vite.config.ts`:
4226

43-
```js
44-
// tailwind.config.js
45-
import geaPreset from '@geajs/ui/tailwind-preset'
27+
```ts
28+
import tailwindcss from '@tailwindcss/vite'
29+
import { defineConfig } from 'vite'
4630

47-
export default {
48-
presets: [geaPreset],
49-
content: [
50-
'./src/**/*.{tsx,ts,jsx,js}',
51-
'./node_modules/@geajs/ui/dist/**/*.mjs',
52-
],
53-
}
31+
export default defineConfig({
32+
plugins: [tailwindcss()],
33+
})
5434
```
5535

56-
The preset configures:
57-
58-
- **Semantic colors**`primary`, `secondary`, `destructive`, `muted`, `accent`, `popover`, `card`, `border`, `input`, `ring`, `background`, `foreground` — all driven by CSS custom properties.
59-
- **Border radius**`lg`, `md`, `sm` tokens tied to a single `--radius` variable.
60-
- **Dark mode** — enabled via the `dark` class strategy.
61-
62-
### 3. Import the Theme CSS
36+
### 2. Import the Theme CSS
6337

64-
The theme stylesheet defines the CSS custom properties that the preset references. Import it once in your entry point:
38+
Import the theme stylesheet once in your entry point. It sets up Tailwind, all semantic color tokens, and dark mode support:
6539

6640
```ts
6741
// main.ts
6842
import '@geajs/ui/style.css'
6943
```
7044

71-
You also need a CSS file that includes Tailwind's directives. Create a `src/style.css` (or similar) and import it in your entry point:
45+
### 3. (Optional) Extend with Your Own CSS
46+
47+
If you need additional Tailwind utilities or custom styles, create a CSS file and import it after the theme:
7248

7349
```css
7450
/* src/style.css */
75-
@tailwind base;
76-
@tailwind components;
77-
@tailwind utilities;
51+
/* your custom theme extensions or utilities */
7852
```
7953

8054
```ts
8155
// main.ts
56+
// make sure the theme is imported first
8257
import '@geajs/ui/style.css'
8358
import './style.css'
8459
```
8560

86-
### 4. Include @geajs/ui in Tailwind's Content Paths
87-
88-
Make sure `node_modules/@geajs/ui/dist/**/*.mjs` is listed in your `content` array (shown above). This allows Tailwind to scan @geajs/ui's component source and include the utility classes they use.
61+
You don't have to `@import "tailwindcss";` in your CSS file, it is already loaded by the theme.
8962

9063
## Minimal Example
9164

docs/gea-ui/theming.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ The theme stylesheet (`@geajs/ui/style.css`) defines variables in HSL format on
4040
}
4141
```
4242

43-
Each variable holds the HSL values without the `hsl()` wrapper. The Tailwind preset references them as `hsl(var(--primary))`, so you only need to provide the three numbers (hue, saturation, lightness).
43+
Each variable holds the HSL values without the `hsl()` wrapper. The theme CSS maps them to Tailwind utilities via `@theme inline` (e.g. `hsl(var(--primary))`), so you only need to provide the three numbers (hue, saturation, lightness).
4444

4545
## Available Tokens
4646

@@ -147,13 +147,14 @@ Styled components also apply semantic class names (`dialog-trigger`, `tabs-conte
147147

148148
## Border Radius
149149

150-
The `--radius` variable controls the base radius. The Tailwind preset derives three tokens from it:
150+
The `--radius` variable controls the base radius. The theme derives four tokens from it:
151151

152152
| Token | Value |
153153
| --- | --- |
154154
| `rounded-lg` | `var(--radius)` |
155155
| `rounded-md` | `calc(var(--radius) - 2px)` |
156156
| `rounded-sm` | `calc(var(--radius) - 4px)` |
157+
| `rounded-xs` | `calc(var(--radius) - 6px)` |
157158

158159
Change `--radius` once and all components update:
159160

examples/chat/styles.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
* {
2-
margin: 0;
3-
padding: 0;
4-
box-sizing: border-box;
5-
}
6-
71
body {
82
font-family: 'Inter', system-ui, sans-serif;
93
background: hsl(var(--background));

examples/dashboard/styles.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
* {
2-
margin: 0;
3-
padding: 0;
4-
box-sizing: border-box;
5-
}
6-
71
body {
82
font-family: 'Inter', system-ui, sans-serif;
93
background-color: hsl(var(--background));

examples/docs/app.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,15 +2076,15 @@ export default class App extends Component {
20762076
<Dialog title="Confirm Delete" description="This cannot be undone." triggerLabel="Open Dialog">
20772077
<div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end', marginTop: '1rem' }}>
20782078
<button
2079-
class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 text-xs"
2079+
class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground h-8 rounded-md px-3 text-xs"
20802080
type="button"
20812081
data-part="close-trigger"
20822082
click={() => (this.dialogAction = 'Cancel clicked')}
20832083
>
20842084
Cancel
20852085
</button>
20862086
<button
2087-
class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90 h-8 rounded-md px-3 text-xs"
2087+
class="inline-flex items-center justify-center gap-2 whitespace-nowrap font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90 h-8 rounded-md px-3 text-xs"
20882088
type="button"
20892089
data-part="close-trigger"
20902090
click={() => (this.dialogAction = 'Delete clicked')}

examples/docs/styles.css

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
* {
2-
margin: 0;
3-
padding: 0;
4-
box-sizing: border-box;
5-
}
6-
71
body {
82
font-family: 'Inter', system-ui, sans-serif;
93
background-color: hsl(var(--background));

0 commit comments

Comments
 (0)