@utopia-studio-design/design-system-cli 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/README.md +12 -41
  2. package/bin/utopia-ds-mcp.mjs +55 -0
  3. package/bin/utopia-ds.mjs +233 -318
  4. package/data/docs/ai-platform-plan.md +53 -0
  5. package/data/docs/arabic-friendly.md +72 -0
  6. package/data/docs/foundations.md +154 -0
  7. package/data/docs/guide.md +37 -0
  8. package/data/docs/quick-start-ai.md +122 -0
  9. package/data/docs/shadcn-conversion.md +35 -0
  10. package/data/docs/theme-authoring.md +46 -0
  11. package/data/manifests/catalog.json +547 -0
  12. package/data/manifests/components.json +4780 -0
  13. package/data/manifests/patterns.json +12 -0
  14. package/data/manifests/templates.json +96 -0
  15. package/data/manifests/theme-barrier-intelligence.json +123 -0
  16. package/data/manifests/theme-dextrum.json +255 -0
  17. package/data/manifests/theme-utopia-default.json +163 -0
  18. package/data/manifests/themes.json +417 -0
  19. package/data/templates/saas-solution-homepage/README.md +52 -0
  20. package/data/templates/saas-solution-homepage/agents/index.html +1 -0
  21. package/data/templates/saas-solution-homepage/changelog/index.html +1 -0
  22. package/data/templates/saas-solution-homepage/contact-sales/index.html +1 -0
  23. package/data/templates/saas-solution-homepage/customers/aster-labs/index.html +1 -0
  24. package/data/templates/saas-solution-homepage/customers/index.html +1 -0
  25. package/data/templates/saas-solution-homepage/index.html +17 -0
  26. package/data/templates/saas-solution-homepage/integrations/index.html +1 -0
  27. package/data/templates/saas-solution-homepage/integrations/slack/index.html +1 -0
  28. package/data/templates/saas-solution-homepage/main.tsx +642 -0
  29. package/data/templates/saas-solution-homepage/pricing/index.html +1 -0
  30. package/data/templates/saas-solution-homepage/product/index.html +1 -0
  31. package/data/templates/saas-solution-homepage/styles.css +863 -0
  32. package/data/templates/saas-solution-homepage/template.manifest.json +36 -0
  33. package/lib/api.mjs +152 -0
  34. package/package.json +17 -15
  35. package/LICENSE +0 -21
  36. package/templates/agent-docs/examples.md +0 -33
  37. package/templates/agent-docs/index.md +0 -78
  38. package/templates/agent-docs/llms.txt +0 -33
  39. package/templates/components/badge.tsx +0 -12
  40. package/templates/components/brand-action.tsx +0 -32
  41. package/templates/components/button-group.tsx +0 -30
  42. package/templates/components/button.tsx +0 -44
  43. package/templates/components/card.tsx +0 -23
  44. package/templates/components/cta-section.tsx +0 -34
  45. package/templates/components/icon-button.tsx +0 -44
  46. package/templates/components/page-hero.tsx +0 -47
  47. package/templates/components/tooltip.tsx +0 -48
  48. package/templates/components/topbar.tsx +0 -59
  49. package/templates/lib/utils.ts +0 -6
  50. package/templates/styles/styles.css +0 -271
  51. package/templates/styles/tokens.css +0 -21
  52. package/templates/tokens.json +0 -41
@@ -0,0 +1,36 @@
1
+ {
2
+ "id": "template-saas-solution-homepage",
3
+ "name": "SaaS Solution Website",
4
+ "category": "website-page",
5
+ "status": "available",
6
+ "purpose": "A complete product-led SaaS website family spanning marketing, product, proof, adoption, updates, integrations, and sales contact surfaces.",
7
+ "packageImport": "@utopia-studio-design/design-system",
8
+ "sourcePath": "templates/saas-solution-homepage/main.tsx",
9
+ "entryPath": "templates/saas-solution-homepage/index.html",
10
+ "shadcnFoundation": ["Navigation Menu", "Button", "Badge", "Card", "Input", "Select", "Textarea"],
11
+ "requiredTokens": ["background", "foreground", "muted-foreground", "surface", "surface-strong", "border", "primary", "ring", "radius-control", "radius-surface", "space-*", "motion-duration-*"],
12
+ "sections": ["Hero", "Product interface preview", "Core workflow", "Agents and automation", "Integrations", "Security and reliability", "Team proof", "Adoption CTA", "Footer"],
13
+ "pages": [
14
+ { "id": "home", "path": "index.html", "purpose": "Primary narrative and product proof" },
15
+ { "id": "product", "path": "product/index.html", "purpose": "Product overview and capability architecture" },
16
+ { "id": "agents", "path": "agents/index.html", "purpose": "Automation and agent governance" },
17
+ { "id": "integrations", "path": "integrations/index.html", "purpose": "Searchable integration directory" },
18
+ { "id": "integration-detail", "path": "integrations/slack/index.html", "purpose": "Integration capabilities and setup" },
19
+ { "id": "customers", "path": "customers/index.html", "purpose": "Customer proof library" },
20
+ { "id": "customer-story", "path": "customers/aster-labs/index.html", "purpose": "Long-form customer case study" },
21
+ { "id": "pricing", "path": "pricing/index.html", "purpose": "Plans, comparison, and FAQ" },
22
+ { "id": "changelog", "path": "changelog/index.html", "purpose": "Product update timeline" },
23
+ { "id": "contact-sales", "path": "contact-sales/index.html", "purpose": "Sales qualification form and success state" }
24
+ ],
25
+ "features": { "lightDark": true, "rtl": true, "responsive": true, "seededDemoData": true, "scrollAwareNavigation": true, "reducedMotion": true },
26
+ "useWhen": ["A SaaS product needs a complete product-led marketing narrative", "A real interface preview can carry the product proof", "Automation, integrations, and reliability are material buying criteria"],
27
+ "avoidWhen": ["The page is a single-feature launch", "The primary experience is editorial", "There is no reviewed product model behind the interface claims"],
28
+ "neverInvent": ["Do not copy Linear branding, copy, assets, or proprietary interface details", "Do not hard-code brand primitives inside reusable components", "Do not use left/right-only layout APIs where logical properties work", "Do not invent production customer claims from seeded demo content"],
29
+ "queryParameters": { "seed": "Integer used to deterministically select product, company, people, pricing, and proof data." },
30
+ "defaultUrl": "/templates/saas-solution-homepage/index.html?seed=1974341818#agents",
31
+ "themeContract": {
32
+ "default": "utopia-default",
33
+ "generationFlag": "--theme <id>",
34
+ "ownership": "The active theme owns dark/light semantic mappings; the template owns no brand primitives."
35
+ }
36
+ }
package/lib/api.mjs ADDED
@@ -0,0 +1,152 @@
1
+ import { existsSync, readFileSync, readdirSync } from 'node:fs'
2
+ import { dirname, join, resolve } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+
5
+ const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
6
+ const workspaceRoot = resolve(packageRoot, '../..')
7
+ const packagedDataRoot = join(packageRoot, 'data')
8
+ const hasWorkspaceSource = existsSync(join(workspaceRoot, 'packages/design-system/src/manifests/components.json'))
9
+
10
+ export const apiVersion = 1
11
+ export const cliVersion = '0.3.0'
12
+ export const mcpLaunch = {
13
+ command: 'npx',
14
+ args: ['-y', '--package', '@utopia-studio-design/design-system-cli', 'utopia-ds', 'mcp'],
15
+ }
16
+
17
+ export function paths(root = hasWorkspaceSource ? workspaceRoot : packagedDataRoot) {
18
+ return {
19
+ root,
20
+ manifests: hasWorkspaceSource && root === workspaceRoot ? join(root, 'packages/design-system/src/manifests') : join(root, 'manifests'),
21
+ docs: hasWorkspaceSource && root === workspaceRoot ? join(root, 'docs/design-system') : join(root, 'docs'),
22
+ }
23
+ }
24
+
25
+ function readJson(path) {
26
+ return JSON.parse(readFileSync(path, 'utf8'))
27
+ }
28
+
29
+ function normalize(value) {
30
+ return String(value).toLowerCase().replace(/[^a-z0-9]/g, '')
31
+ }
32
+
33
+ export function envelope(type, data, meta = {}) {
34
+ return { apiVersion, type, data, meta: { version: cliVersion, ...meta } }
35
+ }
36
+
37
+ export function getCatalog() {
38
+ return readJson(join(paths().manifests, 'catalog.json'))
39
+ }
40
+
41
+ export function listComponents() {
42
+ return [...readJson(join(paths().manifests, 'components.json')).components]
43
+ .sort((a, b) => a.name.localeCompare(b.name, undefined, { numeric: true, sensitivity: 'base' }))
44
+ }
45
+
46
+ export function getComponent(name) {
47
+ const key = normalize(name)
48
+ return listComponents().find((item) => normalize(item.name) === key) ?? null
49
+ }
50
+
51
+ export function listTemplates() {
52
+ return readJson(join(paths().manifests, 'templates.json')).templates
53
+ }
54
+
55
+ export function getTemplate(name) {
56
+ const key = normalize(name)
57
+ return listTemplates().find((item) => normalize(item.id) === key || normalize(item.title) === key) ?? null
58
+ }
59
+
60
+ export function listThemes() {
61
+ return readJson(join(paths().manifests, 'themes.json')).themes
62
+ }
63
+
64
+ export function getTheme(name) {
65
+ const key = normalize(name)
66
+ const entry = listThemes().find((item) => normalize(item.id) === key || normalize(item.name) === key)
67
+ if (!entry) return null
68
+ const policyPath = hasWorkspaceSource
69
+ ? join(workspaceRoot, entry.policyManifest)
70
+ : join(paths().manifests, entry.policyManifest.split('/').at(-1))
71
+ return { ...entry, policy: readJson(policyPath) }
72
+ }
73
+
74
+ export function listDocs() {
75
+ return readdirSync(paths().docs)
76
+ .filter((name) => name.endsWith('.md'))
77
+ .map((name) => name.slice(0, -3))
78
+ .sort()
79
+ }
80
+
81
+ export function getDoc(topic) {
82
+ const match = listDocs().find((name) => normalize(name) === normalize(topic))
83
+ if (!match) return null
84
+ return { topic: match, content: readFileSync(join(paths().docs, `${match}.md`), 'utf8') }
85
+ }
86
+
87
+ export function search(query) {
88
+ const terms = String(query).toLowerCase().split(/\s+/).filter(Boolean)
89
+ const score = (text) => terms.reduce((total, term) => total + (text.toLowerCase().includes(term) ? 1 : 0), 0)
90
+ const results = [
91
+ ...listComponents().map((item) => ({ kind: 'component', id: item.name, title: item.name, summary: [...item.useWhen, item.category].join(' '), item })),
92
+ ...listTemplates().map((item) => ({ kind: 'template', id: item.id, title: item.title, summary: `${item.category} ${item.purpose}`, item })),
93
+ ...listThemes().map((item) => ({ kind: 'theme', id: item.id, title: item.name, summary: item.role, item })),
94
+ ...listDocs().map((topic) => ({ kind: 'docs', id: topic, title: topic, summary: getDoc(topic)?.content.slice(0, 500) ?? '', item: { topic } })),
95
+ ]
96
+ return results
97
+ .map((result) => ({ ...result, score: score(`${result.title} ${result.summary}`) }))
98
+ .filter((result) => result.score > 0)
99
+ .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
100
+ .map(({ summary: _summary, ...result }) => result)
101
+ }
102
+
103
+ export function capabilityManifest() {
104
+ return {
105
+ name: 'utopia-ds',
106
+ version: cliVersion,
107
+ description: 'Ceramic Design System interface for humans, agents, build tools, and MCP clients.',
108
+ transports: ['cli', 'json', 'mcp-stdio', 'programmatic-api'],
109
+ globalOptions: [
110
+ { flag: '--json', type: 'boolean', description: 'Return a typed JSON envelope.' },
111
+ { flag: '--dense', type: 'boolean', description: 'Return compact agent-readable text.' },
112
+ ],
113
+ commands: [
114
+ { name: 'init', args: ['[directory]'], flags: ['--yes', '--theme <id>'], responseType: 'init-result' },
115
+ { name: 'search', args: ['<query>'], flags: [], responseType: 'search-results' },
116
+ { name: 'component', args: ['<name>|--list'], flags: [], responseType: 'component|component-list' },
117
+ { name: 'template', args: ['<id>|--list'], flags: ['--skeleton', '--copy <directory>', '--theme <id>', '--force'], responseType: 'template|template-list|template-copy-result' },
118
+ { name: 'theme', args: ['<id>|--list', 'create <id> [directory]'], flags: ['--force'], responseType: 'theme|theme-list|theme-create-result' },
119
+ { name: 'docs', args: ['<topic>|--list'], flags: [], responseType: 'docs|docs-list' },
120
+ { name: 'manifest', args: [], flags: [], responseType: 'manifest' },
121
+ { name: 'doctor', args: [], flags: [], responseType: 'doctor-result' },
122
+ { name: 'mcp', args: [], flags: [], responseType: 'stdio-server' },
123
+ ],
124
+ mcp: {
125
+ ...mcpLaunch,
126
+ tools: ['search', 'list_components', 'get_component', 'list_templates', 'get_template', 'list_themes', 'get_theme', 'list_docs', 'get_docs', 'doctor'],
127
+ },
128
+ }
129
+ }
130
+
131
+ export function repositoryDoctor() {
132
+ const required = [
133
+ 'catalog.json', 'components.json', 'templates.json', 'themes.json',
134
+ ].map((name) => join(paths().manifests, name))
135
+ const docs = ['quick-start-ai', 'arabic-friendly', 'theme-authoring'].map((name) => join(paths().docs, `${name}.md`))
136
+ const missing = [...required, ...docs].filter((path) => !existsSync(path))
137
+ return {
138
+ ok: missing.length === 0,
139
+ checks: {
140
+ manifests: required.every(existsSync),
141
+ docs: docs.every(existsSync),
142
+ components: listComponents().length,
143
+ templates: listTemplates().length,
144
+ themes: listThemes().length,
145
+ mcp: existsSync(join(packageRoot, 'bin/utopia-ds-mcp.mjs'))
146
+ && mcpLaunch.command === 'npx'
147
+ && mcpLaunch.args.includes('utopia-ds')
148
+ && mcpLaunch.args.at(-1) === 'mcp',
149
+ },
150
+ missing,
151
+ }
152
+ }
package/package.json CHANGED
@@ -1,30 +1,32 @@
1
1
  {
2
2
  "name": "@utopia-studio-design/design-system-cli",
3
- "version": "0.1.0",
4
- "description": "CLI for installing, inspecting, and validating the Utopia Studio design system.",
3
+ "version": "0.3.0",
4
+ "description": "AI-readable CLI and MCP server for Ceramic Design System",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "bin": {
8
- "utopia-ds": "bin/utopia-ds.mjs"
8
+ "utopia-ds": "bin/utopia-ds.mjs",
9
+ "ceramic-mcp": "bin/utopia-ds-mcp.mjs"
10
+ },
11
+ "exports": {
12
+ ".": "./lib/api.mjs",
13
+ "./api": "./lib/api.mjs",
14
+ "./mcp": "./bin/utopia-ds-mcp.mjs"
9
15
  },
10
16
  "files": [
11
17
  "bin",
12
- "templates",
13
- "README.md",
14
- "LICENSE"
18
+ "lib",
19
+ "data",
20
+ "README.md"
15
21
  ],
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
22
  "scripts": {
20
- "build": "node bin/utopia-ds.mjs --help",
21
- "check": "node bin/utopia-ds.mjs tokens > /tmp/utopia-ds-tokens.json && node bin/utopia-ds.mjs doctor --cwd ../..",
22
- "prepublishOnly": "npm run check"
23
+ "sync-data": "node scripts/sync-data.mjs",
24
+ "prepack": "npm run sync-data"
23
25
  },
24
- "dependencies": {
25
- "@utopia-studio-design/design-system": "0.1.0"
26
+ "publishConfig": {
27
+ "access": "public"
26
28
  },
27
29
  "engines": {
28
- "node": ">=18.18"
30
+ "node": ">=20"
29
31
  }
30
32
  }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 The Utopia Studio
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
@@ -1,33 +0,0 @@
1
- # Site Examples
2
-
3
- Use this page as the human-readable companion to `site-examples.json`.
4
-
5
- ## Source
6
-
7
- - Prefer `packages/design-system/src/manifests/site-examples.json` when it exists in the app.
8
- - If the manifest is installed through the package, read `node_modules/@utopia-studio-design/design-system/site-examples.json`.
9
- - Use this document as an index for humans and AI agents; keep detailed examples source-driven.
10
-
11
- ## Rules
12
-
13
- - Use real Utopia sample copy before inventing placeholder content.
14
- - Preserve `sourceRoute` and source file references when generating docs or UI examples.
15
- - Use examples for content shape, tone, and component fit only.
16
- - Use `tokens.json`, `themes.json`, `components.json`, and `patterns.json` for design rules.
17
-
18
- ## Expected Collections
19
-
20
- - `sourceRoutes`: route labels, hrefs, and source files.
21
- - `badges`: status and taxonomy labels with variants.
22
- - `cards`: Card-ready examples with eyebrow, title, body, href, source route, and component fit.
23
- - `heroes`: PageHero-ready examples with actions.
24
- - `ctas`: CtaSection-ready examples with actions.
25
- - `agentRules`: source-example rules for AI agents.
26
-
27
- ## Workflow
28
-
29
- 1. Read `site-examples.json`.
30
- 2. Choose the collection that matches the component or template.
31
- 3. Copy the content shape, not just the words.
32
- 4. Keep Utopia-specific language and routes.
33
- 5. Do not create generic SaaS metrics, fake companies, decorative cards, or placeholder copy.
@@ -1,78 +0,0 @@
1
- # Utopia Studio Design System for AI Agents
2
-
3
- Use this as the canonical design guidance when generating Utopia Studio UI.
4
-
5
- ## Intent
6
-
7
- The interface is architectural, editorial, precise, calm, and commercially credible.
8
-
9
- ## Rules
10
-
11
- - Utopia is the default/reference theme, not the whole design system.
12
- - For the Utopia brand site, use TWK Lausanne, Special Black, White, Brick Red, Light Grey, and black/red tints.
13
- - For themeable apps/templates, map every visual decision through semantic roles in `themes.json`.
14
- - Use `site-examples.json` for real Utopia examples before writing placeholder content.
15
- - Use `docs/design-system/examples.md` when a human-readable source-example summary is enough.
16
- - Use `templates.json` for page structure before inventing layouts.
17
- - Keep all primary surfaces square.
18
- - Use hairline borders instead of shadows.
19
- - In the Utopia theme, Brick Red is action, focus, active states, and emphasis.
20
- - Do not create gradients, glass panels, decorative blobs, or rounded SaaS cards.
21
- - Use semantic headings and visible focus states.
22
-
23
- ## Theme Decision Gate
24
-
25
- Before generating UI, decide:
26
-
27
- 1. Is this for the Utopia Studio brand site?
28
- 2. Is this for a themeable app/template?
29
- 3. Is this a new theme?
30
-
31
- Use the locked `utopia` theme for the brand site. For themeable contexts, use semantic roles and make sure every role in `themes.json` is defined before using a new theme.
32
-
33
- ## Core Components
34
-
35
- Before inventing UI, inspect each component's `agentContract` in `packages/design-system/src/manifests/components.json`.
36
- Read component `props`, `anatomy`, `states`, and `slots` before composing examples.
37
- Before writing docs examples, inspect `packages/design-system/src/manifests/site-examples.json`.
38
- When reading quickly, use `docs/design-system/examples.md` for the generated source-example summary.
39
- Before scaffolding a page, inspect `packages/design-system/src/manifests/templates.json`.
40
-
41
- - Button
42
- - IconButton
43
- - ButtonGroup
44
- - Card
45
- - Badge
46
- - PageHero
47
- - CtaSection
48
- - Topbar
49
-
50
- ## Agent Contract
51
-
52
- Each manifest component should define:
53
-
54
- - `useWhen`
55
- - `avoidWhen`
56
- - `fallbackToShadcn`
57
- - `requiredTokens`
58
- - `neverInvent`
59
- - `validationChecklist`
60
-
61
- Component entries can also define:
62
-
63
- - `props`
64
- - `anatomy`
65
- - `states`
66
- - `slots`
67
-
68
- If a needed UI primitive is missing, first try `npx shadcn@latest add <primitive> --yes`, then layer Utopia tokens and rules on top.
69
-
70
- ## CLI
71
-
72
- ```sh
73
- utopia-ds init
74
- utopia-ds add button
75
- utopia-ds add card
76
- utopia-ds tokens
77
- utopia-ds doctor
78
- ```
@@ -1,33 +0,0 @@
1
- # Utopia Studio Design System
2
-
3
- Canonical package: @utopia-studio-design/design-system
4
- CLI package: @utopia-studio-design/design-system-cli
5
-
6
- Read first:
7
- - docs/design-system/agent-docs/index.md
8
- - docs/design-system/examples.md
9
- - packages/design-system/src/tokens.json
10
- - packages/design-system/src/manifests/components.json
11
- - packages/design-system/src/manifests/patterns.json
12
- - packages/design-system/src/manifests/themes.json
13
- - packages/design-system/src/manifests/site-examples.json
14
- - packages/design-system/src/manifests/templates.json
15
-
16
- Agent contract:
17
- - Read each component's agentContract before creating UI.
18
- - Read component props, anatomy, states, and slots before composing examples.
19
- - Prefer fallbackToShadcn via `npx shadcn@latest add <primitive> --yes`.
20
- - Never invent tokens, colors, shapes, or component APIs outside the manifest.
21
- - Utopia is the default/reference theme, not the whole design system.
22
- - For the Utopia brand site use the locked Utopia theme; for themeable apps/templates map decisions through semantic roles.
23
- - Use site-examples.json for real Utopia sample copy before inventing examples.
24
- - Use docs/design-system/examples.md when a human-readable source-example summary is enough.
25
- - Use templates.json for page structure before inventing layouts.
26
-
27
- Design rules:
28
- - Square geometry.
29
- - TWK Lausanne only.
30
- - Utopia theme palette: Brick Red, Special Black, White, Light Grey, and approved tints.
31
- - Themeable architecture: use semantic roles before raw values.
32
- - Borders over shadows.
33
- - No gradients, glassmorphism, blobs, or rounded SaaS cards.
@@ -1,12 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { cn } from '../../lib/utils'
4
-
5
- export function Badge({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
6
- return (
7
- <div
8
- className={cn('inline-flex w-fit items-center border px-2.5 py-1 text-xs font-medium uppercase', className)}
9
- {...props}
10
- />
11
- )
12
- }
@@ -1,32 +0,0 @@
1
- import { cva, type VariantProps } from 'class-variance-authority'
2
- import type * as React from 'react'
3
-
4
- import { cn } from '../../lib/utils'
5
-
6
- const brandActionVariants = cva('brand-action', {
7
- variants: {
8
- variant: {
9
- primary: 'brand-action--primary',
10
- secondary: 'brand-action--secondary',
11
- ghost: 'brand-action--ghost',
12
- },
13
- },
14
- defaultVariants: {
15
- variant: 'primary',
16
- },
17
- })
18
-
19
- export interface BrandActionProps
20
- extends React.AnchorHTMLAttributes<HTMLAnchorElement>,
21
- VariantProps<typeof brandActionVariants> {
22
- label: string
23
- }
24
-
25
- export function BrandAction({ className, label, variant, ...props }: BrandActionProps) {
26
- return (
27
- <a className={cn(brandActionVariants({ className, variant }))} {...props}>
28
- <span>{label}</span>
29
- {variant === 'primary' || !variant ? <span aria-hidden="true">-&gt;</span> : null}
30
- </a>
31
- )
32
- }
@@ -1,30 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { cn } from '../../lib/utils'
4
- import { Button, type ButtonProps } from './button'
5
-
6
- export function ButtonGroup({
7
- className,
8
- ...props
9
- }: React.HTMLAttributes<HTMLDivElement>) {
10
- return (
11
- <div
12
- className={cn('utopia-button-group', className)}
13
- role="group"
14
- {...props}
15
- />
16
- )
17
- }
18
-
19
- export const ButtonGroupButton = React.forwardRef<HTMLButtonElement, ButtonProps>(
20
- ({ className, variant = 'secondary', ...props }, ref) => (
21
- <Button
22
- className={cn('utopia-button-group__button', className)}
23
- ref={ref}
24
- variant={variant}
25
- {...props}
26
- />
27
- ),
28
- )
29
-
30
- ButtonGroupButton.displayName = 'ButtonGroupButton'
@@ -1,44 +0,0 @@
1
- import * as React from 'react'
2
- import { Slot } from '@radix-ui/react-slot'
3
- import { cva, type VariantProps } from 'class-variance-authority'
4
-
5
- import { cn } from '../../lib/utils'
6
-
7
- const buttonVariants = cva(
8
- 'utopia-button inline-flex shrink-0 items-center justify-center disabled:pointer-events-none disabled:opacity-50',
9
- {
10
- variants: {
11
- variant: {
12
- primary: '',
13
- default: '',
14
- destructive: 'bg-[hsl(var(--destructive))] text-[hsl(var(--destructive-foreground))]',
15
- secondary: 'bg-[hsl(var(--secondary))] text-[hsl(var(--secondary-foreground))]',
16
- outline: 'border-[hsl(var(--border))] bg-transparent text-[hsl(var(--foreground))]',
17
- ghost: 'border-transparent bg-transparent text-[hsl(var(--foreground))]',
18
- link: 'h-auto min-h-0 border-0 bg-transparent px-0 underline underline-offset-4 hover:text-[hsl(var(--primary))]',
19
- },
20
- size: {
21
- default: '',
22
- sm: 'h-[var(--button-height)] px-[var(--button-padding-x)]',
23
- lg: 'h-12',
24
- icon: 'size-11 p-0',
25
- },
26
- },
27
- defaultVariants: { variant: 'primary', size: 'default' },
28
- },
29
- )
30
-
31
- export interface ButtonProps
32
- extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33
- VariantProps<typeof buttonVariants> {
34
- asChild?: boolean
35
- }
36
-
37
- export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
38
- ({ asChild = false, className, size, variant, ...props }, ref) => {
39
- const Comp = asChild ? Slot : 'button'
40
- return <Comp className={cn(buttonVariants({ className, size, variant }))} ref={ref} {...props} />
41
- },
42
- )
43
-
44
- Button.displayName = 'Button'
@@ -1,23 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { cn } from '../../lib/utils'
4
-
5
- export function Card({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
6
- return <div className={cn('utopia-card', className)} {...props} />
7
- }
8
-
9
- export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
10
- return <div className={cn('grid gap-1.5 p-5', className)} {...props} />
11
- }
12
-
13
- export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
14
- return <h3 className={cn('m-0 text-2xl font-medium leading-tight', className)} {...props} />
15
- }
16
-
17
- export function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
18
- return <p className={cn('m-0 text-sm leading-6 opacity-75', className)} {...props} />
19
- }
20
-
21
- export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
22
- return <div className={cn('p-5 pt-0', className)} {...props} />
23
- }
@@ -1,34 +0,0 @@
1
- import { cn } from '../../lib/utils'
2
- import { BrandAction } from './brand-action'
3
-
4
- type CtaAction = {
5
- href: string
6
- label: string
7
- variant?: 'primary' | 'secondary' | 'ghost'
8
- }
9
-
10
- export function CtaSection({
11
- actions,
12
- className,
13
- eyebrow,
14
- title,
15
- }: {
16
- actions: CtaAction[]
17
- className?: string
18
- eyebrow: string
19
- title: string
20
- }) {
21
- return (
22
- <section className={cn('brand-cta-section', className)}>
23
- <div className="brand-cta-section__inner">
24
- <p className="brand-cta-section__eyebrow">{eyebrow}</p>
25
- <h2 className="m-0 text-[length:var(--type-hero-title)] font-bold leading-none">{title}</h2>
26
- <div className="brand-cta-section__actions">
27
- {actions.map((action) => (
28
- <BrandAction key={action.label} {...action} />
29
- ))}
30
- </div>
31
- </div>
32
- </section>
33
- )
34
- }
@@ -1,44 +0,0 @@
1
- import * as React from 'react'
2
-
3
- import { cn } from '../../lib/utils'
4
- import { Button, type ButtonProps } from './button'
5
- import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './tooltip'
6
-
7
- export interface IconButtonProps
8
- extends Omit<ButtonProps, 'children' | 'size'> {
9
- children: React.ReactNode
10
- label: string
11
- tooltip?: string
12
- }
13
-
14
- export const IconButton = React.forwardRef<HTMLButtonElement, IconButtonProps>(
15
- ({ children, className, label, tooltip, variant = 'ghost', ...props }, ref) => {
16
- const control = (
17
- <Button
18
- aria-label={label}
19
- className={cn('utopia-icon-button', className)}
20
- ref={ref}
21
- size="icon"
22
- variant={variant}
23
- {...props}
24
- >
25
- {children}
26
- </Button>
27
- )
28
-
29
- if (!tooltip) {
30
- return control
31
- }
32
-
33
- return (
34
- <TooltipProvider>
35
- <Tooltip>
36
- <TooltipTrigger asChild>{control}</TooltipTrigger>
37
- <TooltipContent>{tooltip}</TooltipContent>
38
- </Tooltip>
39
- </TooltipProvider>
40
- )
41
- },
42
- )
43
-
44
- IconButton.displayName = 'IconButton'