Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .augment/rules/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"sharedRules": [
{
"path": "AGENTS.md",
"type": "manual"
}
]
}
6 changes: 6 additions & 0 deletions .changeset/cool-carrots-tease.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@hashintel/ds-helpers": patch
"@hashintel/ds-theme": patch
---

Refinements to figma-variable-to-token transformation logic, to skip WIP/hidden properties from the Figma
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ blocks/**/.env
**/mise*.local.toml
**/mise/config*.local.toml

# agentprofiles (temporary, until I can switch to using `mise.local.toml` files)
.envrc
.envrc.agentprofiles

# macOS directory file
**/.DS_Store

Expand Down
49 changes: 21 additions & 28 deletions libs/@hashintel/ds-components/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,6 @@ React component library built with **Panda CSS** and **Ark UI**. Components cons
- **ds-helpers**: Generates and exports styled-system utilities (`css()`, `cva()`, `token()`, JSX components)
- **ds-components**: Imports from ds-helpers, wraps Ark UI primitives with styled components

### Reference Implementation

This package aims to follow patterns from **Park UI** (https://park-ui.com), the official Ark UI + Panda CSS integration:

- Park UI preset: `/Users/lunelson/Code/chakra-ui/park-ui/packages/preset`
- Park UI React components: `/Users/lunelson/Code/chakra-ui/park-ui/components/react`

## Panda CSS Configuration

### panda.config.ts
Expand Down Expand Up @@ -54,13 +47,13 @@ Key points:

With `strictTokens: true`, you must use the exact token names:

| Token Type | ❌ Invalid | βœ… Valid |
|------------|-----------|----------|
| Spacing | `spacing.4`, `"4"` | `default.4`, `compact.4`, `comfortable.4` |
| Radii | `radius.2`, `md` | `md.2`, `sm.3`, `lg.full`, `component.button.sm` |
| FontSize | `size.textsm` | `sm`, `xs`, `base`, `lg`, `xl`, `2xl` |
| LineHeight | `leading.none.textsm` | `none.text-sm`, `normal.text-base` |
| Arbitrary values | `64px` | `[64px]` |
| Token Type | ❌ Invalid | βœ… Valid |
| ---------------- | --------------------- | ------------------------------------------------ |
| Spacing | `spacing.4`, `"4"` | `default.4`, `compact.4`, `comfortable.4` |
| Radii | `radius.2`, `md` | `md.2`, `sm.3`, `lg.full`, `component.button.sm` |
| FontSize | `size.textsm` | `sm`, `xs`, `base`, `lg`, `xl`, `2xl` |
| LineHeight | `leading.none.textsm` | `none.text-sm`, `normal.text-base` |
| Arbitrary values | `64px` | `[64px]` |

The token types are defined in `@hashintel/ds-helpers/types`.

Expand Down Expand Up @@ -125,14 +118,14 @@ surface.{default,subtle,muted,emphasis,alt,inverted}

When updating components, use this mapping:

| Old (incorrect) | New (correct) |
|--------------------------|----------------------------|
| `bg.brand.*` | `bg.accent.*` |
| `core.gray.20` | `gray.20` |
| `core.red.50` | `red.50` |
| `core.custom.30` | `accent.30` |
| `text.linkhover` | `text.linkHover` |
| `text.semantic.critical` | `text.status.critical` |
| Old (incorrect) | New (correct) |
| ------------------------ | ---------------------- |
| `bg.brand.*` | `bg.accent.*` |
| `core.gray.20` | `gray.20` |
| `core.red.50` | `red.50` |
| `core.custom.30` | `accent.30` |
| `text.linkhover` | `text.linkHover` |
| `text.semantic.critical` | `text.status.critical` |

## Component Patterns

Expand Down Expand Up @@ -197,12 +190,12 @@ export const Checkbox = (props) => (

## Scripts

| Script | Description |
|--------|-------------|
| `yarn storybook` | Start Storybook dev server |
| `yarn storybook:build` | Build static Storybook |
| `yarn build` | Build component library with Vite |
| `yarn lint:tsc` | TypeScript type checking |
| Script | Description |
| ---------------------------- | ------------------------------------ |
| `yarn storybook` | Start Storybook dev server |
| `yarn storybook:build` | Build static Storybook |
| `yarn build` | Build component library with Vite |
| `yarn lint:tsc` | TypeScript type checking |
| `yarn panda codegen --clean` | Regenerate styled-system from preset |

## File Structure
Expand Down
286 changes: 286 additions & 0 deletions libs/@hashintel/ds-components/_ai/park-ui-porting-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,286 @@
# Park UI Porting Guide

This document captures findings from analyzing park-ui's architecture to establish a repeatable process for porting components to `@hashintel/ds-components`.

## Reference Repositories

| Repository | Local Path | Description |
|------------|------------|-------------|
| chakra-ui/ark | `~/Clones/chakra-ui/ark` | Headless component primitives |
| chakra-ui/panda | `~/Clones/chakra-ui/panda` | CSS-in-JS framework |
| chakra-ui/park-ui | `~/Clones/chakra-ui/park-ui` | Styled components (our reference) |

## Park UI Architecture

### Package Structure

```
park-ui/
β”œβ”€β”€ packages/preset/ # Panda CSS preset with recipes + tokens
β”‚ └── src/
β”‚ β”œβ”€β”€ recipes/ # Slot recipes (defineSlotRecipe)
β”‚ └── theme/
β”‚ β”œβ”€β”€ colors/ # Per-palette semantic tokens
β”‚ └── tokens/ # Base tokens
└── components/react/ # React component wrappers
└── src/components/ui/ # Components using createStyleContext
```

### Slot Recipes Pattern

Recipes use `defineSlotRecipe` with anatomy from Ark UI:

```ts
import { checkboxAnatomy } from '@ark-ui/react/anatomy'
import { defineSlotRecipe } from '@pandacss/dev'

export const checkbox = defineSlotRecipe({
slots: checkboxAnatomy.keys(),
className: 'checkbox',
base: {
root: { /* styles */ },
control: { /* styles */ },
label: { /* styles */ },
},
variants: {
size: { sm: {}, md: {}, lg: {} },
variant: { solid: {}, subtle: {}, outline: {} },
},
defaultVariants: { variant: 'solid', size: 'md' },
})
```

### Component Pattern

Components wrap Ark UI primitives with style context:

```tsx
import { Checkbox } from '@ark-ui/react/checkbox'
import { createStyleContext } from 'styled-system/jsx'
import { checkbox } from 'styled-system/recipes'

const { withProvider, withContext } = createStyleContext(checkbox)

export const Root = withProvider(Checkbox.Root, 'root')
export const Control = withContext(Checkbox.Control, 'control')
export const Label = withContext(Checkbox.Label, 'label')
```

### Testing Pattern

Park UI uses **Storybook examples only** β€” no unit tests. Stories are organized as:
- `/examples/{component}/{component}.stories.tsx` β€” story entry
- `/examples/{component}/basic.tsx`, `variants.tsx`, etc. β€” individual examples

---

## Color Token Architecture

### Radix Color Scale (1-12)

Park UI uses Radix colors with semantic meaning per step:

| Steps | Purpose |
|-------|---------|
| 1-2 | Backgrounds (app, subtle) |
| 3-5 | Interactive UI (default, hover, active) |
| 6-8 | Borders (subtle, default, emphasis) |
| 9-10 | Solid backgrounds (default, hover) |
| 11-12 | Text (low-contrast, high-contrast) |

Plus alpha variants `a1-a12` for translucent overlays.

### HASH Gray Scale Mapping

Established mapping from Radix neutral β†’ HASH gray:

| Radix | HASH | Purpose |
|-------|------|---------|
| 1 | 10 | Lightest background |
| 2 | 10 | Subtle background |
| 3 | 20 | UI element background |
| 4 | 20 | Hovered UI element |
| 5 | 30 | Active/selected UI |
| 6 | 30 | Subtle borders |
| 7 | 40 | Border / focus ring |
| 8 | 40 | Solid border |
| 9 | 50 | Solid backgrounds |
| 10 | 50 | Hovered solid |
| 11 | 60 | Low-contrast text |
| 12 | 90 | High-contrast text |

Visual comparison story: `libs/@hashintel/ds-helpers/stories/colors.radix-mapping.story.tsx`

### Alpha β†’ Solid Mapping

Since HASH doesn't have alpha variants, alpha references map to solid equivalents:

| Radix Alpha | HASH Solid |
|-------------|------------|
| a1-a2 | 00 |
| a3-a4 | 10 |
| a5-a6 | 20 |
| a7 | 30 |
| a8 | 40 |
| a9-a10 | 50 |
| a11 | 60 |
| a12 | 90 |

**Limitation**: Loses translucency effects for hover states and layered surfaces.

---

## Semantic Token Variants

Park UI defines 5 semantic variants per color palette:

### 1. `solid`
Bold, primary actions (buttons, badges)
```
bg.DEFAULT β†’ 9
bg.hover β†’ 10
fg β†’ white
```

### 2. `subtle`
Soft backgrounds, secondary emphasis
```
bg.DEFAULT β†’ a3
bg.hover β†’ a4
bg.active β†’ a5
fg β†’ a11
```

### 3. `surface`
Cards, panels with visible borders
```
bg.DEFAULT β†’ a2
bg.active β†’ a3
border.DEFAULT β†’ a6
border.hover β†’ a7
fg β†’ a11
```

### 4. `outline`
Ghost-style with border only
```
bg.hover β†’ a2
bg.active β†’ a3
border.DEFAULT β†’ a7
fg β†’ a11
```

### 5. `plain`
Text-only, no background or border
```
bg.hover β†’ a3
bg.active β†’ a4
fg β†’ a11
```

### Global Aliases

```
fg.default β†’ gray.12
fg.muted β†’ gray.11
fg.subtle β†’ gray.10
canvas β†’ gray.1
border β†’ gray.4
error β†’ red.9
```

---

## Token Reference Patterns in Recipes

### 1. `colorPalette.*` (dynamic)
```ts
bg: 'colorPalette.solid.bg'
color: 'colorPalette.subtle.fg'
borderColor: 'colorPalette.surface.border.hover'
```
Resolves based on `colorPalette` prop (e.g., `colorPalette="blue"`).

### 2. `gray.*` semantic
```ts
bg: 'gray.surface.bg'
borderColor: 'gray.outline.border'
```
References gray palette's semantic variants directly.

### 3. `gray.{1-12}` direct (rare)
```ts
bg: { _light: 'gray.2', _dark: 'gray.1' }
```
Direct scale reference for edge cases.

### 4. Global aliases
```ts
color: 'fg.default'
bg: 'border'
bg: 'error'
```

---

## Porting Strategy Options

### Option A: Adapt Tokens

Add park-ui's variant structure to `@hashintel/ds-theme`:

```ts
// In ds-theme semantic tokens
gray: {
solid: {
bg: { DEFAULT: '{colors.gray.50}', hover: '{colors.gray.60}' },
fg: { DEFAULT: '{colors.neutral.white}' },
},
subtle: {
bg: { DEFAULT: '{colors.gray.10}', hover: '{colors.gray.20}', active: '{colors.gray.30}' },
fg: { DEFAULT: '{colors.gray.60}' },
},
// ... surface, outline, plain
}
```

**Pros**: Recipes work with minimal changes
**Cons**: Token structure becomes more complex; may diverge from Figma source

### Option B: Adapt Recipes

Keep current HASH token structure, refactor each recipe:

```ts
// Before (park-ui)
bg: 'colorPalette.solid.bg'

// After (HASH)
bg: 'bg.accent.bold.default'
```

**Pros**: Token structure stays aligned with Figma
**Cons**: More work per recipe; need mapping table

### Hybrid Approach

1. Define the 5 variant structures in ds-theme for `gray`/`neutral` only
2. Map `colorPalette` to use HASH accent tokens
3. Keep direct scale references using the established LUT

---

## Files Created

| File | Purpose |
|------|---------|
| `libs/@hashintel/ds-helpers/stories/colors.radix-mapping.story.tsx` | Visual comparison tool for scale mapping |
| `libs/@hashintel/ds-components/_ai/park-ui-porting-guide.md` | This document |

## Next Steps

1. Decide on porting strategy (adapt tokens vs adapt recipes)
2. If adapting tokens: extend ds-theme with variant structures
3. If adapting recipes: create transform utility with LUT
4. Port first component (suggest: Checkbox or Button) as proof of concept
5. Document the porting workflow based on learnings
Loading
Loading