From 8ae037ababd130767a0d433a869146975c8ba136 Mon Sep 17 00:00:00 2001 From: Andres David Jimenez Sulbaran Date: Wed, 20 May 2026 10:08:25 -0600 Subject: [PATCH 1/8] chore(ui): open package feature chain From b125360ff66c3c840bbb66e581ea3e9e05d1ff8c Mon Sep 17 00:00:00 2001 From: Andres David Jimenez Date: Wed, 20 May 2026 10:17:57 -0600 Subject: [PATCH 2/8] feat(ui): scaffold package and shadcn routing (#3) --- .mcp.json | 14 + apps/web/components.json | 22 + apps/web/package.json | 8 +- docs/prd-ui-package.md | 486 ++++++++++++++++++ packages/ui/components.json | 22 + packages/ui/package.json | 64 +++ packages/ui/project.json | 38 ++ packages/ui/src/components/ui/button.tsx | 63 +++ packages/ui/src/index.ts | 3 + packages/ui/src/lib/utils.ts | 6 + packages/ui/src/styles.css | 35 ++ .../ui/src/testing/tree-shaking-smoke.test.ts | 17 + packages/ui/tsconfig.json | 10 + packages/ui/tsconfig.lib.json | 19 + packages/ui/vite.config.ts | 19 + packages/ui/vitest.config.ts | 15 + pnpm-lock.yaml | 215 ++++++++ pnpm-workspace.yaml | 5 + 18 files changed, 1060 insertions(+), 1 deletion(-) create mode 100644 .mcp.json create mode 100644 apps/web/components.json create mode 100644 docs/prd-ui-package.md create mode 100644 packages/ui/components.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/project.json create mode 100644 packages/ui/src/components/ui/button.tsx create mode 100644 packages/ui/src/index.ts create mode 100644 packages/ui/src/lib/utils.ts create mode 100644 packages/ui/src/styles.css create mode 100644 packages/ui/src/testing/tree-shaking-smoke.test.ts create mode 100644 packages/ui/tsconfig.json create mode 100644 packages/ui/tsconfig.lib.json create mode 100644 packages/ui/vite.config.ts create mode 100644 packages/ui/vitest.config.ts diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..c9b2787 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,14 @@ +{ + "mcpServers": { + "nx-mcp": { + "command": "pnpm", + "args": [ + "exec", + "nx", + "mcp" + ], + "lifecycle": "lazy", + "directTools": true + } + } +} diff --git a/apps/web/components.json b/apps/web/components.json new file mode 100644 index 0000000..e88efc9 --- /dev/null +++ b/apps/web/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "kobalte", + "tsx": true, + "tailwind": { + "config": "", + "css": "../../packages/ui/src/styles.css", + "baseColor": "neutral", + "cssVariables": true + }, + "iconLibrary": "lucide", + "aliases": { + "components": "#components", + "ui": "@os-copilot/ui/components/ui", + "lib": "#lib", + "hooks": "#hooks", + "utils": "@os-copilot/ui/lib/utils" + }, + "registries": { + "@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json" + } +} diff --git a/apps/web/package.json b/apps/web/package.json index 6c2ba1e..24f02af 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -22,12 +22,18 @@ "@tanstack/solid-router-devtools": "catalog:", "@tanstack/solid-start": "catalog:", "better-auth": "catalog:", - "solid-js": "catalog:" + "solid-js": "catalog:", + "@os-copilot/ui": "workspace:*" }, "devDependencies": { "@cloudflare/workers-types": "catalog:", "vite": "catalog:", "vite-plugin-solid": "catalog:", "vitest": "catalog:" + }, + "imports": { + "#components/*": "./src/components/*.tsx", + "#lib/*": "./src/lib/*.ts", + "#hooks/*": "./src/hooks/*.ts" } } diff --git a/docs/prd-ui-package.md b/docs/prd-ui-package.md new file mode 100644 index 0000000..53eac99 --- /dev/null +++ b/docs/prd-ui-package.md @@ -0,0 +1,486 @@ +# PRD: Mobile-first Solid UI package + +Create `packages/ui` as a Solid + TypeScript component library for the Nx monorepo. The package must be mobile-first, Storybook-driven, Nx-cacheable, and strict ESM/tree-shakeable so apps and future slice libraries can reuse UI without pulling unnecessary components. + +## Decision summary + +| Topic | Decision | +| ---------------- | ------------------------------------------------------------------------------------------------------ | +| Package path | `packages/ui` | +| Runtime | Solid + TypeScript | +| Build | Vite library mode through Nx targets | +| Component source | Zaidan via `shadcn` CLI when available; custom implementation only when unavailable | +| Reference repo | `repos/arkitect-ui/packages/solid` for setup patterns and component inventory only; do not copy source | +| Stories location | `packages/ui/src/stories/ui/*` | +| Styling baseline | Zaidan Vega style, neutral theme, Inter font, Tailwind CSS v4 | +| Export model | Strict ESM, root barrel plus per-component subpath exports | +| Performance bar | Tree-shakeable component imports, externalized peers, cacheable Nx build/typecheck/storybook targets | + +## Goals + +- Provide a reusable Solid UI library consumed from monorepo apps and future package slices. +- Install Zaidan-provided components with the `shadcn` CLI instead of hand-copying reference code. +- Match the current Arkitect Solid UI component inventory where practical. +- Keep every public component documented in Storybook under `src/stories/ui/*`. +- Guarantee strict ESM outputs with granular imports for tree-shaking. +- Make build, typecheck, lint, test, and Storybook targets cacheable through Nx. + +## Non-goals + +- Do not copy component source from `repos/arkitect-ui`. +- Do not create React components. +- Do not publish to npm in the first milestone. +- Do not build a docs site beyond Storybook. +- Do not implement custom versions of components already available from Zaidan unless a design/product gap is documented. + +## Reference findings + +`repos/arkitect-ui/packages/solid` is a useful reference for package shape: + +- `project.json` defines Nx targets for Vite build, Vitest, lint, Storybook serve, and Storybook build. +- `vite.config.ts` uses Solid, declaration generation, library entry, and externalized runtime packages. +- `package.json` uses `type: "module"` and an `exports` map. +- `.storybook/main.ts` uses Storybook Solid + Vite integration. +- Components are flat in `src/components/ui`, with colocated stories in the reference. For this package, stories must move to `src/stories/ui/*`. + +## Zaidan setup + +Zaidan is a shadcn-compatible Solid registry, not a runtime npm UI package. The implementation must configure `components.json` for the Zaidan Kobalte registry and then install registry items with `pnpm dlx shadcn@latest add`. + +Baseline design-system command: + +```bash +pnpm dlx shadcn@latest add @zaidan/font-inter @zaidan/neutral @zaidan/style-vega +``` + +Expected registry config shape: + +```json +{ + "style": "kobalte", + "tsx": true, + "tailwind": { + "baseColor": "neutral" + }, + "iconLibrary": "lucide", + "registries": { + "@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json" + } +} +``` + +## shadcn monorepo routing + +shadcn has explicit monorepo support: each workspace that runs the CLI needs its own `components.json`, and aliases decide where generated files land and how imports are rewritten. For this project, the app should keep a shadcn setup, but UI components installed from the app must route to `packages/ui`. + +### App workspace contract + +`apps/web/components.json` should point UI and shared utilities at the UI workspace package while keeping app-owned components local: + +```json +{ + "style": "kobalte", + "tsx": true, + "tailwind": { + "config": "", + "css": "../../packages/ui/src/styles.css", + "baseColor": "neutral", + "cssVariables": true + }, + "iconLibrary": "lucide", + "aliases": { + "components": "#components", + "ui": "@os-copilot/ui/components/ui", + "lib": "#lib", + "hooks": "#hooks", + "utils": "@os-copilot/ui/lib/utils" + }, + "registries": { + "@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json" + } +} +``` + +With this routing, running from the app should install UI primitives into `packages/ui` and rewrite app imports to the workspace package: + +```bash +cd apps/web +pnpm dlx shadcn@latest add @zaidan/button +``` + +Expected app import: + +```ts +import { Button } from "@os-copilot/ui/components/ui/button" +``` + +App-local blocks or non-UI components can still live under the app's `#components` alias. + +### UI workspace contract + +`packages/ui/components.json` should use package-local aliases so CLI commands run inside `packages/ui` generate files inside the package: + +```json +{ + "style": "kobalte", + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true + }, + "iconLibrary": "lucide", + "aliases": { + "components": "#components", + "ui": "#components/ui", + "lib": "#lib", + "hooks": "#hooks", + "utils": "#lib/utils" + }, + "registries": { + "@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json" + } +} +``` + +The UI package must expose every path that an app-level `components.json` can reference. This means `package.json` needs source-level exports for CLI-time routing, plus build output exports for library consumption if those differ. + +```json +{ + "name": "@os-copilot/ui", + "type": "module", + "imports": { + "#components/*": "./src/components/*.tsx", + "#lib/*": "./src/lib/*.ts", + "#hooks/*": "./src/hooks/*.ts" + }, + "exports": { + "./styles.css": "./src/styles.css", + "./components/*": "./src/components/*.tsx", + "./lib/*": "./src/lib/*.ts", + "./hooks/*": "./src/hooks/*.ts" + } +} +``` + +Implementation must reconcile this shadcn source-export routing with the stricter build output export contract below. If publishing later becomes a goal, source exports should be replaced or complemented with `dist` exports without breaking app-level CLI installs. + +## Component inventory and source plan + +Components below are taken from `repos/arkitect-ui/packages/solid/src/components/ui/*` and classified against confirmed Zaidan Kobalte availability. + +### Install from Zaidan + +These must be installed through the shadcn CLI, then adapted only for local package boundaries, exports, stories, and mobile-first QA. + +```bash +pnpm dlx shadcn@latest add \ + @zaidan/alert-dialog \ + @zaidan/avatar \ + @zaidan/badge \ + @zaidan/button \ + @zaidan/card \ + @zaidan/chart \ + @zaidan/checkbox \ + @zaidan/collapsible \ + @zaidan/combobox \ + @zaidan/dialog \ + @zaidan/dropdown-menu \ + @zaidan/empty \ + @zaidan/input \ + @zaidan/input-group \ + @zaidan/label \ + @zaidan/pagination \ + @zaidan/select \ + @zaidan/separator \ + @zaidan/sheet \ + @zaidan/sidebar \ + @zaidan/skeleton \ + @zaidan/table \ + @zaidan/textarea \ + @zaidan/tooltip +``` + +| Arkitect component | Zaidan CLI item | Notes | +| ------------------ | ----------------------- | ------------------------------------------------------ | +| `alert-dialog` | `@zaidan/alert-dialog` | Install | +| `avatar` | `@zaidan/avatar` | Install | +| `badge` | `@zaidan/badge` | Install | +| `button` | `@zaidan/button` | Install | +| `card` | `@zaidan/card` | Install | +| `chart` | `@zaidan/chart` | Install; validate chart peer dependencies | +| `checkbox` | `@zaidan/checkbox` | Install | +| `collapsible` | `@zaidan/collapsible` | Install | +| `combobox` | `@zaidan/combobox` | Install | +| `dialog` | `@zaidan/dialog` | Install | +| `dropdown-menu` | `@zaidan/dropdown-menu` | Install | +| `empty` | `@zaidan/empty` | Install | +| `input` | `@zaidan/input` | Install | +| `input-group` | `@zaidan/input-group` | Install | +| `label` | `@zaidan/label` | Install | +| `pagination` | `@zaidan/pagination` | Install | +| `select` | `@zaidan/select` | Install | +| `separator` | `@zaidan/separator` | Install | +| `sheet` | `@zaidan/sheet` | Install; mobile drawer behavior is critical | +| `sidebar` | `@zaidan/sidebar` | Install; validate mobile collapsed/off-canvas behavior | +| `skeleton` | `@zaidan/skeleton` | Install | +| `table` | `@zaidan/table` | Install; data-table remains custom | +| `textarea` | `@zaidan/textarea` | Install | +| `tooltip` | `@zaidan/tooltip` | Install | + +### Implement custom in `packages/ui` + +These were present in Arkitect but were not confirmed as Zaidan Kobalte components. Implement them from scratch for this project, with different structure and behavior where useful. + +| Arkitect component | PRD source | Requirement | +| -------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `center` | Custom | Layout primitive for centering content with responsive sizing props. | +| `copy-id-button` | Custom | Mobile-safe copy affordance with accessible feedback and no hard dependency on Arkitect behavior. | +| `data-table` | Custom | Composition over `@zaidan/table`; mobile-first stacked/card fallback for narrow screens. | +| `data-table-filters` | Custom | Filter primitives optimized for touch targets and collapsible mobile controls. | +| `float` | Custom | Positioning/layout primitive; avoid hidden desktop assumptions. | +| `grid` | Custom | Responsive grid primitive with mobile-first defaults. | +| `grid-pattern` | Custom | Decorative background primitive; ensure SSR-safe and low render cost. | +| `marquee` | Custom | Optional motion component respecting reduced-motion preferences. | +| `stack` | Custom | Layout primitive for vertical/horizontal spacing with responsive variants. | +| `toast` | Custom or adapter | Zaidan has `@zaidan/sonner`, not `toast`; decide whether to expose a `toast` adapter over Sonner or implement a local API. | + +## Package architecture + +Proposed file shape: + +```text +apps/web/ + components.json +packages/ui/ + components.json + package.json + project.json + tsconfig.json + tsconfig.lib.json + vite.config.ts + src/ + index.ts + styles.css + lib/ + cn.ts + components/ + ui/ + button.tsx + dialog.tsx + ... + stories/ + ui/ + button.stories.tsx + dialog.stories.tsx + ... + testing/ + smoke-imports.test.ts + .storybook/ + main.ts + preview.ts +``` + +### Export contract + +`packages/ui` must support both ergonomic and granular imports. The package name below is provisional until the workspace naming convention is confirmed. + +```ts +import { Button } from "@os-copilot/ui" +import { Button } from "@os-copilot/ui/button" +import "@os-copilot/ui/styles.css" +``` + +Acceptance requirements: + +- Package is `type: "module"`. +- Build output is ESM-only unless a later consumer requires CJS. +- `solid-js`, `solid-js/web`, Kobalte, Lucide, and other runtime framework libraries are externalized and modeled as peer dependencies where appropriate. +- Root barrel re-exports public components only; no Storybook/test/internal exports. +- Per-component subpath exports exist in `package.json`. +- shadcn monorepo exports also expose `./components/*`, `./lib/*`, `./hooks/*`, and `./styles.css` for CLI routing from the app workspace. +- Generated `.d.ts` files preserve root and subpath imports. +- `sideEffects` is restricted to CSS files, for example `["**/*.css"]`. + +Example `exports` intent: + +```json +{ + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "./button": { + "types": "./dist/components/ui/button.d.ts", + "import": "./dist/components/ui/button.js" + }, + "./styles.css": "./dist/styles.css" +} +``` + +## Storybook and Nx compatibility + +- Use Storybook Solid + Vite through `storybook-solidjs-vite` unless Nx adds first-party Solid Storybook support before implementation. +- Install `@nx/storybook` at the same major/minor version as `nx` to avoid version mismatch issues. +- Add `@nx/storybook/plugin` to `nx.json` so Storybook tasks are inferred from project-level `.storybook` config files. +- Treat Nx's Storybook generator as partially compatible: current documented `uiFramework` choices do not include Solid, so `packages/ui/.storybook/*` may need manual setup or a generated baseline followed by Solid-specific replacement. +- Use project-level Storybook config for `packages/ui`; do not start with a root shared Storybook unless multiple packages later need composition. +- Use Storybook Vite `viteFinal` to merge Solid/Vite aliases, Nx path resolution, Tailwind v4 CSS processing, and package workspace exports. +- Stories live outside component source: `src/stories/ui/.stories.tsx`. +- Every public component has at least one default mobile viewport story. +- Interactive components include keyboard and touch-oriented examples where relevant. +- Storybook must load `src/styles.css` and Zaidan theme tokens. +- Required Storybook targets: + - `storybook` + - `build-storybook` + - `test-storybook` if interaction tests are enabled +- Prefer addons for docs and accessibility if dependency cost is acceptable. + +## SSR and SPA compatibility + +The UI package must work in both SSR and SPA consumers. Solid components should render deterministically on the server and hydrate cleanly in the browser. + +Requirements: + +- Components must not read `window`, `document`, `localStorage`, layout measurements, media queries, or element refs during initial render. +- Browser-only behavior must be isolated in `onMount`, event handlers, lazy imports, or explicit client-only wrappers in the consuming app. +- Initial signal values must not branch on `isServer` in a way that changes server HTML vs hydrated client HTML. +- Components that cannot be SSR-safe must be explicitly marked as client-only and excluded from the default SSR acceptance path. +- Random IDs, generated IDs, timestamps, and measurements must be stable between SSR and hydration or delegated to Solid/Kobalte primitives that guarantee stability. +- Overlay/portal components must document SSR expectations and avoid assuming `document.body` exists during server render. +- Custom components need SPA and SSR smoke stories/tests where practical: render-to-string or SSR fixture plus client mount/hydration fixture. + +## CSS, shadcn, Zaidan, and tweakcn compatibility + +The CSS layer must remain compatible with shadcn's Tailwind v4 theming model and with tweakcn-style theme editing. + +Findings: + +- shadcn Tailwind v4 themes are CSS-variable first: semantic variables live in `:root` and `.dark`, then `@theme inline` maps them to Tailwind utilities such as `bg-background`, `text-foreground`, `border-border`, and `ring-ring`. +- Zaidan uses the shadcn registry model: `registry:theme`, `registry:style`, `registry:font`, `cssVars`, `tailwind`, `css`, and registry dependencies. The selected Zaidan setup installs `@zaidan/font-inter`, `@zaidan/neutral`, and `@zaidan/style-vega` into the configured Tailwind CSS file. +- Vega is a shadcn visual style; Zaidan adapts that registry/style model for Solid/Kobalte. +- tweakcn targets shadcn-compatible theme variables and supports Tailwind v4, so this package should preserve standard variable names instead of inventing a parallel token system. + +Requirements: + +- `src/styles.css` is the single source of truth for shadcn/Zaidan/tweakcn theme variables. +- Keep `tailwind.cssVariables: true` in both app and UI `components.json` files. +- Keep Tailwind v4 `tailwind.config` empty in `components.json` unless the project later proves a config file is required. +- Preserve standard shadcn variable names: `--background`, `--foreground`, `--card`, `--popover`, `--primary`, `--secondary`, `--muted`, `--accent`, `--destructive`, `--border`, `--input`, `--ring`, `--chart-*`, `--sidebar-*`, and `--radius`. +- Preserve `@theme inline` mappings for Tailwind utilities and radius derivations. +- Custom component CSS must consume semantic utilities or variables; do not hardcode one-off OKLCH values inside components unless the value is a documented design token. +- tweakcn-exported Tailwind v4 CSS should be pasteable into `src/styles.css` without changing component source. +- Zaidan CLI CSS changes must be reviewed after install because registry items may append CSS, imports, animations, or variables. + +## Mobile-first requirements + +- Default examples and component variants must assume narrow screens first. +- Touch targets should be at least 44px where the component is interactive. +- Overlays (`dialog`, `sheet`, `dropdown-menu`, `combobox`, `select`, `sidebar`) must be usable on mobile viewport sizes. +- Tables must not require horizontal scrolling as the only mobile strategy; `data-table` must provide a stacked/card alternative or explicit responsive mode. +- Motion components must respect `prefers-reduced-motion`. +- Components must support dark mode through tokens, not one-off component overrides. + +## Nx targets and cache + +`project.json` should expose cacheable targets compatible with root `nx.json` defaults: + +- `build`: Vite library build. +- `typecheck`: TypeScript/tsgo typecheck for the library. +- `lint`: oxlint/dprint compatible lint formatting path. +- `test`: Vitest smoke/unit tests where applicable. +- `storybook`: local Storybook, using Nx's default inferred task name where possible. +- `build-storybook`: CI/cacheable static Storybook build. +- `test-storybook`: optional interaction/a11y checks if enabled. + +Build inputs should include source, package config, Storybook config, Vite config, Tailwind/Zaidan config, and dependency lockfiles as appropriate. + +## Acceptance criteria + +### Package setup + +- [ ] `packages/ui` exists and is included by `pnpm-workspace.yaml`. +- [ ] Nx recognizes `ui` as a project with build/typecheck/lint/test/storybook targets. +- [ ] `@nx/storybook` is installed at a version matching `nx` and `@nx/storybook/plugin` is configured if inferred tasks are used. +- [ ] `pnpm nx build ui` produces ESM JS and `.d.ts` output. +- [ ] `pnpm nx typecheck ui` passes. +- [ ] `pnpm nx build-storybook ui` passes. + +### Zaidan and shadcn monorepo install path + +- [ ] `apps/web/components.json` and `packages/ui/components.json` point to the Zaidan Kobalte registry. +- [ ] Both workspaces keep the same `style`, `iconLibrary`, and `baseColor`. +- [ ] App `components.json` routes `ui` to `@os-copilot/ui/components/ui` and `utils` to `@os-copilot/ui/lib/utils`. +- [ ] UI `components.json` uses package-local `#components`, `#lib`, and `#hooks` aliases. +- [ ] `packages/ui/package.json` exports the paths referenced by the app workspace aliases. +- [ ] Running `pnpm dlx shadcn@latest add @zaidan/button` from `apps/web` installs the UI component into `packages/ui` and rewrites imports to `@os-copilot/ui/components/ui/button`. +- [ ] Baseline design packages are installed with `pnpm dlx shadcn@latest add @zaidan/font-inter @zaidan/neutral @zaidan/style-vega`. +- [ ] Confirmed Zaidan components are installed through the CLI, not manually copied from Arkitect. +- [ ] CLI-generated files are normalized to the package's structure and export policy. + +### Tree-shaking + +- [ ] Root barrel import works. +- [ ] Per-component subpath imports work. +- [ ] A smoke test or fixture verifies importing one component does not require importing all components. +- [ ] Package metadata uses `sideEffects` narrowly for CSS only. +- [ ] Peer dependencies are externalized in Vite build. + +### SSR and SPA + +- [ ] Components render in a Solid SSR fixture without `window`/`document` errors. +- [ ] Components mount in a SPA fixture without hydration-only assumptions. +- [ ] Hydration smoke test covers at least one interactive component, one overlay component, and one custom layout component. +- [ ] Any client-only component is documented and not exported as SSR-safe by default. + +### CSS and theming + +- [ ] `src/styles.css` contains shadcn/Zaidan Tailwind v4 variables and `@theme inline` mappings. +- [ ] tweakcn Tailwind v4 theme output can replace or override the variables without component source changes. +- [ ] Custom components use semantic utilities/variables instead of hardcoded colors. +- [ ] Zaidan registry CSS changes are reviewed after CLI install. + +### Storybook + +- [ ] Stories live in `src/stories/ui/*`. +- [ ] Every public component has a story. +- [ ] Mobile viewport is the default review path. +- [ ] Storybook renders with Zaidan theme/style/font tokens. +- [ ] Storybook config uses `storybook-solidjs-vite` or a documented compatible Solid+Vite adapter. +- [ ] Storybook Vite config resolves workspace package exports and app/UI aliases. + +### Custom components + +- [ ] Custom components are implemented from scratch. +- [ ] Custom components have a clear reason for existing because Zaidan does not provide them. +- [ ] Custom components expose mobile-first stories and accessibility notes. +- [ ] `toast` decision is documented: Sonner adapter vs local toast implementation. + +## Open questions + +1. Should the package name be `@os-copilot/ui`, `@repo/ui`, or another workspace convention? + R: @os-copilot/ui +2. Should `toast` become an adapter around `@zaidan/sonner`, or remain a custom component API? + R: adapter from @zaidan/sonner +3. Should layout primitives (`center`, `stack`, `grid`, `float`) live in `components/ui` or a separate `src/layout/*` namespace with exported aliases? + R: is ok live in components/ui +4. Should data-table depend on a table utility such as TanStack Table, or stay as a lightweight composition layer initially? + R: yup Tanstack Table +5. Should Storybook include visual regression tooling in the first milestone or only static build verification? + R: Yes +6. Should client-only exceptions be allowed in the core package, or should all exported components be SSR-safe by default? + R: we need to take a look in this one because i dont understand well the quesion. +7. Should tweakcn be a documented workflow only, or should we add a fixture/theme import test for tweakcn-generated Tailwind v4 CSS? + R: should add fixuter theme import test for. + +## Suggested implementation milestones + +1. **Scaffold package**: create `packages/ui`, Vite/Solid build, Nx targets, TypeScript config, package exports, styles entry. +2. **Configure Zaidan**: add `components.json`, Tailwind v4 styling path, baseline theme/font/style packages. +3. **Install Zaidan components**: add confirmed components with shadcn CLI and normalize exports. +4. **Storybook foundation**: configure `@nx/storybook` plus `storybook-solidjs-vite`, global styles, mobile viewport defaults, stories folder convention. +5. **CSS compatibility**: verify shadcn/Zaidan Tailwind v4 variables, `@theme inline`, dark mode, and tweakcn replacement workflow. +6. **Custom component batch**: implement missing Arkitect-equivalent components from scratch, prioritizing layout primitives and data-table. +7. **SSR/SPA verification**: add render/hydration smoke fixtures and mark any client-only exceptions. +8. **Tree-shaking verification**: add smoke fixture/tests for root and subpath imports, externalized peers, and CSS side effects. diff --git a/packages/ui/components.json b/packages/ui/components.json new file mode 100644 index 0000000..9f089f7 --- /dev/null +++ b/packages/ui/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "kobalte", + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true + }, + "iconLibrary": "lucide", + "aliases": { + "components": "#components", + "ui": "#components/ui", + "lib": "#lib", + "hooks": "#hooks", + "utils": "#lib/utils" + }, + "registries": { + "@zaidan": "https://zaidan.carere.dev/r/{style}/{name}.json" + } +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..c4b1894 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,64 @@ +{ + "name": "@os-copilot/ui", + "version": "0.0.0", + "private": true, + "type": "module", + "sideEffects": [ + "**/*.css" + ], + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + }, + "./button": { + "types": "./src/components/ui/button.tsx", + "import": "./src/components/ui/button.tsx" + }, + "./styles.css": "./src/styles.css", + "./components/ui/*": { + "types": "./src/components/ui/*.tsx", + "import": "./src/components/ui/*.tsx" + }, + "./components/*": { + "types": "./src/components/*.tsx", + "import": "./src/components/*.tsx" + }, + "./lib/*": { + "types": "./src/lib/*.ts", + "import": "./src/lib/*.ts" + }, + "./hooks/*": { + "types": "./src/hooks/*.ts", + "import": "./src/hooks/*.ts" + } + }, + "imports": { + "#components/*": "./src/components/*.tsx", + "#lib/*": "./src/lib/*.ts", + "#hooks/*": "./src/hooks/*.ts" + }, + "scripts": { + "build": "vite build && tsgo -p tsconfig.lib.json", + "typecheck": "tsgo --noEmit -p tsconfig.json", + "test": "vitest run --passWithNoTests" + }, + "peerDependencies": { + "solid-js": "catalog:" + }, + "devDependencies": { + "@effect/tsgo": "catalog:", + "solid-js": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plugin-solid": "catalog:", + "vitest": "catalog:" + }, + "dependencies": { + "@kobalte/core": "catalog:", + "class-variance-authority": "catalog:", + "clsx": "catalog:", + "tailwind-merge": "catalog:", + "tailwindcss": "catalog:" + } +} diff --git a/packages/ui/project.json b/packages/ui/project.json new file mode 100644 index 0000000..72d3d47 --- /dev/null +++ b/packages/ui/project.json @@ -0,0 +1,38 @@ +{ + "name": "ui", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "library", + "sourceRoot": "packages/ui/src", + "targets": { + "build": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/ui", + "command": "pnpm build" + } + }, + "typecheck": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/ui", + "command": "pnpm typecheck" + } + }, + "test": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/ui", + "command": "pnpm test" + } + }, + "lint": { + "executor": "nx-oxlint:lint", + "options": { + "configFile": ".oxlintrc.json", + "lintFilePatterns": ["packages/ui/**/*.{ts,tsx,js,jsx,mts,cts}"] + }, + "outputs": ["{options.outputFile}"] + } + }, + "tags": ["scope:ui", "type:lib"] +} diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx new file mode 100644 index 0000000..bc3db04 --- /dev/null +++ b/packages/ui/src/components/ui/button.tsx @@ -0,0 +1,63 @@ +import { type ButtonRootProps, Root } from "@kobalte/core/button" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" +import { cva, type VariantProps } from "class-variance-authority" +import { type ComponentProps, splitProps, type ValidComponent } from "solid-js" +import { cn } from "#lib/utils" + +const buttonVariants = cva( + "group/button z-button inline-flex shrink-0 select-none items-center justify-center whitespace-nowrap outline-none transition-all active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0", + { + variants: { + variant: { + default: "z-button-variant-default", + outline: "z-button-variant-outline", + secondary: "z-button-variant-secondary", + ghost: "z-button-variant-ghost", + destructive: "z-button-variant-destructive", + link: "z-button-variant-link", + }, + size: { + default: "z-button-size-default", + xs: "z-button-size-xs", + sm: "z-button-size-sm", + lg: "z-button-size-lg", + icon: "z-button-size-icon", + "icon-xs": "z-button-size-icon-xs", + "icon-sm": "z-button-size-icon-sm", + "icon-lg": "z-button-size-icon-lg", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +) + +type ButtonProps = + & PolymorphicProps< + T, + ButtonRootProps + > + & VariantProps + & Pick, "class"> + +const Button = (props: ButtonProps) => { + const [local, others] = splitProps(props as ButtonProps, [ + "variant", + "size", + "class", + ]) + return ( + + ) +} + +export { Button, type ButtonProps, buttonVariants } diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts new file mode 100644 index 0000000..10fe948 --- /dev/null +++ b/packages/ui/src/index.ts @@ -0,0 +1,3 @@ +export { Button } from "./components/ui/button" +export type { ButtonProps } from "./components/ui/button" +export { cn } from "./lib/utils" diff --git a/packages/ui/src/lib/utils.ts b/packages/ui/src/lib/utils.ts new file mode 100644 index 0000000..27ec672 --- /dev/null +++ b/packages/ui/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ReadonlyArray): string { + return twMerge(clsx(inputs)) +} diff --git a/packages/ui/src/styles.css b/packages/ui/src/styles.css new file mode 100644 index 0000000..51204dc --- /dev/null +++ b/packages/ui/src/styles.css @@ -0,0 +1,35 @@ +@import "tailwindcss"; + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-border: var(--border); + --color-ring: var(--ring); + --radius-lg: var(--radius); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --border: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --primary: oklch(0.922 0 0); + --primary-foreground: oklch(0.205 0 0); + --border: oklch(1 0 0 / 10%); + --ring: oklch(0.556 0 0); +} + +.ui-button { + min-height: 44px; + border-radius: var(--radius); +} diff --git a/packages/ui/src/testing/tree-shaking-smoke.test.ts b/packages/ui/src/testing/tree-shaking-smoke.test.ts new file mode 100644 index 0000000..f6ca712 --- /dev/null +++ b/packages/ui/src/testing/tree-shaking-smoke.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vitest" + +import { Button as RootButton, cn } from "@os-copilot/ui" +import { Button as SubpathButton } from "@os-copilot/ui/button" +import { Button as SourceRoutedButton } from "@os-copilot/ui/components/ui/button" +import { cn as SourceRoutedCn } from "@os-copilot/ui/lib/utils" + +describe("ui package public exports", () => { + it("resolves root, ergonomic subpath, shadcn source-routed, and css exports", async () => { + await expect(import("@os-copilot/ui/styles.css")).resolves.toBeDefined() + + expect(RootButton).toBe(SubpathButton) + expect(RootButton).toBe(SourceRoutedButton) + expect(cn("a", false, "b")).toBe("a b") + expect(SourceRoutedCn("a", undefined, "b")).toBe("a b") + }) +}) diff --git a/packages/ui/tsconfig.json b/packages/ui/tsconfig.json new file mode 100644 index 0000000..163b11c --- /dev/null +++ b/packages/ui/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "types": ["vite/client"], + "resolvePackageJsonExports": true, + "resolvePackageJsonImports": true + }, + "include": ["src/**/*", "vite.config.ts", "vitest.config.ts"], + "exclude": ["dist", "node_modules"] +} diff --git a/packages/ui/tsconfig.lib.json b/packages/ui/tsconfig.lib.json new file mode 100644 index 0000000..92d7afe --- /dev/null +++ b/packages/ui/tsconfig.lib.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src/**/*"], + "exclude": [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + "src/stories/**/*", + "src/testing/**/*" + ] +} diff --git a/packages/ui/vite.config.ts b/packages/ui/vite.config.ts new file mode 100644 index 0000000..eabec2f --- /dev/null +++ b/packages/ui/vite.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "vite" +import viteSolid from "vite-plugin-solid" + +export default defineConfig({ + build: { + lib: { + entry: "src/index.ts", + fileName: "index", + formats: ["es"], + }, + rollupOptions: { + external: ["solid-js", "solid-js/web", "solid-js/store"], + }, + }, + resolve: { + tsconfigPaths: true, + }, + plugins: [viteSolid()], +}) diff --git a/packages/ui/vitest.config.ts b/packages/ui/vitest.config.ts new file mode 100644 index 0000000..5261cd5 --- /dev/null +++ b/packages/ui/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from "vitest/config" +import viteSolid from "vite-plugin-solid" + +export default defineConfig({ + plugins: [viteSolid()], + test: { + environment: "node", + include: [ + "src/**/*.test.ts", + "src/**/*.test.tsx", + "src/**/*.spec.ts", + "src/**/*.spec.tsx", + ], + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 276b8ac..429db8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,6 +18,9 @@ catalogs: '@effect/tsgo': specifier: 0.7.0 version: 0.7.0 + '@kobalte/core': + specifier: 0.13.11 + version: 0.13.11 '@nx/js': specifier: 22.7.1 version: 22.7.1 @@ -42,6 +45,12 @@ catalogs: better-auth: specifier: 1.6.11 version: 1.6.11 + class-variance-authority: + specifier: 0.7.1 + version: 0.7.1 + clsx: + specifier: 2.1.1 + version: 2.1.1 dprint: specifier: 0.54.0 version: 0.54.0 @@ -66,6 +75,12 @@ catalogs: solid-js: specifier: 1.9.12 version: 1.9.12 + tailwind-merge: + specifier: 3.6.0 + version: 3.6.0 + tailwindcss: + specifier: 4.3.0 + version: 4.3.0 typescript: specifier: 5.9.3 version: 5.9.3 @@ -125,6 +140,9 @@ importers: '@effect/platform-node': specifier: 'catalog:' version: 4.0.0-beta.66(effect@4.0.0-beta.66)(ioredis@5.10.1) + '@os-copilot/ui': + specifier: workspace:* + version: link:../../packages/ui '@tanstack/solid-router': specifier: 'catalog:' version: 1.170.4(solid-js@1.9.12) @@ -160,6 +178,43 @@ importers: specifier: 'catalog:' version: 4.1.6(@types/node@25.7.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.3)(jiti@2.7.0)(yaml@2.9.0)) + packages/ui: + dependencies: + '@kobalte/core': + specifier: 'catalog:' + version: 0.13.11(solid-js@1.9.12) + class-variance-authority: + specifier: 'catalog:' + version: 0.7.1 + clsx: + specifier: 'catalog:' + version: 2.1.1 + tailwind-merge: + specifier: 'catalog:' + version: 3.6.0 + tailwindcss: + specifier: 'catalog:' + version: 4.3.0 + devDependencies: + '@effect/tsgo': + specifier: 'catalog:' + version: 0.7.0 + solid-js: + specifier: 'catalog:' + version: 1.9.12 + typescript: + specifier: 'catalog:' + version: 5.9.3 + vite: + specifier: 'catalog:' + version: 8.0.12(@types/node@25.7.0)(esbuild@0.27.3)(jiti@2.7.0)(yaml@2.9.0) + vite-plugin-solid: + specifier: 'catalog:' + version: 2.11.12(solid-js@1.9.12)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.3)(jiti@2.7.0)(yaml@2.9.0)) + vitest: + specifier: 'catalog:' + version: 4.1.6(@types/node@25.7.0)(vite@8.0.12(@types/node@25.7.0)(esbuild@0.27.3)(jiti@2.7.0)(yaml@2.9.0)) + packages: '@alcalzone/ansi-tokenize@0.2.5': @@ -976,6 +1031,11 @@ packages: '@cloudflare/workers-types@4.20260511.1': resolution: {integrity: sha512-FA+si7cOq9i/gtCHhIc0XJL0l1F/ApF+m00752Aj7WZFJrj3ZulT2T8/+rT3BabMT0QEnqFEGIqCgrmqhgEfMg==} + '@corvu/utils@0.4.2': + resolution: {integrity: sha512-Ox2kYyxy7NoXdKWdHeDEjZxClwzO4SKM8plAaVwmAJPxHMqA0rLOoAsa+hBDwRLpctf+ZRnAd/ykguuJidnaTA==} + peerDependencies: + solid-js: ^1.8 + '@distilled.cloud/aws@0.21.0': resolution: {integrity: sha512-zS5DCu/awiVdbIP4A4kb61XB3+qD8Z6VVEefWT5mwqJrnUWwNdljSXNLnfXwR01nJcCaEC8nKCeat48F+I179w==} peerDependencies: @@ -1332,6 +1392,21 @@ packages: cpu: [x64] os: [win32] + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@internationalized/date@3.12.1': + resolution: {integrity: sha512-6IedsVWXyq4P9Tj+TxuU8WGWM70hYLl12nbYU8jkikVpa6WXapFazPUcHUMDMoWftIDE2ILDkFFte6W2nFCkRQ==} + + '@internationalized/number@3.6.6': + resolution: {integrity: sha512-iFgmQaXHE0vytNfpLZWOC2mEJCBRzcUxt53Xf/yCXG93lRvqas237i3r7X4RKMwO3txiyZD4mQjKAByFv6UGSQ==} + '@ioredis/commands@1.5.1': resolution: {integrity: sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==} @@ -1355,6 +1430,16 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@kobalte/core@0.13.11': + resolution: {integrity: sha512-hK7TYpdib/XDb/r/4XDBFaO9O+3ZHz4ZWryV4/3BfES+tSQVgg2IJupDnztKXB0BqbSRy/aWlHKw1SPtNPYCFQ==} + peerDependencies: + solid-js: ^1.8.15 + + '@kobalte/utils@0.9.1': + resolution: {integrity: sha512-eeU60A3kprIiBDAfv9gUJX1tXGLuZiKMajUfSQURAF2pk4ZoMYiqIzmrMBvzcxP39xnYttgTyQEVLwiTZnrV4w==} + peerDependencies: + solid-js: ^1.8.8 + '@libsql/client@0.17.3': resolution: {integrity: sha512-HXk9wiAoJbKFbyBH4O+aEhN6ir5ERXuXvwE5OD2eR4/5RUa3Pw/8L9zrnVdU+iNJitRvisPWaIwmhkO3bH7giA==} @@ -2057,11 +2142,26 @@ packages: peerDependencies: solid-js: ^1.6.12 + '@solid-primitives/keyed@1.5.3': + resolution: {integrity: sha512-zNadtyYBhJSOjXtogkGHmRxjGdz9KHc8sGGVAGlUABkE8BED2tbIZoxkwSqzOwde8OcUEH0bb5DLZUWIMvyBSA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/map@0.4.13': + resolution: {integrity: sha512-B1zyFbsiTQvqPr+cuPCXO72sRuczG9Swncqk5P74NCGw1VE8qa/Ry9GlfI1e/VdeQYHjan+XkbE3rO2GW/qKew==} + peerDependencies: + solid-js: ^1.6.12 + '@solid-primitives/media@2.3.5': resolution: {integrity: sha512-LX9fB5WDaK87FMDtUB1qokBOfT2et9Uobv/zZaKLH9caFSz4+P70MBKEIBHcZQy+9MV5M2XvGYLTbLskjkzMjA==} peerDependencies: solid-js: ^1.6.12 + '@solid-primitives/props@3.2.3': + resolution: {integrity: sha512-XzG6en9gSFwmvbKcATm2BxL63HegZ+BAG5fmHi8jyBppQHcaths7ffz+6vYvwYy3nlgLa20ufJLj7tst+PcHFA==} + peerDependencies: + solid-js: ^1.6.12 + '@solid-primitives/refs@1.1.3': resolution: {integrity: sha512-aam02fjNKpBteewF/UliPSQCVJsIIGOLEWQOh+ll6R/QePzBOOBMcC4G+5jTaO75JuUS1d/14Q1YXT3X0Ow6iA==} peerDependencies: @@ -2092,6 +2192,11 @@ packages: peerDependencies: solid-js: ^1.6.12 + '@solid-primitives/trigger@1.2.3': + resolution: {integrity: sha512-Za2JebEiDyfamjmDwRaESYqBBYOlgYGzB8kHYH0QrkXyLf2qNADlKdGN+z3vWSLCTDcKxChS43Kssjuc0OZhng==} + peerDependencies: + solid-js: ^1.6.12 + '@solid-primitives/utils@6.4.0': resolution: {integrity: sha512-AeGTBg8Wtkh/0s+evyLtP8piQoS4wyqqQaAFs2HJcFMMjYAtUgo+ZPduRXLjPlqKVc2ejeR544oeqpbn8Egn8A==} peerDependencies: @@ -2105,6 +2210,9 @@ packages: '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@swc/helpers@0.5.21': + resolution: {integrity: sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==} + '@tanstack/history@1.162.0': resolution: {integrity: sha512-79pf/RkhteYZTRgcR4F9kbk84P2N8rugQJswxfIqovlbRiT3yI7eBE+5QorIrZaOKktsgzRlXh1l/du/xpl4iA==} engines: {node: '>=20.19'} @@ -2671,6 +2779,9 @@ packages: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cli-boxes@3.0.0: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} @@ -3907,6 +4018,16 @@ packages: solid-js@1.9.12: resolution: {integrity: sha512-QzKaSJq2/iDrWR1As6MHZQ8fQkdOBf8GReYb7L5iKwMGceg7HxDcaOHk0at66tNgn9U2U7dXo8ZZpLIAmGMzgw==} + solid-presence@0.1.8: + resolution: {integrity: sha512-pWGtXUFWYYUZNbg5YpG5vkQJyOtzn2KXhxYaMx/4I+lylTLYkITOLevaCwMRN+liCVk0pqB6EayLWojNqBFECA==} + peerDependencies: + solid-js: ^1.8 + + solid-prevent-scroll@0.1.10: + resolution: {integrity: sha512-KplGPX2GHiWJLZ6AXYRql4M127PdYzfwvLJJXMkO+CMb8Np4VxqDAg5S8jLdwlEuBis/ia9DKw2M8dFx5u8Mhw==} + peerDependencies: + solid-js: ^1.8 + solid-refresh@0.6.3: resolution: {integrity: sha512-F3aPsX6hVw9ttm5LYlth8Q15x6MlI/J3Dn+o3EQyRTtTxidepSTwAYdozt01/YA+7ObcciagGEyXIopGZzQtbA==} peerDependencies: @@ -4003,6 +4124,12 @@ packages: resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} engines: {node: '>=20'} + tailwind-merge@3.6.0: + resolution: {integrity: sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -5467,6 +5594,11 @@ snapshots: '@cloudflare/workers-types@4.20260511.1': {} + '@corvu/utils@0.4.2(solid-js@1.9.12)': + dependencies: + '@floating-ui/dom': 1.7.6 + solid-js: 1.9.12 + '@distilled.cloud/aws@0.21.0(effect@4.0.0-beta.66)': dependencies: '@aws-crypto/crc32': 5.2.0 @@ -5731,6 +5863,25 @@ snapshots: '@esbuild/win32-x64@0.27.3': optional: true + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + + '@internationalized/date@3.12.1': + dependencies: + '@swc/helpers': 0.5.21 + + '@internationalized/number@3.6.6': + dependencies: + '@swc/helpers': 0.5.21 + '@ioredis/commands@1.5.1': {} '@jest/diff-sequences@30.0.1': {} @@ -5754,6 +5905,29 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@kobalte/core@0.13.11(solid-js@1.9.12)': + dependencies: + '@floating-ui/dom': 1.7.6 + '@internationalized/date': 3.12.1 + '@internationalized/number': 3.6.6 + '@kobalte/utils': 0.9.1(solid-js@1.9.12) + '@solid-primitives/props': 3.2.3(solid-js@1.9.12) + '@solid-primitives/resize-observer': 2.1.5(solid-js@1.9.12) + solid-js: 1.9.12 + solid-presence: 0.1.8(solid-js@1.9.12) + solid-prevent-scroll: 0.1.10(solid-js@1.9.12) + + '@kobalte/utils@0.9.1(solid-js@1.9.12)': + dependencies: + '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) + '@solid-primitives/keyed': 1.5.3(solid-js@1.9.12) + '@solid-primitives/map': 0.4.13(solid-js@1.9.12) + '@solid-primitives/media': 2.3.5(solid-js@1.9.12) + '@solid-primitives/props': 3.2.3(solid-js@1.9.12) + '@solid-primitives/refs': 1.1.3(solid-js@1.9.12) + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + '@libsql/client@0.17.3': dependencies: '@libsql/core': 0.17.3 @@ -6385,6 +6559,15 @@ snapshots: '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) solid-js: 1.9.12 + '@solid-primitives/keyed@1.5.3(solid-js@1.9.12)': + dependencies: + solid-js: 1.9.12 + + '@solid-primitives/map@0.4.13(solid-js@1.9.12)': + dependencies: + '@solid-primitives/trigger': 1.2.3(solid-js@1.9.12) + solid-js: 1.9.12 + '@solid-primitives/media@2.3.5(solid-js@1.9.12)': dependencies: '@solid-primitives/event-listener': 2.4.5(solid-js@1.9.12) @@ -6393,6 +6576,11 @@ snapshots: '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) solid-js: 1.9.12 + '@solid-primitives/props@3.2.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + '@solid-primitives/refs@1.1.3(solid-js@1.9.12)': dependencies: '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) @@ -6426,6 +6614,11 @@ snapshots: '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) solid-js: 1.9.12 + '@solid-primitives/trigger@1.2.3(solid-js@1.9.12)': + dependencies: + '@solid-primitives/utils': 6.4.0(solid-js@1.9.12) + solid-js: 1.9.12 + '@solid-primitives/utils@6.4.0(solid-js@1.9.12)': dependencies: solid-js: 1.9.12 @@ -6436,6 +6629,10 @@ snapshots: '@standard-schema/spec@1.1.0': {} + '@swc/helpers@0.5.21': + dependencies: + tslib: 2.8.1 + '@tanstack/history@1.162.0': {} '@tanstack/router-core@1.171.2': @@ -7092,6 +7289,10 @@ snapshots: dependencies: readdirp: 4.1.2 + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + cli-boxes@3.0.0: {} cli-cursor@3.1.0: @@ -8402,6 +8603,16 @@ snapshots: seroval: 1.5.4 seroval-plugins: 1.5.4(seroval@1.5.4) + solid-presence@0.1.8(solid-js@1.9.12): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.12) + solid-js: 1.9.12 + + solid-prevent-scroll@0.1.10(solid-js@1.9.12): + dependencies: + '@corvu/utils': 0.4.2(solid-js@1.9.12) + solid-js: 1.9.12 + solid-refresh@0.6.3(solid-js@1.9.12): dependencies: '@babel/generator': 7.29.1 @@ -8488,6 +8699,10 @@ snapshots: tagged-tag@1.0.0: {} + tailwind-merge@3.6.0: {} + + tailwindcss@4.3.0: {} + tar-stream@2.2.0: dependencies: bl: 4.1.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 749b3a3..bacdbc0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -47,6 +47,7 @@ catalog: "@effect/language-service": 0.86.1 "@effect/platform-node": 4.0.0-beta.66 "@effect/tsgo": 0.7.0 + "@kobalte/core": 0.13.11 "@nx/js": 22.7.1 "@nx/vite": 22.7.1 "@tanstack/solid-router": 1.170.4 @@ -55,6 +56,8 @@ catalog: "@typescript/native-preview": 7.0.0-dev.20260513.1 "@cloudflare/workers-types": 4.20260511.1 better-auth: 1.6.11 + class-variance-authority: 0.7.1 + clsx: 2.1.1 dprint: 0.54.0 alchemy: 2.0.0-beta.40 effect: 4.0.0-beta.66 @@ -64,6 +67,8 @@ catalog: nx-oxlint: 0.1.1 oxlint: 1.64.0 solid-js: 1.9.12 + tailwind-merge: 3.6.0 + tailwindcss: 4.3.0 typescript: 5.9.3 vite: 8.0.12 vite-plugin-solid: 2.11.12 From 3576910407f4ef3747bf784e5a9c5a808cae54c5 Mon Sep 17 00:00:00 2001 From: Andres David Jimenez Date: Thu, 21 May 2026 07:41:01 -0600 Subject: [PATCH 3/8] feat(ui): add Zaidan Storybook baseline Merge PR 2 into the UI package tracker branch. --- .gitignore | 2 + nx.json | 9 + package.json | 2 + packages/ui/.storybook/main.ts | 21 + packages/ui/.storybook/preview.ts | 15 + packages/ui/package.json | 18 +- packages/ui/project.json | 14 + .../ui/src/components/ui/alert-dialog.tsx | 226 ++ packages/ui/src/components/ui/avatar.tsx | 125 + packages/ui/src/components/ui/badge.tsx | 42 + packages/ui/src/components/ui/button.tsx | 16 +- packages/ui/src/components/ui/card.tsx | 91 + packages/ui/src/components/ui/chart.tsx | 437 ++++ packages/ui/src/components/ui/checkbox.tsx | 80 + packages/ui/src/components/ui/collapsible.tsx | 36 + packages/ui/src/components/ui/combobox.tsx | 343 +++ packages/ui/src/components/ui/dialog.tsx | 188 ++ .../ui/src/components/ui/dropdown-menu.tsx | 328 +++ packages/ui/src/components/ui/empty.tsx | 115 + packages/ui/src/components/ui/input-group.tsx | 163 ++ packages/ui/src/components/ui/input.tsx | 21 + packages/ui/src/components/ui/label.tsx | 22 + packages/ui/src/components/ui/pagination.tsx | 124 + packages/ui/src/components/ui/select.tsx | 193 ++ packages/ui/src/components/ui/separator.tsx | 25 + packages/ui/src/components/ui/sheet.tsx | 159 ++ packages/ui/src/components/ui/sidebar.tsx | 737 ++++++ packages/ui/src/components/ui/skeleton.tsx | 11 + packages/ui/src/components/ui/table.tsx | 64 + packages/ui/src/components/ui/textarea.tsx | 21 + packages/ui/src/components/ui/tooltip.tsx | 53 + packages/ui/src/hooks/use-mobile.ts | 20 + packages/ui/src/index.ts | 25 +- .../src/stories/ui/alert-dialog.stories.tsx | 35 + packages/ui/src/stories/ui/avatar.stories.tsx | 14 + packages/ui/src/stories/ui/badge.stories.tsx | 8 + packages/ui/src/stories/ui/button.stories.tsx | 16 + packages/ui/src/stories/ui/card.stories.tsx | 18 + packages/ui/src/stories/ui/chart.stories.tsx | 22 + .../ui/src/stories/ui/checkbox.stories.tsx | 16 + .../ui/src/stories/ui/collapsible.stories.tsx | 15 + .../ui/src/stories/ui/combobox.stories.tsx | 23 + packages/ui/src/stories/ui/dialog.stories.tsx | 28 + .../src/stories/ui/dropdown-menu.stories.tsx | 24 + packages/ui/src/stories/ui/empty.stories.tsx | 17 + .../ui/src/stories/ui/input-group.stories.tsx | 28 + packages/ui/src/stories/ui/input.stories.tsx | 8 + packages/ui/src/stories/ui/label.stories.tsx | 8 + .../ui/src/stories/ui/pagination.stories.tsx | 38 + packages/ui/src/stories/ui/select.stories.tsx | 25 + .../ui/src/stories/ui/separator.stories.tsx | 16 + packages/ui/src/stories/ui/sheet.stories.tsx | 21 + .../ui/src/stories/ui/sidebar.stories.tsx | 17 + .../ui/src/stories/ui/skeleton.stories.tsx | 8 + packages/ui/src/stories/ui/table.stories.tsx | 25 + .../ui/src/stories/ui/textarea.stories.tsx | 8 + .../ui/src/stories/ui/tooltip.stories.tsx | 16 + packages/ui/src/styles.css | 143 +- packages/ui/src/styles/base.css | 1318 ++++++++++ packages/ui/vite.config.ts | 23 +- packages/ui/vitest.config.ts | 2 +- pnpm-lock.yaml | 2132 ++++++++++++++++- pnpm-workspace.yaml | 10 + 63 files changed, 7773 insertions(+), 55 deletions(-) create mode 100644 packages/ui/.storybook/main.ts create mode 100644 packages/ui/.storybook/preview.ts create mode 100644 packages/ui/src/components/ui/alert-dialog.tsx create mode 100644 packages/ui/src/components/ui/avatar.tsx create mode 100644 packages/ui/src/components/ui/badge.tsx create mode 100644 packages/ui/src/components/ui/card.tsx create mode 100644 packages/ui/src/components/ui/chart.tsx create mode 100644 packages/ui/src/components/ui/checkbox.tsx create mode 100644 packages/ui/src/components/ui/collapsible.tsx create mode 100644 packages/ui/src/components/ui/combobox.tsx create mode 100644 packages/ui/src/components/ui/dialog.tsx create mode 100644 packages/ui/src/components/ui/dropdown-menu.tsx create mode 100644 packages/ui/src/components/ui/empty.tsx create mode 100644 packages/ui/src/components/ui/input-group.tsx create mode 100644 packages/ui/src/components/ui/input.tsx create mode 100644 packages/ui/src/components/ui/label.tsx create mode 100644 packages/ui/src/components/ui/pagination.tsx create mode 100644 packages/ui/src/components/ui/select.tsx create mode 100644 packages/ui/src/components/ui/separator.tsx create mode 100644 packages/ui/src/components/ui/sheet.tsx create mode 100644 packages/ui/src/components/ui/sidebar.tsx create mode 100644 packages/ui/src/components/ui/skeleton.tsx create mode 100644 packages/ui/src/components/ui/table.tsx create mode 100644 packages/ui/src/components/ui/textarea.tsx create mode 100644 packages/ui/src/components/ui/tooltip.tsx create mode 100644 packages/ui/src/hooks/use-mobile.ts create mode 100644 packages/ui/src/stories/ui/alert-dialog.stories.tsx create mode 100644 packages/ui/src/stories/ui/avatar.stories.tsx create mode 100644 packages/ui/src/stories/ui/badge.stories.tsx create mode 100644 packages/ui/src/stories/ui/button.stories.tsx create mode 100644 packages/ui/src/stories/ui/card.stories.tsx create mode 100644 packages/ui/src/stories/ui/chart.stories.tsx create mode 100644 packages/ui/src/stories/ui/checkbox.stories.tsx create mode 100644 packages/ui/src/stories/ui/collapsible.stories.tsx create mode 100644 packages/ui/src/stories/ui/combobox.stories.tsx create mode 100644 packages/ui/src/stories/ui/dialog.stories.tsx create mode 100644 packages/ui/src/stories/ui/dropdown-menu.stories.tsx create mode 100644 packages/ui/src/stories/ui/empty.stories.tsx create mode 100644 packages/ui/src/stories/ui/input-group.stories.tsx create mode 100644 packages/ui/src/stories/ui/input.stories.tsx create mode 100644 packages/ui/src/stories/ui/label.stories.tsx create mode 100644 packages/ui/src/stories/ui/pagination.stories.tsx create mode 100644 packages/ui/src/stories/ui/select.stories.tsx create mode 100644 packages/ui/src/stories/ui/separator.stories.tsx create mode 100644 packages/ui/src/stories/ui/sheet.stories.tsx create mode 100644 packages/ui/src/stories/ui/sidebar.stories.tsx create mode 100644 packages/ui/src/stories/ui/skeleton.stories.tsx create mode 100644 packages/ui/src/stories/ui/table.stories.tsx create mode 100644 packages/ui/src/stories/ui/textarea.stories.tsx create mode 100644 packages/ui/src/stories/ui/tooltip.stories.tsx create mode 100644 packages/ui/src/styles/base.css diff --git a/.gitignore b/.gitignore index 63adc91..dabd051 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ apps/*/.tanstack/tmp/ apps/*/.wrangler/ apps/*/dist/ apps/*/node_modules +packages/*/storybook-static/ +storybook-static/ # Local Pi runtime state .pi/ diff --git a/nx.json b/nx.json index 39a16b1..c03e6e3 100644 --- a/nx.json +++ b/nx.json @@ -38,6 +38,15 @@ "buildDepsTargetName": "build-deps", "watchDepsTargetName": "watch-deps" } + }, + { + "plugin": "@nx/storybook/plugin", + "options": { + "buildStorybookTargetName": "build-storybook", + "serveStorybookTargetName": "storybook", + "testStorybookTargetName": "test-storybook", + "staticStorybookTargetName": "static-storybook" + } } ], "targetDefaults": { diff --git a/package.json b/package.json index a99df61..6ab48f1 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,9 @@ "@effect/language-service": "catalog:", "@effect/tsgo": "catalog:", "@nx/js": "catalog:", + "@nx/storybook": "catalog:", "@nx/vite": "catalog:", + "@tailwindcss/vite": "catalog:", "@typescript/native-preview": "catalog:", "dprint": "catalog:", "husky": "catalog:", diff --git a/packages/ui/.storybook/main.ts b/packages/ui/.storybook/main.ts new file mode 100644 index 0000000..2d57b93 --- /dev/null +++ b/packages/ui/.storybook/main.ts @@ -0,0 +1,21 @@ +import tailwindcss from "@tailwindcss/vite" +import type { StorybookConfig } from "storybook-solidjs-vite" +import { mergeConfig } from "vite" + +const config: StorybookConfig = { + stories: ["../src/stories/ui/**/*.stories.@(ts|tsx|mdx)"], + addons: [], + framework: { + name: "storybook-solidjs-vite", + options: {}, + }, + viteFinal: async (viteConfig) => + mergeConfig(viteConfig, { + plugins: [tailwindcss()], + resolve: { + tsconfigPaths: true, + }, + }), +} + +export default config diff --git a/packages/ui/.storybook/preview.ts b/packages/ui/.storybook/preview.ts new file mode 100644 index 0000000..84490a0 --- /dev/null +++ b/packages/ui/.storybook/preview.ts @@ -0,0 +1,15 @@ +import type { Preview } from "storybook-solidjs-vite" + +// oxlint-disable-next-line import/no-unassigned-import -- Storybook preview applies global UI styles. +import "../src/styles.css" + +const preview: Preview = { + parameters: { + viewport: { + defaultViewport: "mobile1", + }, + layout: "centered", + }, +} + +export default preview diff --git a/packages/ui/package.json b/packages/ui/package.json index c4b1894..54bc046 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -15,6 +15,10 @@ "types": "./src/components/ui/button.tsx", "import": "./src/components/ui/button.tsx" }, + "./chart": { + "types": "./src/components/ui/chart.tsx", + "import": "./src/components/ui/chart.tsx" + }, "./styles.css": "./src/styles.css", "./components/ui/*": { "types": "./src/components/ui/*.tsx", @@ -41,24 +45,34 @@ "scripts": { "build": "vite build && tsgo -p tsconfig.lib.json", "typecheck": "tsgo --noEmit -p tsconfig.json", - "test": "vitest run --passWithNoTests" + "test": "vitest run --passWithNoTests", + "storybook": "storybook dev -p 6006", + "build-storybook": "storybook build" }, "peerDependencies": { "solid-js": "catalog:" }, "devDependencies": { "@effect/tsgo": "catalog:", + "@testing-library/jest-dom": "catalog:", + "jsdom": "catalog:", "solid-js": "catalog:", + "storybook": "catalog:", + "storybook-solidjs-vite": "catalog:", "typescript": "catalog:", "vite": "catalog:", "vite-plugin-solid": "catalog:", "vitest": "catalog:" }, "dependencies": { + "@fontsource-variable/inter": "catalog:", "@kobalte/core": "catalog:", "class-variance-authority": "catalog:", "clsx": "catalog:", + "echarts": "catalog:", + "lucide-solid": "catalog:", "tailwind-merge": "catalog:", - "tailwindcss": "catalog:" + "tailwindcss": "catalog:", + "tw-animate-css": "catalog:" } } diff --git a/packages/ui/project.json b/packages/ui/project.json index 72d3d47..8fda2da 100644 --- a/packages/ui/project.json +++ b/packages/ui/project.json @@ -25,6 +25,20 @@ "command": "pnpm test" } }, + "storybook": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/ui", + "command": "pnpm storybook" + } + }, + "build-storybook": { + "executor": "nx:run-commands", + "options": { + "cwd": "packages/ui", + "command": "pnpm build-storybook" + } + }, "lint": { "executor": "nx-oxlint:lint", "options": { diff --git a/packages/ui/src/components/ui/alert-dialog.tsx b/packages/ui/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..b28bed1 --- /dev/null +++ b/packages/ui/src/components/ui/alert-dialog.tsx @@ -0,0 +1,226 @@ +import * as AlertDialogPrimitive from "@kobalte/core/alert-dialog" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" +import type { Component, ComponentProps, ValidComponent } from "solid-js" +import { mergeProps, splitProps } from "solid-js" + +import { cn } from "#lib/utils" +import { Button, type ButtonProps } from "#components/ui/button" + +const AlertDialog: Component = (props) => { + return +} + +type AlertDialogTriggerProps = PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogTriggerProps +> + +const AlertDialogTrigger = ( + props: AlertDialogTriggerProps, +) => { + return +} + +const AlertDialogPortal: Component = (props) => { + return +} + +type AlertDialogOverlayProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogOverlayProps + > + & Pick, "class"> + +const AlertDialogOverlay = ( + props: AlertDialogOverlayProps, +) => { + const [local, others] = splitProps(props as AlertDialogOverlayProps, ["class"]) + return ( + + ) +} + +type AlertDialogContentProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogContentProps + > + & Pick, "class"> + & { size?: "default" | "sm" } + +const AlertDialogContent = ( + props: AlertDialogContentProps, +) => { + const mergedProps = mergeProps({ size: "default" } as AlertDialogContentProps, props) + const [local, others] = splitProps(mergedProps, ["class", "size"]) + return ( + + + + + ) +} + +type AlertDialogHeaderProps = ComponentProps<"div"> + +const AlertDialogHeader = (props: AlertDialogHeaderProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type AlertDialogFooterProps = ComponentProps<"div"> + +const AlertDialogFooter = (props: AlertDialogFooterProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type AlertDialogMediaProps = ComponentProps<"div"> + +const AlertDialogMedia = (props: AlertDialogMediaProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type AlertDialogTitleProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogTitleProps + > + & Pick, "class"> + +const AlertDialogTitle = (props: AlertDialogTitleProps) => { + const [local, others] = splitProps(props as AlertDialogTitleProps, ["class"]) + return ( + + ) +} + +type AlertDialogDescriptionProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogDescriptionProps + > + & Pick, "class"> + +const AlertDialogDescription = ( + props: AlertDialogDescriptionProps, +) => { + const [local, others] = splitProps(props as AlertDialogDescriptionProps, ["class"]) + return ( + + ) +} + +type AlertDialogActionProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogCloseButtonProps + > + & Pick + +const AlertDialogAction = ( + props: AlertDialogActionProps, +) => { + const mergedProps = mergeProps({ variant: "default", size: "default" }, props) + const [local, others] = splitProps(mergedProps as AlertDialogActionProps, [ + "class", + "variant", + "size", + ]) + return ( + + ) +} + +type AlertDialogCancelProps = + & PolymorphicProps< + T, + AlertDialogPrimitive.AlertDialogCloseButtonProps + > + & Pick + +const AlertDialogCancel = ( + props: AlertDialogCancelProps, +) => { + const mergedProps = mergeProps({ variant: "outline", size: "default" }, props) + const [local, others] = splitProps(mergedProps as AlertDialogCancelProps, [ + "class", + "variant", + "size", + ]) + return ( + + ) +} + +export { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogMedia, + AlertDialogOverlay, + AlertDialogPortal, + AlertDialogTitle, + AlertDialogTrigger, +} diff --git a/packages/ui/src/components/ui/avatar.tsx b/packages/ui/src/components/ui/avatar.tsx new file mode 100644 index 0000000..cc63a1e --- /dev/null +++ b/packages/ui/src/components/ui/avatar.tsx @@ -0,0 +1,125 @@ +import * as ImagePrimitive from "@kobalte/core/image" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" +import type { ComponentProps, ValidComponent } from "solid-js" +import { mergeProps, splitProps } from "solid-js" + +import { cn } from "#lib/utils" + +type AvatarRootProps = + & PolymorphicProps< + T, + ImagePrimitive.ImageRootProps + > + & Pick, "class"> + & { + size?: "sm" | "default" | "lg" + } + +const Avatar = (props: AvatarRootProps) => { + const mergedProps = mergeProps({ size: "default" }, props) + const [local, others] = splitProps(mergedProps as AvatarRootProps, ["class", "size"]) + return ( + + ) +} + +type AvatarImageProps = + & PolymorphicProps< + T, + ImagePrimitive.ImageImgProps + > + & Pick, "class"> + +const AvatarImage = (props: AvatarImageProps) => { + const [local, others] = splitProps(props as AvatarImageProps, ["class"]) + return ( + + ) +} + +type AvatarFallbackProps = + & PolymorphicProps< + T, + ImagePrimitive.ImageFallbackProps + > + & Pick, "class"> + +const AvatarFallback = (props: AvatarFallbackProps) => { + const [local, others] = splitProps(props as AvatarFallbackProps, ["class"]) + return ( + + ) +} + +type AvatarBadgeProps = ComponentProps<"span"> + +function AvatarBadge(props: AvatarBadgeProps) { + const [local, others] = splitProps(props as AvatarBadgeProps, ["class"]) + return ( + svg]:hidden", + "group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2", + "group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2", + local.class, + )} + {...others} + /> + ) +} + +type AvatarGroupProps = ComponentProps<"div"> + +function AvatarGroup(props: AvatarGroupProps) { + const [local, others] = splitProps(props as AvatarGroupProps, ["class"]) + return ( +
+ ) +} + +type AvatarGroupCountProps = ComponentProps<"div"> + +function AvatarGroupCount(props: AvatarGroupCountProps) { + const [local, others] = splitProps(props as AvatarGroupCountProps, ["class"]) + return ( +
+ ) +} + +export { Avatar, AvatarBadge, AvatarFallback, AvatarGroup, AvatarGroupCount, AvatarImage } diff --git a/packages/ui/src/components/ui/badge.tsx b/packages/ui/src/components/ui/badge.tsx new file mode 100644 index 0000000..4ebc0e4 --- /dev/null +++ b/packages/ui/src/components/ui/badge.tsx @@ -0,0 +1,42 @@ +import { type BadgeRootProps, Root } from "@kobalte/core/badge" +import type { PolymorphicProps } from "@kobalte/core/polymorphic" +import { cva, type VariantProps } from "class-variance-authority" +import { splitProps, type ValidComponent } from "solid-js" +import { cn } from "#lib/utils" + +const badgeVariants = cva( + "group/badge z-badge inline-flex w-fit shrink-0 items-center justify-center overflow-hidden whitespace-nowrap transition-colors focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none", + { + variants: { + variant: { + default: "z-badge-variant-default", + secondary: "z-badge-variant-secondary", + destructive: "z-badge-variant-destructive", + outline: "z-badge-variant-outline", + ghost: "z-badge-variant-ghost", + link: "z-badge-variant-link", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +) + +type BadgeProps = + & PolymorphicProps> + & VariantProps + +const Badge = (props: BadgeProps) => { + const [local, others] = splitProps(props as BadgeProps, ["class", "variant"]) + return ( + + ) +} + +export { Badge, badgeVariants } diff --git a/packages/ui/src/components/ui/button.tsx b/packages/ui/src/components/ui/button.tsx index bc3db04..150b478 100644 --- a/packages/ui/src/components/ui/button.tsx +++ b/packages/ui/src/components/ui/button.tsx @@ -35,25 +35,15 @@ const buttonVariants = cva( ) type ButtonProps = - & PolymorphicProps< - T, - ButtonRootProps - > + & PolymorphicProps> & VariantProps & Pick, "class"> const Button = (props: ButtonProps) => { - const [local, others] = splitProps(props as ButtonProps, [ - "variant", - "size", - "class", - ]) + const [local, others] = splitProps(props as ButtonProps, ["variant", "size", "class"]) return ( diff --git a/packages/ui/src/components/ui/card.tsx b/packages/ui/src/components/ui/card.tsx new file mode 100644 index 0000000..0c30ee4 --- /dev/null +++ b/packages/ui/src/components/ui/card.tsx @@ -0,0 +1,91 @@ +import { type ComponentProps, mergeProps, splitProps } from "solid-js" +import { cn } from "#lib/utils" + +type CardProps = ComponentProps<"div"> & { size?: "default" | "sm" } + +const Card = (props: CardProps) => { + const mergedProps = mergeProps({ size: "default" } as const, props) + const [local, others] = splitProps(mergedProps, ["class", "size"]) + return ( +
+ ) +} + +type CardHeaderProps = ComponentProps<"div"> + +const CardHeader = (props: CardHeaderProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type CardTitleProps = ComponentProps<"div"> + +const CardTitle = (props: CardTitleProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type CardDescriptionProps = ComponentProps<"div"> + +const CardDescription = (props: CardDescriptionProps) => { + const [local, others] = splitProps(props, ["class"]) + return
+} + +type CardActionProps = ComponentProps<"div"> + +const CardAction = (props: CardActionProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +
+ ) +} + +type CardContentProps = ComponentProps<"div"> + +const CardContent = (props: CardContentProps) => { + const [local, others] = splitProps(props, ["class"]) + return
+} + +type CardFooterProps = ComponentProps<"div"> + +const CardFooter = (props: CardFooterProps) => { + const [local, others] = splitProps(props, ["class"]) + return ( +