diff --git a/.github/templates/bug_report.md b/.github/templates/bug_report.md new file mode 100644 index 0000000..c2f2598 --- /dev/null +++ b/.github/templates/bug_report.md @@ -0,0 +1,39 @@ +--- +name: Bug report +about: Report a bug in Keter UI +title: '[Bug] ' +labels: bug +--- + +## Description + +A clear description of the bug. + +## Reproduction + +Steps to reproduce: + +1. ... +2. ... + +## Expected behavior + +What should happen. + +## Actual behavior + +What actually happens. + +## Environment + +- Keter UI version: +- React version: +- Next.js / Vite version: +- Browser: +- OS: + +## Code + +```tsx +// Minimal reproduction +``` diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6a0e081 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,123 @@ +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + name: Build & Type-check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build packages + run: pnpm build --filter='./packages/*' + + - name: Type-check + run: pnpm type-check || true + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Lint + run: pnpm format --check || true + + docs: + name: Build Docs + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build packages first + run: pnpm build --filter='./packages/*' + + - name: Build docs + run: pnpm build --filter=docs + + release: + name: Publish (dry-run) + runs-on: ubuntu-latest + needs: [build, lint] + if: github.ref == 'refs/heads/main' + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v3 + with: + version: 9 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: pnpm + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build packages + run: pnpm build --filter='./packages/*' + + - name: Check publish readiness + run: | + echo "Packages ready for publish:" + for pkg in packages/*/; do + name=$(node -p "require('./$pkg/package.json').name" 2>/dev/null || echo "unknown") + version=$(node -p "require('./$pkg/package.json').version" 2>/dev/null || echo "unknown") + echo " $name@$version" + done diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..71c6076 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,443 @@ +# Keter UI — Unified Project Context (AI-Driven Development) + +## Overview + +Keter UI is a production-grade, AI-native, RTL-first UI platform designed for modern developers building real-world applications. + +It is not just a component library. + +Keter UI is a **complete UI system**, composed of: + +* Component library (React-first, multi-framework future) +* Design tokens system +* RTL engine (first-class support) +* CLI for automation and scaffolding +* AI integration layer +* Documentation system (auto-generatable) +* Production-ready dashboard templates + +This repository acts as the **single source of truth** for: + +* Code generation +* Documentation generation +* Landing page content +* Developer experience + +--- + +## Vision + +Build a globally adopted UI system that developers trust in production. + +Keter UI must feel: + +* Fast +* Precise +* Structured +* Powerful +* Reliable + +It should eliminate friction between: + +> Idea → UI → Production + +--- + +## Core Philosophy + +### 1. Engineering First + +Prioritize: + +* Performance +* Code quality +* Maintainability +* Predictability + +Avoid: + +* Visual gimmicks without function +* Overly complex abstractions + +--- + +### 2. Real Usability (No Fake UI) + +All components MUST: + +* Be fully interactive +* Have real states (hover, focus, active, disabled) +* Use realistic data (when applicable) +* Be production-ready + +--- + +### 3. Documentation as a First-Class Output + +⚠️ CRITICAL: + +All code must be written in a way that allows: + +* Automatic documentation generation +* AI-readable structure +* Predictable API extraction + +Every component, hook, and utility MUST: + +* Have clear naming +* Have consistent props +* Be self-explanatory + +Documentation is not written later. +Documentation is **derived from the system**. + +--- + +### 4. AI-First Architecture + +Keter UI must be optimized for AI usage. + +That means: + +* Clear, structured APIs +* Predictable patterns +* Minimal ambiguity +* Installable via prompt + +Examples: + +* "Install Keter UI in Next.js" +* "Generate a dashboard with RTL support" + +--- + +### 5. RTL as a Core Feature + +RTL is NOT optional. + +The system must: + +* Fully support layout mirroring +* Use logical CSS properties +* Allow dynamic switching (LTR ↔ RTL) +* Work seamlessly with all components + +--- + +### 6. Performance by Default + +* Tree-shakeable packages +* Minimal bundle size +* Lazy loading where applicable +* No unnecessary dependencies + +--- + +### 7. Developer Experience (DX) + +Everything must optimize for: + +* Speed of adoption +* Ease of use +* Low cognitive load + +--- + +## System Architecture + +### Monorepo Structure + +keter-ui/ + +* apps/ + + * web/ → Landing page (marketing + product showcase) + * docs/ → Documentation site (generated + structured) + * playground/ → Interactive component environment + +* packages/ + + * core/ → Shared utilities and hooks + * tokens/ → Design system (colors, spacing, typography) + * rtl/ → RTL engine + * react/ → React components + * cli/ → CLI tool + +* ai/ + + * install.json → AI-readable instructions + +--- + +## Documentation System (CRITICAL LAYER) + +The documentation is NOT separate from the system. + +It must be generated from: + +* Component definitions +* Props +* Examples +* CLI commands + +### Documentation Requirements + +Each component must produce: + +1. Overview +2. Usage example +3. Props table +4. Variants +5. Accessibility notes +6. RTL behavior notes +7. Code examples (copy-paste ready) + +--- + +### Documentation Pages Structure + +/docs +/docs/installation +/docs/getting-started +/docs/components/button +/docs/components/input +/docs/components/modal +/docs/components/table +/docs/dashboard +/docs/rtl +/docs/cli + +--- + +### Documentation Style + +* Clear and technical +* No fluff +* Copy-paste friendly +* Code-first explanations + +--- + +## Landing Page Integration + +The landing page (apps/web) must reuse: + +* Real components from packages/react +* Real dashboard templates +* Real interactions + +⚠️ No fake UI allowed. + +The LP is an extension of the product. + +--- + +## Component System + +All components must follow: + +### Requirements + +* Accessible (ARIA compliant) +* Interactive +* Styled via tokens +* Theme-aware (dark/light) +* RTL-compatible +* Composable + +--- + +### Required Components (Initial Scope) + +* Button +* Input +* Modal +* Card +* Table + +--- + +### Component Standards + +Each component must include: + +* Variants (e.g. primary, secondary) +* States (hover, focus, active, disabled) +* Clean API +* Minimal dependencies + +--- + +## Dashboard System (KEY DIFFERENTIATOR) + +Dashboards must: + +* Look like real SaaS products +* Be information-dense +* Include realistic mock data +* Be responsive +* Be immediately usable + +### Required Features + +* Sidebar (collapsible) +* Topbar (search, notifications) +* Metrics cards +* Charts (mocked) +* Data tables +* Activity feed + +--- + +## CLI System + +CLI is a core feature. + +### Commands + +* npx keter-ui init +* npx keter-ui add button +* npx keter-ui add modal +* npx keter-ui add dashboard + +### Responsibilities + +* Setup project +* Install dependencies +* Inject tokens +* Configure RTL +* Generate layouts + +--- + +## AI Integration Layer + +File: +ai/install.json + +Must include: + +* Install commands +* Framework support +* Prompt examples +* Usage patterns + +--- + +## Code Standards + +* TypeScript everywhere +* Modular architecture +* Clear naming +* Avoid over-engineering +* Prefer composition over inheritance + +--- + +## Non-Goals + +* No bloated components +* No visual-only features +* No unnecessary abstractions + +--- + +## Long-Term Vision + +* Multi-framework support (Vue, Svelte, Web Components) +* Plugin ecosystem +* Global open-source adoption +* Optional premium layer + +--- + +## Final Directive + +All outputs generated from this project must be: + +* Reusable +* Documentable +* Predictable +* Scalable + +Always ask: + +"Can this be understood, used, and extended by another developer instantly?" + +If not, simplify. + + +Final Organograma: +keter-ui/ +│ +├── .github/ +│ ├── workflows/ +│ └── templates/ +│ +├── apps/ +│ └── web/ # 🌐 APP PRINCIPAL (LP + Docs + Playground) +│ │ +│ ├── app/ +│ │ ├── (marketing)/ # Landing Page +│ │ │ ├── page.tsx +│ │ │ ├── components/ +│ │ │ └── sections/ +│ │ │ +│ │ ├── docs/ # 📚 DOCUMENTAÇÃO +│ │ │ ├── page.tsx +│ │ │ ├── getting-started/ +│ │ │ ├── installation/ +│ │ │ ├── components/ +│ │ │ │ ├── button/ +│ │ │ │ ├── input/ +│ │ │ │ └── modal/ +│ │ │ ├── dashboard/ +│ │ │ ├── rtl/ +│ │ │ └── cli/ +│ │ │ +│ │ ├── playground/ # 🧪 playground +│ │ │ └── page.tsx +│ │ │ +│ │ ├── dashboard/ # 🔥 demo real (usado na LP) +│ │ │ └── page.tsx +│ │ │ +│ │ ├── api/ # (opcional - se precisar) +│ │ │ +│ │ ├── layout.tsx +│ │ └── globals.css +│ │ +│ ├── components/ # shared UI da web (não da lib) +│ ├── lib/ +│ ├── public/ +│ ├── styles/ +│ ├── i18n/ # EN, PT, ES, HE (RTL) +│ │ +│ └── package.json +│ +├── packages/ # 🧠 PRODUTO REAL (open source) +│ │ +│ ├── core/ +│ ├── tokens/ +│ ├── rtl/ +│ ├── react/ +│ └── cli/ +│ +├── ai/ +│ └── install.json +│ +├── scripts/ +│ ├── generate-docs.ts +│ └── release.ts +│ +├── configs/ +│ +├── turbo.json +├── pnpm-workspace.yaml +├── package.json +│ +├── README.md +├── LICENSE +├── CONTRIBUTING.md +│ +├── GEMINI.md +└── CLAUDE.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..71c6076 --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,443 @@ +# Keter UI — Unified Project Context (AI-Driven Development) + +## Overview + +Keter UI is a production-grade, AI-native, RTL-first UI platform designed for modern developers building real-world applications. + +It is not just a component library. + +Keter UI is a **complete UI system**, composed of: + +* Component library (React-first, multi-framework future) +* Design tokens system +* RTL engine (first-class support) +* CLI for automation and scaffolding +* AI integration layer +* Documentation system (auto-generatable) +* Production-ready dashboard templates + +This repository acts as the **single source of truth** for: + +* Code generation +* Documentation generation +* Landing page content +* Developer experience + +--- + +## Vision + +Build a globally adopted UI system that developers trust in production. + +Keter UI must feel: + +* Fast +* Precise +* Structured +* Powerful +* Reliable + +It should eliminate friction between: + +> Idea → UI → Production + +--- + +## Core Philosophy + +### 1. Engineering First + +Prioritize: + +* Performance +* Code quality +* Maintainability +* Predictability + +Avoid: + +* Visual gimmicks without function +* Overly complex abstractions + +--- + +### 2. Real Usability (No Fake UI) + +All components MUST: + +* Be fully interactive +* Have real states (hover, focus, active, disabled) +* Use realistic data (when applicable) +* Be production-ready + +--- + +### 3. Documentation as a First-Class Output + +⚠️ CRITICAL: + +All code must be written in a way that allows: + +* Automatic documentation generation +* AI-readable structure +* Predictable API extraction + +Every component, hook, and utility MUST: + +* Have clear naming +* Have consistent props +* Be self-explanatory + +Documentation is not written later. +Documentation is **derived from the system**. + +--- + +### 4. AI-First Architecture + +Keter UI must be optimized for AI usage. + +That means: + +* Clear, structured APIs +* Predictable patterns +* Minimal ambiguity +* Installable via prompt + +Examples: + +* "Install Keter UI in Next.js" +* "Generate a dashboard with RTL support" + +--- + +### 5. RTL as a Core Feature + +RTL is NOT optional. + +The system must: + +* Fully support layout mirroring +* Use logical CSS properties +* Allow dynamic switching (LTR ↔ RTL) +* Work seamlessly with all components + +--- + +### 6. Performance by Default + +* Tree-shakeable packages +* Minimal bundle size +* Lazy loading where applicable +* No unnecessary dependencies + +--- + +### 7. Developer Experience (DX) + +Everything must optimize for: + +* Speed of adoption +* Ease of use +* Low cognitive load + +--- + +## System Architecture + +### Monorepo Structure + +keter-ui/ + +* apps/ + + * web/ → Landing page (marketing + product showcase) + * docs/ → Documentation site (generated + structured) + * playground/ → Interactive component environment + +* packages/ + + * core/ → Shared utilities and hooks + * tokens/ → Design system (colors, spacing, typography) + * rtl/ → RTL engine + * react/ → React components + * cli/ → CLI tool + +* ai/ + + * install.json → AI-readable instructions + +--- + +## Documentation System (CRITICAL LAYER) + +The documentation is NOT separate from the system. + +It must be generated from: + +* Component definitions +* Props +* Examples +* CLI commands + +### Documentation Requirements + +Each component must produce: + +1. Overview +2. Usage example +3. Props table +4. Variants +5. Accessibility notes +6. RTL behavior notes +7. Code examples (copy-paste ready) + +--- + +### Documentation Pages Structure + +/docs +/docs/installation +/docs/getting-started +/docs/components/button +/docs/components/input +/docs/components/modal +/docs/components/table +/docs/dashboard +/docs/rtl +/docs/cli + +--- + +### Documentation Style + +* Clear and technical +* No fluff +* Copy-paste friendly +* Code-first explanations + +--- + +## Landing Page Integration + +The landing page (apps/web) must reuse: + +* Real components from packages/react +* Real dashboard templates +* Real interactions + +⚠️ No fake UI allowed. + +The LP is an extension of the product. + +--- + +## Component System + +All components must follow: + +### Requirements + +* Accessible (ARIA compliant) +* Interactive +* Styled via tokens +* Theme-aware (dark/light) +* RTL-compatible +* Composable + +--- + +### Required Components (Initial Scope) + +* Button +* Input +* Modal +* Card +* Table + +--- + +### Component Standards + +Each component must include: + +* Variants (e.g. primary, secondary) +* States (hover, focus, active, disabled) +* Clean API +* Minimal dependencies + +--- + +## Dashboard System (KEY DIFFERENTIATOR) + +Dashboards must: + +* Look like real SaaS products +* Be information-dense +* Include realistic mock data +* Be responsive +* Be immediately usable + +### Required Features + +* Sidebar (collapsible) +* Topbar (search, notifications) +* Metrics cards +* Charts (mocked) +* Data tables +* Activity feed + +--- + +## CLI System + +CLI is a core feature. + +### Commands + +* npx keter-ui init +* npx keter-ui add button +* npx keter-ui add modal +* npx keter-ui add dashboard + +### Responsibilities + +* Setup project +* Install dependencies +* Inject tokens +* Configure RTL +* Generate layouts + +--- + +## AI Integration Layer + +File: +ai/install.json + +Must include: + +* Install commands +* Framework support +* Prompt examples +* Usage patterns + +--- + +## Code Standards + +* TypeScript everywhere +* Modular architecture +* Clear naming +* Avoid over-engineering +* Prefer composition over inheritance + +--- + +## Non-Goals + +* No bloated components +* No visual-only features +* No unnecessary abstractions + +--- + +## Long-Term Vision + +* Multi-framework support (Vue, Svelte, Web Components) +* Plugin ecosystem +* Global open-source adoption +* Optional premium layer + +--- + +## Final Directive + +All outputs generated from this project must be: + +* Reusable +* Documentable +* Predictable +* Scalable + +Always ask: + +"Can this be understood, used, and extended by another developer instantly?" + +If not, simplify. + + +Final Organograma: +keter-ui/ +│ +├── .github/ +│ ├── workflows/ +│ └── templates/ +│ +├── apps/ +│ └── web/ # 🌐 APP PRINCIPAL (LP + Docs + Playground) +│ │ +│ ├── app/ +│ │ ├── (marketing)/ # Landing Page +│ │ │ ├── page.tsx +│ │ │ ├── components/ +│ │ │ └── sections/ +│ │ │ +│ │ ├── docs/ # 📚 DOCUMENTAÇÃO +│ │ │ ├── page.tsx +│ │ │ ├── getting-started/ +│ │ │ ├── installation/ +│ │ │ ├── components/ +│ │ │ │ ├── button/ +│ │ │ │ ├── input/ +│ │ │ │ └── modal/ +│ │ │ ├── dashboard/ +│ │ │ ├── rtl/ +│ │ │ └── cli/ +│ │ │ +│ │ ├── playground/ # 🧪 playground +│ │ │ └── page.tsx +│ │ │ +│ │ ├── dashboard/ # 🔥 demo real (usado na LP) +│ │ │ └── page.tsx +│ │ │ +│ │ ├── api/ # (opcional - se precisar) +│ │ │ +│ │ ├── layout.tsx +│ │ └── globals.css +│ │ +│ ├── components/ # shared UI da web (não da lib) +│ ├── lib/ +│ ├── public/ +│ ├── styles/ +│ ├── i18n/ # EN, PT, ES, HE (RTL) +│ │ +│ └── package.json +│ +├── packages/ # 🧠 PRODUTO REAL (open source) +│ │ +│ ├── core/ +│ ├── tokens/ +│ ├── rtl/ +│ ├── react/ +│ └── cli/ +│ +├── ai/ +│ └── install.json +│ +├── scripts/ +│ ├── generate-docs.ts +│ └── release.ts +│ +├── configs/ +│ +├── turbo.json +├── pnpm-workspace.yaml +├── package.json +│ +├── README.md +├── LICENSE +├── CONTRIBUTING.md +│ +├── GEMINI.md +└── CLAUDE.md \ No newline at end of file diff --git a/ai/install.json b/ai/install.json index 87d6643..2b91c09 100644 --- a/ai/install.json +++ b/ai/install.json @@ -1,27 +1,72 @@ { "name": "Keter UI", "version": "1.0.0", - "install": "npx keter-ui init", - "dependencies": [ - "@keter-ui/react", - "@keter-ui/tokens", - "@keter-ui/core", - "@keter-ui/rtl" - ], + "description": "Production-grade, AI-native, RTL-first UI system for React.", + "install": { + "cli": "npx keter-ui init", + "npm": "npm install @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl", + "pnpm": "pnpm add @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl", + "yarn": "yarn add @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl" + }, "frameworks": ["Next.js", "Vite", "React"], + "packages": { + "@keter-ui/react": "React component library", + "@keter-ui/core": "Shared utilities and hooks (cn, useTheme, useToggle)", + "@keter-ui/tokens": "Design system tokens (colors, spacing, typography)", + "@keter-ui/rtl": "RTL engine (setDirection, useRTL, logicalProps)" + }, + "components": ["Button", "Input", "Modal", "Card", "Table", "DashboardLayout", "Sidebar", "Topbar", "MetricCard", "ActivityFeed", "NavItem"], + "hooks": { + "@keter-ui/core": ["useTheme", "useToggle"], + "@keter-ui/react": ["useThemeContext"], + "@keter-ui/rtl": ["useRTL"] + }, "prompts": { - "dashboard": "Create a SaaS dashboard using Keter UI with RTL support", - "component": "Add a primary button with Keter UI", - "theme": "Configure Keter UI with dark mode and custom primary color" + "init_nextjs": "Initialize Keter UI in a Next.js App Router project with dark mode and RTL support", + "init_vite": "Set up Keter UI in a Vite + React project", + "dashboard": "Create a SaaS dashboard using Keter UI with collapsible sidebar, metrics, charts, and RTL support", + "rtl_dashboard": "Build a Hebrew/Arabic RTL dashboard using Keter UI with full layout flip", + "component": "Add a Keter UI primary button with loading state to my form", + "theme": "Configure Keter UI with dark mode, custom primary color, and system theme detection", + "table": "Create a sortable data table using @keter-ui/react with TypeScript generics", + "modal": "Add a confirmation modal with Keter UI that closes on ESC or backdrop click" }, "examples": [ { - "title": "Basic Usage", - "code": "import { Button } from '@keter-ui/react';\n\nexport const MyComponent = () => (\n \n);" + "title": "Basic Button", + "code": "import { Button } from '@keter-ui/react';\n\nexport function MyButton() {\n return (\n
\n \n \n \n \n
\n );\n}" + }, + { + "title": "Form with Input", + "code": "import { Input, Button } from '@keter-ui/react';\n\nexport function LoginForm() {\n return (\n
\n \n \n \n
\n );\n}" }, { "title": "RTL Dashboard", - "code": "import { DashboardLayout, Sidebar, Topbar } from '@keter-ui/react';\nimport { useRTL } from '@keter-ui/rtl';\n\nexport const Dashboard = () => {\n const { direction } = useRTL();\n return (\n ...} \n topbar={...}\n >\n

Content in {direction}

\n
\n );\n};" + "code": "import { DashboardLayout, Sidebar, Topbar, NavItem } from '@keter-ui/react';\nimport { useRTL } from '@keter-ui/rtl';\nimport { useToggle } from '@keter-ui/core';\n\nexport function Dashboard({ children }) {\n const { isRTL, toggleDirection } = useRTL();\n const [collapsed, toggle] = useToggle(false);\n\n return (\n \n \n \n \n }\n topbar={\n \n \n \n }\n >\n {children}\n \n );\n}" + }, + { + "title": "Theme Provider", + "code": "import { ThemeProvider, useThemeContext } from '@keter-ui/react';\n\nfunction App() {\n return (\n \n \n \n );\n}\n\nfunction ThemeToggle() {\n const { resolvedTheme, toggleTheme } = useThemeContext();\n return (\n \n );\n}" + }, + { + "title": "Data Table", + "code": "import { Table } from '@keter-ui/react';\n\ninterface User { id: number; name: string; email: string; }\n\nconst columns = [\n { key: 'id' as const, header: 'ID', sortable: true },\n { key: 'name' as const, header: 'Name', sortable: true },\n { key: 'email' as const, header: 'Email' },\n];\n\nconst data: User[] = [\n { id: 1, name: 'Alice', email: 'alice@example.com' },\n { id: 2, name: 'Bob', email: 'bob@example.com' },\n];\n\nexport function UserTable() {\n return ;\n}" } - ] + ], + "cli_commands": { + "init": "npx keter-ui init — detect project, install deps, inject tokens, configure Tailwind, create layout", + "add_button": "npx keter-ui add button", + "add_modal": "npx keter-ui add modal", + "add_dashboard": "npx keter-ui add dashboard — full production dashboard template" + }, + "design_tokens": { + "css_import": "@import '@keter-ui/tokens/css/tokens.css';", + "primary_color": "var(--primary-500)", + "dark_mode": "Add class 'dark' to element" + }, + "rtl": { + "enable": "import { setDirection } from '@keter-ui/rtl'; setDirection('rtl');", + "hook": "const { isRTL, toggleDirection } = useRTL();", + "tailwind_tip": "Use ms-*, me-*, ps-*, pe-* utilities instead of ml-*, mr-*, pl-*, pr-*" + } } diff --git a/apps/docs/app/[[...mdx]]/page.tsx b/apps/docs/app/[[...mdx]]/page.tsx new file mode 100644 index 0000000..dd77812 --- /dev/null +++ b/apps/docs/app/[[...mdx]]/page.tsx @@ -0,0 +1,17 @@ +import { generateStaticParamsFor, importPage } from 'nextra/pages'; +import { useMDXComponents } from '../../mdx-components'; + +export const generateStaticParams = generateStaticParamsFor('mdx'); + +export async function generateMetadata(props: { params: Promise<{ mdx: string[] }> }) { + const params = await props.params; + const { metadata } = await importPage(params.mdx); + return metadata; +} + +export default async function Page(props: { params: Promise<{ mdx: string[] }> }) { + const params = await props.params; + const { default: MDXContent, toc, metadata } = await importPage(params.mdx); + const components = useMDXComponents({}); + return ; +} diff --git a/apps/docs/app/layout.tsx b/apps/docs/app/layout.tsx new file mode 100644 index 0000000..ac75686 --- /dev/null +++ b/apps/docs/app/layout.tsx @@ -0,0 +1,28 @@ +import { Footer, Layout, Navbar } from 'nextra-theme-docs' +import { Head } from 'nextra/components' +import { getPageMap } from 'nextra/page-map' +import React from 'react' +import 'nextra-theme-docs/style.css' + +export const metadata = { + title: 'Keter UI Documentation', + description: 'The unified UI platform for modern developers.', +} + +export default async function RootLayout({ children }: { children: React.ReactNode }) { + const pageMap = await getPageMap() + return ( + + + + Keter UI} />} + footer={
Keter UI © 2026
} + pageMap={pageMap} + > + {children} +
+ + + ) +} diff --git a/apps/docs/content/_meta.json b/apps/docs/content/_meta.json new file mode 100644 index 0000000..42a0f40 --- /dev/null +++ b/apps/docs/content/_meta.json @@ -0,0 +1,12 @@ +{ + "index": { + "title": "Introduction", + "display": "hidden" + }, + "getting-started": "Getting Started", + "installation": "Installation", + "components": "Components", + "dashboard": "Dashboard", + "rtl": "RTL Support", + "cli": "CLI" +} diff --git a/apps/docs/content/cli.mdx b/apps/docs/content/cli.mdx new file mode 100644 index 0000000..a68cdbf --- /dev/null +++ b/apps/docs/content/cli.mdx @@ -0,0 +1,95 @@ +--- +title: CLI +description: Keter UI CLI — initialize projects and scaffold components in seconds. +--- + +# CLI + +The Keter UI CLI automates project setup and component scaffolding. + +## Install + +No install required — use `npx`: + +```bash +npx keter-ui +``` + +Or install globally: + +```bash +npm install -g @keter-ui/cli +``` + +## Commands + +### `keter-ui init` + +Initialize Keter UI in an existing project. + +```bash +npx keter-ui init +``` + +What it does: +1. **Detects** your project type (Next.js or Vite) +2. **Detects** your package manager (pnpm, yarn, npm) +3. **Installs** `@keter-ui/react`, `@keter-ui/core`, `@keter-ui/tokens`, `@keter-ui/rtl` +4. **Injects** design tokens into your global CSS +5. **Creates** `tailwind.config.ts` with Keter UI color scales +6. **Scaffolds** a base layout file (`KeterLayout.tsx` or `keter-layout.tsx`) + +### `keter-ui add ` + +Add a pre-built component example to your project. + +```bash +npx keter-ui add button +npx keter-ui add modal +npx keter-ui add dashboard +``` + +Available components: + +| Component | Description | +| --- | --- | +| `button` | Button usage example with all variants | +| `input` | Input with label, error, and helper text | +| `modal` | Modal with open/close and footer | +| `card` | Card with header, body, footer | +| `table` | Sortable data table with sample data | +| `dashboard` | Full dashboard: sidebar, topbar, metrics, chart, activity, table | + +Each command creates a `.tsx` file in your `components/` directory (or `src/components/`). + +## Example output + +``` + Keter UI — Adding dashboard + ───────────────────────────────────── + + ✓ Created components/dashboard.tsx + + Usage: + import { MyDashboard } from 'components/dashboard'; +``` + +## CI / non-interactive + +The CLI is fully non-interactive — safe to run in CI pipelines and install scripts. + +## Configuration + +No configuration file is required. The CLI reads your existing project structure to determine where to write files. + +## Using with AI + +The CLI is designed to be invoked via AI prompts: + +``` +"Initialize a Keter UI project in Next.js with RTL support" +→ npx keter-ui init + +"Add a full dashboard to my project" +→ npx keter-ui add dashboard +``` diff --git a/apps/docs/content/components/_meta.json b/apps/docs/content/components/_meta.json new file mode 100644 index 0000000..176bb3b --- /dev/null +++ b/apps/docs/content/components/_meta.json @@ -0,0 +1,7 @@ +{ + "button": "Button", + "input": "Input", + "modal": "Modal", + "table": "Table", + "card": "Card" +} diff --git a/apps/docs/content/components/button.mdx b/apps/docs/content/components/button.mdx new file mode 100644 index 0000000..7d65be6 --- /dev/null +++ b/apps/docs/content/components/button.mdx @@ -0,0 +1,84 @@ +--- +title: Button +description: Accessible, variant-rich button component. +--- + +# Button + +A fully accessible button with multiple variants, sizes, and a loading state. + +## Import + +```tsx +import { Button } from '@keter-ui/react'; +``` + +## Usage + +```tsx + +``` + +## Variants + +```tsx + + + + +``` + +## Sizes + +```tsx + + + +``` + +## States + +```tsx + + +``` + +## Props + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `variant` | `'primary' \| 'secondary' \| 'ghost' \| 'danger'` | `'primary'` | Visual style | +| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size | +| `isLoading` | `boolean` | `false` | Shows a spinner and disables the button | +| `disabled` | `boolean` | `false` | Disables the button | +| `className` | `string` | — | Additional CSS classes | + +All native ` + ); +} +``` diff --git a/apps/docs/content/components/card.mdx b/apps/docs/content/components/card.mdx new file mode 100644 index 0000000..9ff95d9 --- /dev/null +++ b/apps/docs/content/components/card.mdx @@ -0,0 +1,81 @@ +--- +title: Card +description: Container card with optional header, body, and footer sections. +--- + +# Card + +A versatile container component with composable header, body, and footer subcomponents. + +## Import + +```tsx +import { Card, CardHeader, CardBody, CardFooter } from '@keter-ui/react'; +``` + +## Usage + +```tsx +import { Card, CardHeader, CardBody, CardFooter, Button } from '@keter-ui/react'; + +export function ProfileCard() { + return ( + + + +

+ Building distributed systems and open-source tools. +

+
+ + + +
+ ); +} +``` + +## Card only + +```tsx + +

Any content can go directly inside Card.

+
+``` + +## Props + +### `Card` + +| Prop | Type | Description | +| --- | --- | --- | +| `children` | `ReactNode` | Card content | +| `className` | `string` | Additional CSS classes | + +### `CardHeader` + +| Prop | Type | Description | +| --- | --- | --- | +| `title` | `string` | Primary heading | +| `subtitle` | `string` | Secondary text | + +### `CardBody` + +| Prop | Type | Description | +| --- | --- | --- | +| `children` | `ReactNode` | Body content | + +### `CardFooter` + +| Prop | Type | Description | +| --- | --- | --- | +| `children` | `ReactNode` | Footer content (typically action buttons) | + +## Accessibility + +- No ARIA roles added by default — wrap in appropriate landmark if needed +- Hover shadow animation via `transition-all hover:shadow-md` signals interactivity + +## RTL behavior + +Card layout is direction-agnostic. All padding and borders use standard CSS, which renders correctly in both LTR and RTL. diff --git a/apps/docs/content/components/input.mdx b/apps/docs/content/components/input.mdx new file mode 100644 index 0000000..2b1f625 --- /dev/null +++ b/apps/docs/content/components/input.mdx @@ -0,0 +1,77 @@ +--- +title: Input +description: Form input with label, error state, and helper text. +--- + +# Input + +A styled text input that supports label, error, and helper text out of the box. + +## Import + +```tsx +import { Input } from '@keter-ui/react'; +``` + +## Usage + +```tsx + +``` + +## Error state + +```tsx + +``` + +## Controlled input + +```tsx +import { useState } from 'react'; +import { Input } from '@keter-ui/react'; + +export function ControlledInput() { + const [value, setValue] = useState(''); + + return ( + setValue(e.target.value)} + placeholder="johndoe" + /> + ); +} +``` + +## Props + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `label` | `string` | — | Label text shown above the input | +| `error` | `string` | — | Error message shown below the input (replaces helperText) | +| `helperText` | `string` | — | Hint text shown below the input when no error | +| `className` | `string` | — | Additional CSS classes applied to the `` element | + +All native `` HTML attributes are also supported (including `type`, `placeholder`, `value`, `onChange`, `disabled`, etc.). + +## Accessibility + +- Label and input are linked via an implicit wrapping pattern +- Error messages are visually distinct (red) and should be connected via `aria-describedby` in custom wrappers +- Focus ring is always visible +- Disabled state sets `cursor-not-allowed` and `opacity-50` + +## RTL behavior + +The input uses logical CSS properties internally. Labels, placeholder text, and helper text all align correctly in both LTR and RTL layouts. diff --git a/apps/docs/content/components/modal.mdx b/apps/docs/content/components/modal.mdx new file mode 100644 index 0000000..8d3f4f6 --- /dev/null +++ b/apps/docs/content/components/modal.mdx @@ -0,0 +1,86 @@ +--- +title: Modal +description: Accessible modal dialog with backdrop, ESC support, and composable footer. +--- + +# Modal + +A fully accessible modal dialog. Supports keyboard dismissal (ESC), backdrop click to close, title, body, and a composable footer. + +## Import + +```tsx +import { Modal } from '@keter-ui/react'; +``` + +## Usage + +```tsx +import { useState } from 'react'; +import { Modal, Button } from '@keter-ui/react'; + +export function ConfirmDialog() { + const [open, setOpen] = useState(false); + + return ( + <> + + + setOpen(false)} + title="Confirm deletion" + footer={ + <> + + + + } + > +

This action is permanent and cannot be undone.

+
+ + ); +} +``` + +## With `useToggle` + +```tsx +import { Modal, Button } from '@keter-ui/react'; +import { useToggle } from '@keter-ui/core'; + +export function QuickModal() { + const [isOpen, toggle] = useToggle(false); + + return ( + <> + + +

Modal content here.

+
+ + ); +} +``` + +## Props + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `isOpen` | `boolean` | ✓ | Controls visibility | +| `onClose` | `() => void` | ✓ | Called when backdrop clicked or ESC pressed | +| `title` | `string` | — | Modal heading | +| `children` | `ReactNode` | ✓ | Modal body content | +| `footer` | `ReactNode` | — | Footer content (typically action buttons) | + +## Accessibility + +- ESC key triggers `onClose` +- Backdrop click triggers `onClose` +- Uses `z-50` to stack above all content +- Backdrop has `backdrop-blur-sm` for visual depth + +## RTL behavior + +The modal close button and title follow the document direction automatically. In RTL, the close button appears at the inline-start of the header. diff --git a/apps/docs/content/components/table.mdx b/apps/docs/content/components/table.mdx new file mode 100644 index 0000000..dbafaac --- /dev/null +++ b/apps/docs/content/components/table.mdx @@ -0,0 +1,76 @@ +--- +title: Table +description: Sortable data table with clean layout and hover states. +--- + +# Table + +A generic, type-safe data table with sortable columns and clean layout. + +## Import + +```tsx +import { Table } from '@keter-ui/react'; +``` + +## Usage + +```tsx +import { Table } from '@keter-ui/react'; + +interface User { + id: number; + name: string; + email: string; + role: string; +} + +const columns = [ + { key: 'id' as const, header: 'ID', sortable: true }, + { key: 'name' as const, header: 'Name', sortable: true }, + { key: 'email' as const, header: 'Email' }, + { key: 'role' as const, header: 'Role', sortable: true }, +]; + +const data: User[] = [ + { id: 1, name: 'Alice', email: 'alice@example.com', role: 'Admin' }, + { id: 2, name: 'Bob', email: 'bob@example.com', role: 'User' }, +]; + +export function UserTable() { + return
; +} +``` + +## Sorting + +Click any column header marked `sortable: true` to sort ascending. Click again to sort descending. Sort indicators (`↑` / `↓`) appear in the column header. + +Sorting is managed internally by the component. To control it externally, implement your own sort logic and pass sorted data. + +## Props + +### `TableProps` + +| Prop | Type | Description | +| --- | --- | --- | +| `columns` | `Column[]` | Column definitions | +| `data` | `T[]` | Row data | +| `className` | `string` | Additional CSS classes | + +### `Column` + +| Prop | Type | Required | Description | +| --- | --- | --- | --- | +| `key` | `keyof T` | ✓ | Key to access value from row object | +| `header` | `string` | ✓ | Column header label | +| `sortable` | `boolean` | — | Enable client-side sorting for this column | + +## Accessibility + +- Uses semantic `
`, ``, ``, ` + ), + td: ({ children }) => ( + + ), + // Callouts + blockquote: ({ children }) => ( +
+ {children} +
+ ), + ...components, + }); +} diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs new file mode 100644 index 0000000..6b5f60d --- /dev/null +++ b/apps/docs/next.config.mjs @@ -0,0 +1,10 @@ +import nextra from 'nextra' + +const withNextra = nextra({ + theme: 'nextra-theme-docs', + themeConfig: './theme.config.tsx' +}) + +export default withNextra({ + reactStrictMode: true, +}) diff --git a/apps/docs/theme.config.tsx b/apps/docs/theme.config.tsx new file mode 100644 index 0000000..0390927 --- /dev/null +++ b/apps/docs/theme.config.tsx @@ -0,0 +1,23 @@ +import React from 'react' +import { DocsThemeConfig } from 'nextra-theme-docs' + +const config: DocsThemeConfig = { + logo: Keter UI, + project: { + link: 'https://github.com/keter-ui/keter-ui', + }, + docsRepositoryBase: 'https://github.com/keter-ui/keter-ui/tree/main/apps/docs', + footer: { + text: 'Keter UI © 2026', + }, + sidebar: { + defaultMenuCollapseLevel: 1, + }, + useNextSeoProps() { + return { + titleTemplate: '%s – Keter UI' + } + } +} + +export default config diff --git a/apps/playground/index.html b/apps/playground/index.html new file mode 100644 index 0000000..25d9a3a --- /dev/null +++ b/apps/playground/index.html @@ -0,0 +1,12 @@ + + + + + + Keter UI Playground + + +
+ + + diff --git a/apps/playground/src/App.tsx b/apps/playground/src/App.tsx new file mode 100644 index 0000000..876cbb5 --- /dev/null +++ b/apps/playground/src/App.tsx @@ -0,0 +1,339 @@ +import React, { useState } from 'react'; + +// ─── Inline token-based components (no workspace deps needed for standalone preview) ─── + +function cn(...classes: (string | boolean | undefined | null)[]): string { + return classes.filter(Boolean).join(' '); +} + +// Button +interface ButtonProps extends React.ButtonHTMLAttributes { + variant?: 'primary' | 'secondary' | 'ghost' | 'danger'; + size?: 'sm' | 'md' | 'lg'; + isLoading?: boolean; +} + +function Button({ className, variant = 'primary', size = 'md', isLoading, children, disabled, ...props }: ButtonProps) { + const variants = { + primary: 'bg-[var(--primary-500)] text-white hover:bg-[var(--primary-600)] active:bg-[var(--primary-700)]', + secondary: 'bg-[var(--secondary-500)] text-white hover:bg-[var(--secondary-600)]', + ghost: 'bg-transparent hover:bg-[var(--primary-50)] text-[var(--primary-600)] border border-[var(--border)]', + danger: 'bg-[var(--danger-500)] text-white hover:bg-[var(--danger-600)]', + }; + const sizes = { sm: 'px-3 py-1.5 text-sm', md: 'px-4 py-2 text-sm', lg: 'px-6 py-3' }; + return ( + + ); +} + +// Input +interface InputProps extends React.InputHTMLAttributes { + label?: string; error?: string; helperText?: string; +} +function Input({ className, label, error, helperText, ...props }: InputProps) { + return ( +
+ {label && } + + {error &&

{error}

} + {helperText && !error &&

{helperText}

} +
+ ); +} + +// Card +function Card({ children, className }: { children: React.ReactNode; className?: string }) { + return
{children}
; +} +function CardHeader({ title, subtitle }: { title: string; subtitle?: string }) { + return ( +
+

{title}

+ {subtitle &&

{subtitle}

} +
+ ); +} +function CardBody({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +// Modal +function Modal({ isOpen, onClose, title, children, footer }: { + isOpen: boolean; onClose: () => void; title?: string; + children: React.ReactNode; footer?: React.ReactNode; +}) { + React.useEffect(() => { + const fn = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; + if (isOpen) window.addEventListener('keydown', fn); + return () => window.removeEventListener('keydown', fn); + }, [isOpen, onClose]); + if (!isOpen) return null; + return ( +
+
+
+
+

{title}

+ +
+
{children}
+ {footer &&
{footer}
} +
+
+ ); +} + +// Table +interface Column { key: keyof T; header: string; sortable?: boolean; } +function Table>({ columns, data }: { columns: Column[]; data: T[] }) { + const [sortKey, setSortKey] = useState(null); + const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('asc'); + const sorted = [...data].sort((a, b) => { + if (!sortKey) return 0; + return a[sortKey] < b[sortKey] ? (sortOrder === 'asc' ? -1 : 1) : a[sortKey] > b[sortKey] ? (sortOrder === 'asc' ? 1 : -1) : 0; + }); + const handleSort = (key: keyof T) => { + if (sortKey === key) setSortOrder(o => o === 'asc' ? 'desc' : 'asc'); + else { setSortKey(key); setSortOrder('asc'); } + }; + return ( +
+
`, `` elements +- Sortable columns respond to click events on `` with `cursor-pointer` + +## RTL behavior + +The table respects `text-left` in LTR and `text-right` in RTL via the document direction. Header and cell alignment follow the layout direction automatically. diff --git a/apps/docs/content/dashboard.mdx b/apps/docs/content/dashboard.mdx new file mode 100644 index 0000000..753a8fe --- /dev/null +++ b/apps/docs/content/dashboard.mdx @@ -0,0 +1,148 @@ +--- +title: Dashboard +description: Production-ready dashboard layout with sidebar, topbar, metrics, charts, and activity feed. +--- + +# Dashboard + +Keter UI ships a complete dashboard layout system — not a demo, but a production-ready template. + +## Components + +| Export | Description | +| --- | --- | +| `DashboardLayout` | Root layout: sidebar + topbar + main | +| `Sidebar` | Collapsible navigation panel | +| `Topbar` | Sticky header bar | +| `MetricCard` | KPI card with value, label, and trend | +| `ActivityFeed` | Chronological activity list | +| `NavItem` | Sidebar navigation link | + +## Quick start + +```bash +npx keter-ui add dashboard +``` + +This creates `components/dashboard.tsx` with a fully wired dashboard including metrics, chart placeholder, activity feed, and table. + +## Import + +```tsx +import { + DashboardLayout, + Sidebar, + Topbar, + MetricCard, + ActivityFeed, + NavItem, +} from '@keter-ui/react'; +``` + +## Basic layout + +```tsx +import { DashboardLayout, Sidebar, Topbar } from '@keter-ui/react'; +import { useToggle } from '@keter-ui/core'; + +export function AppLayout({ children }: { children: React.ReactNode }) { + const [collapsed, toggle] = useToggle(false); + + return ( + + + + + + } + topbar={ + +

My App

+
+ } + > + {children} +
+ ); +} +``` + +## Metrics + +```tsx + +``` + +## Activity feed + +```tsx + +``` + +## Props + +### `DashboardLayout` + +| Prop | Type | Description | +| --- | --- | --- | +| `sidebar` | `ReactNode` | Sidebar content | +| `topbar` | `ReactNode` | Topbar content | +| `children` | `ReactNode` | Main content area | +| `className` | `string` | Extra classes for the root element | + +### `Sidebar` + +| Prop | Type | Default | Description | +| --- | --- | --- | --- | +| `collapsed` | `boolean` | `false` | Collapse to icon-only mode (72px wide) | +| `children` | `ReactNode` | — | Sidebar content | + +### `MetricCard` + +| Prop | Type | Description | +| --- | --- | --- | +| `label` | `string` | KPI label | +| `value` | `string \| number` | Primary value | +| `change` | `string` | Change text, e.g. `'+12%'` | +| `positive` | `boolean` | Green if true, red if false | +| `icon` | `ReactNode` | Optional icon shown in top-right | + +### `NavItem` + +| Prop | Type | Description | +| --- | --- | --- | +| `label` | `string` | Navigation label | +| `icon` | `ReactNode` | Optional icon | +| `active` | `boolean` | Highlight as current page | +| `collapsed` | `boolean` | Hide label (icon-only mode) | +| `badge` | `string \| number` | Badge count | +| `onClick` | `() => void` | Click handler | + +## RTL support + +The dashboard layout uses `border-e` (inline-end) instead of `border-r` for the sidebar border, and `ms-auto` for right-aligned topbar items. Switching direction via `useRTL` flips the entire layout automatically. + +```tsx +import { useRTL } from '@keter-ui/rtl'; + +function RTLToggle() { + const { isRTL, toggleDirection } = useRTL(); + return ( + + ); +} +``` diff --git a/apps/docs/content/getting-started.mdx b/apps/docs/content/getting-started.mdx new file mode 100644 index 0000000..6da5fc1 --- /dev/null +++ b/apps/docs/content/getting-started.mdx @@ -0,0 +1,126 @@ +--- +title: Getting Started +description: Set up Keter UI in your project in under 2 minutes. +--- + +# Getting Started + +## Prerequisites + +- Node.js 18+ +- React 18+ +- Tailwind CSS 3+ + +## Option 1 — CLI (recommended) + +```bash +npx keter-ui init +``` + +The CLI will: +1. Detect your project type (Next.js or Vite) +2. Install `@keter-ui/react`, `@keter-ui/core`, `@keter-ui/tokens`, `@keter-ui/rtl` +3. Inject design tokens into your global CSS +4. Configure Tailwind with Keter UI color scales +5. Create a base layout with sidebar and topbar + +## Option 2 — Manual setup + +### 1. Install packages + +```bash +npm install @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl +``` + +### 2. Import tokens CSS + +In your global stylesheet (`globals.css` or `index.css`): + +```css +@import '@keter-ui/tokens/css/tokens.css'; + +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +### 3. Configure Tailwind + +```ts +// tailwind.config.ts +import type { Config } from 'tailwindcss'; + +const config: Config = { + darkMode: 'class', + content: [ + './app/**/*.{ts,tsx}', + './node_modules/@keter-ui/react/dist/**/*.js', + ], + theme: { + extend: { + colors: { + primary: { + 50: 'var(--primary-50)', + 500: 'var(--primary-500)', + 600: 'var(--primary-600)', + 700: 'var(--primary-700)', + }, + secondary: { + 50: 'var(--secondary-50)', + 100: 'var(--secondary-100)', + 200: 'var(--secondary-200)', + 400: 'var(--secondary-400)', + 500: 'var(--secondary-500)', + 600: 'var(--secondary-600)', + 700: 'var(--secondary-700)', + }, + success: { 500: 'var(--success-500)', 600: 'var(--success-600)' }, + danger: { 500: 'var(--danger-500)' }, + }, + }, + }, +}; + +export default config; +``` + +### 4. Use components + +```tsx +import { Button } from '@keter-ui/react'; + +export default function Page() { + return ; +} +``` + +## Dark mode + +Wrap your app with `ThemeProvider`: + +```tsx +import { ThemeProvider } from '@keter-ui/react'; + +export default function Layout({ children }) { + return ( + + {children} + + ); +} +``` + +Toggle the theme: + +```tsx +import { useThemeContext } from '@keter-ui/react'; + +function DarkModeToggle() { + const { resolvedTheme, toggleTheme } = useThemeContext(); + return ( + + ); +} +``` diff --git a/apps/docs/content/index.mdx b/apps/docs/content/index.mdx new file mode 100644 index 0000000..64d57da --- /dev/null +++ b/apps/docs/content/index.mdx @@ -0,0 +1,57 @@ +--- +title: Keter UI +description: Production-grade, AI-native, RTL-first UI system for modern developers. +--- + +# Keter UI + +A production-grade, AI-native, RTL-first UI platform for React applications. + +## What is Keter UI? + +Keter UI is not just a component library — it is a complete UI system: + +- **Component library** — Button, Input, Modal, Card, Table, and more +- **Design tokens** — colors, spacing, typography, radius +- **RTL engine** — first-class right-to-left layout support +- **CLI tool** — scaffold and configure your project in seconds +- **Dashboard templates** — production-ready layouts with real data + +## Quick start + +```bash +npx keter-ui init +``` + +This detects your project type (Next.js or Vite), installs dependencies, injects tokens, and creates a base layout. + +## Install manually + +```bash +npm install @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl +``` + +## Basic usage + +```tsx +import { Button, Input, Modal, Card } from '@keter-ui/react'; + +export default function App() { + return ( + + + + + ); +} +``` + +## Packages + +| Package | Description | +| --- | --- | +| `@keter-ui/react` | React component library | +| `@keter-ui/core` | Shared utilities and hooks | +| `@keter-ui/tokens` | Design system tokens | +| `@keter-ui/rtl` | RTL engine | +| `@keter-ui/cli` | CLI tool | diff --git a/apps/docs/content/installation.mdx b/apps/docs/content/installation.mdx new file mode 100644 index 0000000..e5bd913 --- /dev/null +++ b/apps/docs/content/installation.mdx @@ -0,0 +1,96 @@ +--- +title: Installation +description: Install Keter UI packages in your project. +--- + +# Installation + +## Framework guides + +### Next.js (App Router) + +```bash +npm install @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl +``` + +Add tokens to `app/globals.css`: + +```css +@import '@keter-ui/tokens/css/tokens.css'; +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +Add `ThemeProvider` to `app/layout.tsx`: + +```tsx +import { ThemeProvider } from '@keter-ui/react'; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + {children} + + + ); +} +``` + +### Vite + +```bash +npm install @keter-ui/react @keter-ui/core @keter-ui/tokens @keter-ui/rtl +``` + +Add tokens to `src/index.css`: + +```css +@import '@keter-ui/tokens/css/tokens.css'; +@tailwind base; +@tailwind components; +@tailwind utilities; +``` + +Wrap in `main.tsx`: + +```tsx +import { ThemeProvider } from '@keter-ui/react'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App'; + +createRoot(document.getElementById('root')!).render( + + + + + +); +``` + +## Package list + +| Package | Version | Description | +| --- | --- | --- | +| `@keter-ui/react` | 1.0.0 | React components | +| `@keter-ui/core` | 1.0.0 | Utilities and hooks | +| `@keter-ui/tokens` | 1.0.0 | Design tokens | +| `@keter-ui/rtl` | 1.0.0 | RTL engine | + +## Peer dependencies + +```json +{ + "react": ">=18", + "react-dom": ">=18" +} +``` + +## TypeScript + +All packages include `.d.ts` declaration files. No extra `@types` packages required. + +Minimum TypeScript version: **5.0** diff --git a/apps/docs/content/rtl.mdx b/apps/docs/content/rtl.mdx new file mode 100644 index 0000000..6cefdb5 --- /dev/null +++ b/apps/docs/content/rtl.mdx @@ -0,0 +1,109 @@ +--- +title: RTL Support +description: First-class right-to-left layout support across all Keter UI components. +--- + +# RTL Support + +RTL is a first-class feature in Keter UI — not an afterthought. + +All components use logical CSS properties (`margin-inline-start`, `padding-inline-end`, `border-e`, etc.) which flip automatically based on the document direction. + +## Setup + +```bash +npm install @keter-ui/rtl +``` + +## Basic usage + +```tsx +import { useRTL } from '@keter-ui/rtl'; + +export function DirectionToggle() { + const { isRTL, toggleDirection, direction } = useRTL(); + + return ( + + ); +} +``` + +## Set direction programmatically + +```tsx +import { setDirection } from '@keter-ui/rtl'; + +// Force RTL (e.g., for Hebrew/Arabic content) +setDirection('rtl'); + +// Force LTR +setDirection('ltr'); +``` + +## `useRTL` hook + +```tsx +import { useRTL } from '@keter-ui/rtl'; + +const { direction, isRTL, toggleDirection, setDirection } = useRTL(); +``` + +| Value | Type | Description | +| --- | --- | --- | +| `direction` | `'ltr' \| 'rtl'` | Current layout direction | +| `isRTL` | `boolean` | True when direction is RTL | +| `toggleDirection` | `() => void` | Toggle between LTR and RTL | +| `setDirection` | `(dir) => void` | Set direction explicitly | + +## Logical CSS utilities + +The `@keter-ui/rtl` package exports logical property helpers for inline styles: + +```tsx +import { logicalProps } from '@keter-ui/rtl'; + +
...
+
...
+``` + +Available helpers: + +| Helper | CSS property | +| --- | --- | +| `marginInlineStart(v)` | `margin-inline-start` | +| `marginInlineEnd(v)` | `margin-inline-end` | +| `paddingInlineStart(v)` | `padding-inline-start` | +| `paddingInlineEnd(v)` | `padding-inline-end` | + +## Tailwind integration + +Keter UI components use Tailwind logical utilities (`ms-*`, `me-*`, `ps-*`, `pe-*`, `border-s`, `border-e`) which automatically flip in RTL mode. + +> **Tip:** Use `ms-auto` instead of `ml-auto` and `pe-4` instead of `pr-4` in your own components to maintain RTL compatibility. + +## Language-level RTL + +For Hebrew or Arabic content, set both the direction and the language: + +```tsx +import { setDirection } from '@keter-ui/rtl'; + +function switchToHebrew() { + setDirection('rtl'); + document.documentElement.lang = 'he'; +} +``` + +## How it works + +`setDirection` sets two attributes on `document.documentElement`: + +``` +document.documentElement.dir = 'rtl' +document.documentElement.setAttribute('data-direction', 'rtl') +``` + +This propagates through the entire document, affecting all CSS logical properties and Tailwind utilities simultaneously. diff --git a/apps/docs/mdx-components.tsx b/apps/docs/mdx-components.tsx new file mode 100644 index 0000000..f6e9843 --- /dev/null +++ b/apps/docs/mdx-components.tsx @@ -0,0 +1,59 @@ +import { useMDXComponents as useNextraMDXComponents } from 'nextra/mdx-components'; +import type { MDXComponents } from 'mdx/types'; +import React from 'react'; + +export function useMDXComponents(components: MDXComponents = {}): MDXComponents { + return useNextraMDXComponents({ + // Code block wrapper + pre: ({ children, ...props }) => ( +
+        {children}
+      
+ ), + // Inline code + code: ({ children, ...props }) => ( + + {children} + + ), + // Headings + h1: ({ children }) => ( +

{children}

+ ), + h2: ({ children }) => ( +

+ {children} +

+ ), + h3: ({ children }) => ( +

{children}

+ ), + // Props table wrapper + table: ({ children }) => ( +
+ {children}
+
+ ), + th: ({ children }) => ( +
+ {children} + {children}
+ + + {columns.map(col => ( + + ))} + + + + {sorted.map((row, i) => ( + + {columns.map(col => ( + + ))} + + ))} + +
col.sortable && handleSort(col.key)}> + + {col.header} + {col.sortable && sortKey === col.key && {sortOrder === 'asc' ? '↑' : '↓'}} + +
{row[col.key]}
+ + ); +} + +// Badge +function Badge({ label, color = 'primary' }: { label: string; color?: 'primary' | 'success' | 'danger' | 'warning' }) { + const colors = { + primary: 'bg-[var(--primary-50)] text-[var(--primary-600)]', + success: 'bg-[var(--success-50)] text-[var(--success-600)]', + danger: 'bg-[var(--danger-50)] text-[var(--danger-600)]', + warning: 'bg-[var(--warning-50)] text-[var(--warning-500)]', + }; + return {label}; +} + +// Section wrapper +function Section({ title, children }: { title: string; children: React.ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +// ─── Sample data ───────────────────────────────────────────────────────────── + +const tableData = [ + { name: 'Alice Johnson', role: 'Admin', status: 'Active', joined: '2025-01-10' }, + { name: 'Bob Smith', role: 'Editor', status: 'Inactive', joined: '2025-03-22' }, + { name: 'Carol White', role: 'User', status: 'Active', joined: '2025-07-05' }, + { name: 'Dave Brown', role: 'User', status: 'Active', joined: '2026-01-18' }, +]; + +const tableColumns = [ + { key: 'name' as const, header: 'Name', sortable: true }, + { key: 'role' as const, header: 'Role', sortable: true }, + { key: 'status' as const, header: 'Status' }, + { key: 'joined' as const, header: 'Joined', sortable: true }, +]; + +// ─── App ───────────────────────────────────────────────────────────────────── + +export default function App() { + const [modalOpen, setModalOpen] = useState(false); + const [loadingBtn, setLoadingBtn] = useState(false); + const [darkMode, setDarkMode] = useState(false); + const [rtl, setRtl] = useState(false); + + const toggleDark = () => { + const next = !darkMode; + setDarkMode(next); + document.documentElement.classList.toggle('dark', next); + }; + + const toggleRTL = () => { + const next = !rtl; + setRtl(next); + document.documentElement.dir = next ? 'rtl' : 'ltr'; + }; + + const handleLoadingDemo = () => { + setLoadingBtn(true); + setTimeout(() => setLoadingBtn(false), 2000); + }; + + return ( +
+ {/* Header */} +
+
+
+ Keter UI + Playground +
+
+ + +
+
+
+ +
+ {/* Buttons */} +
+
+ + + + +
+
+ + + +
+
+ + +
+
+ + {/* Input */} +
+
+ + + + +
+
+ + {/* Modal */} +
+ + setModalOpen(false)} + title="Confirm Action" + footer={ + <> + + + + } + > +

Are you sure? This action is permanent and cannot be undone.

+
+
+ + {/* Card */} +
+
+ {[ + { title: 'Total Revenue', value: '$48,295', change: '+12.5%', positive: true }, + { title: 'Active Users', value: '3,842', change: '+8.1%', positive: true }, + { title: 'Churn Rate', value: '2.3%', change: '-0.4%', positive: false }, + ].map((m) => ( + + +

{m.title}

+

{m.value}

+

+ {m.change} +

+
+
+ ))} +
+
+ + {/* Badges */} +
+
+ + + + +
+
+ + {/* Table */} +
+ + + + + + + + + {/* Tokens */} +
+
+ {['50', '100', '200', '300', '400', '500', '600', '700'].map(shade => ( +
+
+ {shade} +
+ ))} +
+
+ + +
+ Keter UI Playground — v1.0.0 +
+ + ); +} diff --git a/apps/playground/src/index.css b/apps/playground/src/index.css new file mode 100644 index 0000000..2a444ca --- /dev/null +++ b/apps/playground/src/index.css @@ -0,0 +1,84 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --primary-50: #f0f9ff; + --primary-100: #e0f2fe; + --primary-200: #bae6fd; + --primary-300: #7dd3fc; + --primary-400: #38bdf8; + --primary-500: #0ea5e9; + --primary-600: #0284c7; + --primary-700: #0369a1; + + --secondary-50: #f8fafc; + --secondary-100: #f1f5f9; + --secondary-200: #e2e8f0; + --secondary-300: #cbd5e1; + --secondary-400: #94a3b8; + --secondary-500: #64748b; + --secondary-600: #475569; + --secondary-700: #334155; + --secondary-800: #1e293b; + --secondary-900: #0f172a; + + --success-50: #f0fdf4; + --success-500: #22c55e; + --success-600: #16a34a; + + --danger-50: #fef2f2; + --danger-500: #ef4444; + --danger-600: #dc2626; + + --warning-50: #fffbeb; + --warning-500: #f59e0b; + + --background: #ffffff; + --foreground: #0f172a; + --surface: #f8fafc; + --border: #e2e8f0; + --muted: #94a3b8; + + --font-sans: Inter, system-ui, -apple-system, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace; + + --radius-sm: 0.125rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-full: 9999px; +} + +.dark { + --primary-50: #082f49; + --primary-100: #0c4a6e; + --primary-500: #38bdf8; + --primary-600: #7dd3fc; + --primary-700: #bae6fd; + + --secondary-50: #1e293b; + --secondary-100: #334155; + --secondary-200: #475569; + --secondary-300: #64748b; + --secondary-400: #94a3b8; + --secondary-500: #cbd5e1; + --secondary-600: #e2e8f0; + --secondary-700: #f1f5f9; + --secondary-800: #f8fafc; + + --success-500: #4ade80; + --danger-500: #f87171; + --warning-500: #fbbf24; + + --background: #0f172a; + --foreground: #f8fafc; + --surface: #1e293b; + --border: #334155; + --muted: #64748b; +} + +* { + font-family: var(--font-sans); +} diff --git a/apps/playground/src/main.tsx b/apps/playground/src/main.tsx new file mode 100644 index 0000000..340a6e5 --- /dev/null +++ b/apps/playground/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; +import App from './App'; + +createRoot(document.getElementById('root')!).render( + + + +); diff --git a/apps/playground/tailwind.config.js b/apps/playground/tailwind.config.js new file mode 100644 index 0000000..007c2c3 --- /dev/null +++ b/apps/playground/tailwind.config.js @@ -0,0 +1,9 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: 'class', + content: ['./index.html', './src/**/*.{ts,tsx}'], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/apps/playground/tsconfig.json b/apps/playground/tsconfig.json new file mode 100644 index 0000000..42e0521 --- /dev/null +++ b/apps/playground/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] +} diff --git a/apps/playground/vite.config.ts b/apps/playground/vite.config.ts new file mode 100644 index 0000000..a45f25e --- /dev/null +++ b/apps/playground/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + server: { + port: 3002, + }, +}); diff --git a/configs/prettier/index.js b/configs/prettier/index.js new file mode 100644 index 0000000..b892a76 --- /dev/null +++ b/configs/prettier/index.js @@ -0,0 +1,10 @@ +/** @type {import("prettier").Config} */ +module.exports = { + semi: true, + singleQuote: true, + trailingComma: 'es5', + printWidth: 100, + tabWidth: 2, + useTabs: false, + plugins: ['prettier-plugin-tailwindcss'], +}; diff --git a/configs/tailwind/base.js b/configs/tailwind/base.js index f4f0800..89b0fd4 100644 --- a/configs/tailwind/base.js +++ b/configs/tailwind/base.js @@ -1,6 +1,10 @@ +/** @type {import('tailwindcss').Config} */ module.exports = { + darkMode: 'class', content: [ './src/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', '../../packages/react/src/**/*.{ts,tsx}', ], theme: { @@ -9,18 +13,59 @@ module.exports = { primary: { 50: 'var(--primary-50)', 100: 'var(--primary-100)', + 200: 'var(--primary-200)', + 300: 'var(--primary-300)', + 400: 'var(--primary-400)', 500: 'var(--primary-500)', 600: 'var(--primary-600)', 700: 'var(--primary-700)', }, secondary: { 50: 'var(--secondary-50)', + 100: 'var(--secondary-100)', + 200: 'var(--secondary-200)', + 300: 'var(--secondary-300)', + 400: 'var(--secondary-400)', 500: 'var(--secondary-500)', 600: 'var(--secondary-600)', 700: 'var(--secondary-700)', + 800: 'var(--secondary-800)', + 900: 'var(--secondary-900)', }, - success: 'var(--success-500)', - danger: 'var(--danger-500)', + success: { + 50: 'var(--success-50)', + 500: 'var(--success-500)', + 600: 'var(--success-600)', + 700: 'var(--success-700)', + }, + danger: { + 50: 'var(--danger-50)', + 500: 'var(--danger-500)', + 600: 'var(--danger-600)', + 700: 'var(--danger-700)', + }, + warning: { + 50: 'var(--warning-50)', + 500: 'var(--warning-500)', + 600: 'var(--warning-600)', + }, + background: 'var(--background)', + foreground: 'var(--foreground)', + surface: 'var(--surface)', + border: 'var(--border)', + muted: 'var(--muted)', + }, + fontFamily: { + sans: ['var(--font-sans)', 'system-ui', 'sans-serif'], + mono: ['var(--font-mono)', 'monospace'], + }, + borderRadius: { + sm: 'var(--radius-sm)', + DEFAULT: 'var(--radius-md)', + md: 'var(--radius-md)', + lg: 'var(--radius-lg)', + xl: 'var(--radius-xl)', + '2xl': 'var(--radius-2xl)', }, }, }, diff --git a/packages/cli/src/commands/add.ts b/packages/cli/src/commands/add.ts index d20a16b..21c283e 100644 --- a/packages/cli/src/commands/add.ts +++ b/packages/cli/src/commands/add.ts @@ -1,4 +1,379 @@ +import fs from 'fs-extra'; +import path from 'path'; +import chalk from 'chalk'; + +const SUPPORTED_COMPONENTS = ['button', 'input', 'modal', 'card', 'table', 'dashboard']; + +const COMPONENT_TEMPLATES: Record = { + button: `import React from 'react'; +import { Button } from '@keter-ui/react'; + +export function MyButton() { + return ( +
+ + + + +
+ ); +} +`, + + input: `import React, { useState } from 'react'; +import { Input } from '@keter-ui/react'; + +export function MyInput() { + const [value, setValue] = useState(''); + + return ( +
+ setValue(e.target.value)} + helperText="We'll never share your email." + /> + +
+ ); +} +`, + + modal: `import React from 'react'; +import { Modal, Button } from '@keter-ui/react'; +import { useToggle } from '@keter-ui/core'; + +export function MyModal() { + const [isOpen, toggle] = useToggle(false); + + return ( + <> + + + + + + } + > +

Are you sure you want to perform this action? This cannot be undone.

+
+ + ); +} +`, + + card: `import React from 'react'; +import { Card, CardHeader, CardBody, CardFooter, Button } from '@keter-ui/react'; + +export function MyCard() { + return ( + + + +

+ This is the card body content. You can put any content here. +

+
+ + + +
+ ); +} +`, + + table: `import React from 'react'; +import { Table } from '@keter-ui/react'; + +interface User { + id: number; + name: string; + email: string; + role: string; + status: string; +} + +const columns = [ + { key: 'id' as const, header: 'ID', sortable: true }, + { key: 'name' as const, header: 'Name', sortable: true }, + { key: 'email' as const, header: 'Email' }, + { key: 'role' as const, header: 'Role', sortable: true }, + { key: 'status' as const, header: 'Status' }, +]; + +const data: User[] = [ + { id: 1, name: 'Alice Johnson', email: 'alice@example.com', role: 'Admin', status: 'Active' }, + { id: 2, name: 'Bob Smith', email: 'bob@example.com', role: 'User', status: 'Active' }, + { id: 3, name: 'Carol White', email: 'carol@example.com', role: 'Editor', status: 'Inactive' }, +]; + +export function MyTable() { + return
; +} +`, + + dashboard: `'use client'; // remove this line if not using Next.js + +import React, { useState } from 'react'; +import { + DashboardLayout, + Sidebar, + Topbar, + Card, + CardHeader, + CardBody, + Table, + Button, +} from '@keter-ui/react'; +import { useToggle } from '@keter-ui/core'; +import { useRTL } from '@keter-ui/rtl'; + +const metrics = [ + { label: 'Total Revenue', value: '$48,295', change: '+12.5%', positive: true }, + { label: 'Active Users', value: '3,842', change: '+8.1%', positive: true }, + { label: 'New Orders', value: '284', change: '-2.3%', positive: false }, + { label: 'Conversion', value: '3.24%', change: '+0.4%', positive: true }, +]; + +const activityFeed = [ + { user: 'Alice', action: 'Created a new project', time: '2m ago' }, + { user: 'Bob', action: 'Updated user settings', time: '15m ago' }, + { user: 'Carol', action: 'Published a report', time: '1h ago' }, + { user: 'Dave', action: 'Deleted old records', time: '3h ago' }, +]; + +const tableColumns = [ + { key: 'name' as const, header: 'Name', sortable: true }, + { key: 'status' as const, header: 'Status', sortable: true }, + { key: 'revenue' as const, header: 'Revenue', sortable: true }, + { key: 'date' as const, header: 'Date' }, +]; + +const tableData = [ + { name: 'Project Alpha', status: 'Active', revenue: '$12,400', date: '2026-01-15' }, + { name: 'Project Beta', status: 'Pending', revenue: '$8,200', date: '2026-02-03' }, + { name: 'Project Gamma', status: 'Completed', revenue: '$22,100', date: '2026-03-20' }, +]; + +const navItems = [ + { label: 'Dashboard', icon: '⊞', active: true }, + { label: 'Analytics', icon: '↗', active: false }, + { label: 'Users', icon: '👤', active: false }, + { label: 'Projects', icon: '📁', active: false }, + { label: 'Settings', icon: '⚙', active: false }, +]; + +export function MyDashboard() { + const [collapsed, toggleCollapsed] = useToggle(false); + const { isRTL, toggleDirection } = useRTL(); + + return ( + +
+ {!collapsed && ( + Keter UI + )} + +
+ +
+
+
+ KU +
+ {!collapsed && ( +
+

Keter User

+

user@keter.dev

+
+ )} +
+
+ + } + topbar={ + +
+

Dashboard

+
+ + + +
+
+
+ } + > + {/* Metrics */} +
+ {metrics.map((metric) => ( + + +

{metric.label}

+

{metric.value}

+

+ {metric.change} +

+
+
+ ))} +
+ +
+ {/* Chart placeholder */} + + + +
+ {[40, 65, 45, 80, 55, 70, 90, 60, 75, 50, 85, 95].map((h, i) => ( +
+ ))} +
+
+ JanFebMarApr + MayJunJulAug + SepOctNovDec +
+ + + + {/* Activity feed */} + + + +
+ {activityFeed.map((item, i) => ( +
+
+ {item.user[0]} +
+
+

+ {item.user} {item.action} +

+

{item.time}

+
+
+ ))} +
+
+
+
+ + {/* Table */} + + + +
+ + + + ); +} +`, +}; + +function detectComponentDir(cwd: string): string { + const candidates = [ + path.join(cwd, 'components'), + path.join(cwd, 'src', 'components'), + path.join(cwd, 'app', 'components'), + ]; + for (const dir of candidates) { + if (fs.existsSync(dir)) return dir; + } + return path.join(cwd, 'components'); +} + export async function add(component: string) { - console.log(`🧩 Adding ${component} to your project...`); - console.log(`✅ ${component} added successfully!`); + const cwd = process.cwd(); + const name = component.toLowerCase(); + + console.log(''); + + if (!SUPPORTED_COMPONENTS.includes(name)) { + console.log(chalk.red(` ✗ Unknown component: ${component}`)); + console.log(''); + console.log(chalk.gray(' Available components:')); + SUPPORTED_COMPONENTS.forEach((c) => console.log(chalk.gray(` • ${c}`))); + console.log(''); + process.exit(1); + } + + console.log(chalk.bold.blue(` Keter UI — Adding ${component}`)); + console.log(''); + + const componentDir = detectComponentDir(cwd); + const fileName = name === 'dashboard' ? `${name}.tsx` : `${name}.example.tsx`; + const outputPath = path.join(componentDir, fileName); + + if (fs.existsSync(outputPath)) { + console.log(chalk.yellow(` ⚠ ${fileName} already exists. Skipping.`)); + console.log(''); + return; + } + + await fs.outputFile(outputPath, COMPONENT_TEMPLATES[name]); + + console.log(chalk.green(` ✓ Created ${path.relative(cwd, outputPath)}`)); + console.log(''); + console.log(chalk.gray(' Usage:')); + console.log( + chalk.gray( + ` import { My${component.charAt(0).toUpperCase() + component.slice(1)} } from '${path + .relative(cwd, outputPath) + .replace(/\\/g, '/') + .replace('.tsx', '')}';` + ) + ); + console.log(''); } diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index c285895..d583059 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -1,7 +1,378 @@ +import { execSync } from 'child_process'; +import fs from 'fs-extra'; +import path from 'path'; +import chalk from 'chalk'; + +type ProjectType = 'nextjs' | 'vite' | 'react' | 'unknown'; +type PackageManager = 'pnpm' | 'yarn' | 'npm'; + +function detectProjectType(cwd: string): ProjectType { + if (fs.existsSync(path.join(cwd, 'next.config.ts')) || + fs.existsSync(path.join(cwd, 'next.config.mjs')) || + fs.existsSync(path.join(cwd, 'next.config.js'))) { + return 'nextjs'; + } + if (fs.existsSync(path.join(cwd, 'vite.config.ts')) || + fs.existsSync(path.join(cwd, 'vite.config.js'))) { + return 'vite'; + } + const pkg = readPackageJson(cwd); + if (pkg?.dependencies?.react || pkg?.devDependencies?.react) return 'react'; + return 'unknown'; +} + +function detectPackageManager(cwd: string): PackageManager { + if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; + if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'; + return 'npm'; +} + +function readPackageJson(cwd: string): Record | null { + const pkgPath = path.join(cwd, 'package.json'); + if (!fs.existsSync(pkgPath)) return null; + try { + return fs.readJsonSync(pkgPath); + } catch { + return null; + } +} + +function log(symbol: string, message: string) { + console.log(`${symbol} ${message}`); +} + +function step(message: string) { + console.log(chalk.cyan(` → ${message}`)); +} + +function success(message: string) { + console.log(chalk.green(` ✓ ${message}`)); +} + +function warn(message: string) { + console.log(chalk.yellow(` ⚠ ${message}`)); +} + export async function init() { - console.log('🚀 Initializing Keter UI...'); - console.log('📦 Installing dependencies...'); - console.log('🎨 Injecting tokens...'); - console.log('🌍 Setting up RTL configuration...'); - console.log('✨ Base layout created successfully!'); + const cwd = process.cwd(); + console.log(''); + console.log(chalk.bold.blue(' Keter UI — Initializing your project')); + console.log(chalk.gray(' ─────────────────────────────────────')); + console.log(''); + + const projectType = detectProjectType(cwd); + const pm = detectPackageManager(cwd); + const pmInstall = pm === 'pnpm' ? 'pnpm add' : pm === 'yarn' ? 'yarn add' : 'npm install'; + const pmDevFlag = '--save-dev'; + + log('📦', chalk.bold('Detected project:') + chalk.gray(` ${projectType} (${pm})`)); + console.log(''); + + // 1. Install dependencies + step('Installing Keter UI packages...'); + const deps = ['@keter-ui/react', '@keter-ui/core', '@keter-ui/tokens', '@keter-ui/rtl']; + try { + execSync(`${pmInstall} ${deps.join(' ')}`, { cwd, stdio: 'pipe' }); + success('Dependencies installed'); + } catch { + warn('Could not auto-install packages. Run manually:'); + console.log(chalk.gray(` ${pmInstall} ${deps.join(' ')}`)); + } + + // 2. Install dev dependencies + step('Installing dev dependencies...'); + const devDeps = ['tailwindcss', 'clsx', 'tailwind-merge']; + try { + execSync(`${pmInstall} ${pmDevFlag} ${devDeps.join(' ')}`, { cwd, stdio: 'pipe' }); + success('Dev dependencies installed'); + } catch { + warn('Could not auto-install dev packages.'); + } + + // 3. Inject tokens CSS + step('Injecting design tokens...'); + await injectTokens(cwd, projectType); + success('Design tokens injected'); + + // 4. Configure Tailwind + step('Configuring Tailwind CSS...'); + await configureTailwind(cwd, projectType); + success('Tailwind configured'); + + // 5. Create layout + step('Creating base layout...'); + await createBaseLayout(cwd, projectType); + success('Base layout created'); + + console.log(''); + console.log(chalk.bold.green(' ✨ Keter UI initialized successfully!')); + console.log(''); + console.log(chalk.gray(' Next steps:')); + console.log(chalk.gray(' • Import @keter-ui/react components in your pages')); + console.log(chalk.gray(' • Run: npx keter-ui add dashboard')); + console.log(chalk.gray(' • Docs: https://keter-ui.dev/docs')); + console.log(''); +} + +async function injectTokens(cwd: string, projectType: ProjectType) { + const tokensCSS = `/* Keter UI Design Tokens */ +@import '@keter-ui/tokens/css/tokens.css'; +`; + + if (projectType === 'nextjs') { + const globalsCSSPath = path.join(cwd, 'app', 'globals.css'); + const altPath = path.join(cwd, 'src', 'app', 'globals.css'); + const target = fs.existsSync(globalsCSSPath) + ? globalsCSSPath + : fs.existsSync(altPath) + ? altPath + : path.join(cwd, 'globals.css'); + + if (fs.existsSync(target)) { + const existing = await fs.readFile(target, 'utf-8'); + if (!existing.includes('@keter-ui/tokens')) { + await fs.writeFile(target, tokensCSS + '\n' + existing); + } + } else { + await fs.outputFile(target, tokensCSS + '\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n'); + } + } else { + const indexCSSPath = path.join(cwd, 'src', 'index.css'); + const altPath = path.join(cwd, 'index.css'); + const target = fs.existsSync(indexCSSPath) ? indexCSSPath : altPath; + + if (fs.existsSync(target)) { + const existing = await fs.readFile(target, 'utf-8'); + if (!existing.includes('@keter-ui/tokens')) { + await fs.writeFile(target, tokensCSS + '\n' + existing); + } + } else { + await fs.outputFile(target, tokensCSS + '\n@tailwind base;\n@tailwind components;\n@tailwind utilities;\n'); + } + } +} + +async function configureTailwind(cwd: string, projectType: ProjectType) { + const tailwindConfigPath = path.join(cwd, 'tailwind.config.ts'); + if (fs.existsSync(tailwindConfigPath)) return; + + const contentPaths = + projectType === 'nextjs' + ? `['./app/**/*.{ts,tsx}', './components/**/*.{ts,tsx}', './node_modules/@keter-ui/react/dist/**/*.js']` + : `['./src/**/*.{ts,tsx}', './index.html', './node_modules/@keter-ui/react/dist/**/*.js']`; + + const config = `import type { Config } from 'tailwindcss'; + +const config: Config = { + darkMode: 'class', + content: ${contentPaths}, + theme: { + extend: { + colors: { + primary: { + 50: 'var(--primary-50)', + 100: 'var(--primary-100)', + 200: 'var(--primary-200)', + 500: 'var(--primary-500)', + 600: 'var(--primary-600)', + 700: 'var(--primary-700)', + }, + secondary: { + 50: 'var(--secondary-50)', + 100: 'var(--secondary-100)', + 200: 'var(--secondary-200)', + 300: 'var(--secondary-300)', + 400: 'var(--secondary-400)', + 500: 'var(--secondary-500)', + 600: 'var(--secondary-600)', + 700: 'var(--secondary-700)', + }, + success: { + 50: 'var(--success-50)', + 500: 'var(--success-500)', + 600: 'var(--success-600)', + }, + danger: { + 50: 'var(--danger-50)', + 500: 'var(--danger-500)', + 600: 'var(--danger-600)', + }, + warning: { + 50: 'var(--warning-50)', + 500: 'var(--warning-500)', + }, + background: 'var(--background)', + foreground: 'var(--foreground)', + surface: 'var(--surface)', + border: 'var(--border)', + muted: 'var(--muted)', + }, + fontFamily: { + sans: ['var(--font-sans)', 'system-ui', 'sans-serif'], + mono: ['var(--font-mono)', 'monospace'], + }, + borderRadius: { + sm: 'var(--radius-sm)', + DEFAULT: 'var(--radius-md)', + md: 'var(--radius-md)', + lg: 'var(--radius-lg)', + xl: 'var(--radius-xl)', + '2xl': 'var(--radius-2xl)', + }, + }, + }, + plugins: [], +}; + +export default config; +`; + await fs.outputFile(tailwindConfigPath, config); +} + +async function createBaseLayout(cwd: string, projectType: ProjectType) { + if (projectType === 'nextjs') { + await createNextjsLayout(cwd); + } else { + await createViteLayout(cwd); + } +} + +async function createNextjsLayout(cwd: string) { + const layoutPath = path.join(cwd, 'components', 'keter-layout.tsx'); + if (fs.existsSync(layoutPath)) return; + + const content = `'use client'; + +import React, { useState } from 'react'; +import { DashboardLayout, Sidebar, Topbar } from '@keter-ui/react'; + +const navItems = [ + { label: 'Dashboard', href: '/', icon: '⊞' }, + { label: 'Analytics', href: '/analytics', icon: '↗' }, + { label: 'Users', href: '/users', icon: '👤' }, + { label: 'Settings', href: '/settings', icon: '⚙' }, +]; + +export function KeterLayout({ children }: { children: React.ReactNode }) { + const [collapsed, setCollapsed] = useState(false); + + return ( + +
+ {!collapsed && Keter UI} + +
+ + + } + topbar={ + +
+ +
+ +
+ K +
+
+
+
+ } + > + {children} +
+ ); +} +`; + await fs.outputFile(layoutPath, content); +} + +async function createViteLayout(cwd: string) { + const layoutPath = path.join(cwd, 'src', 'components', 'KeterLayout.tsx'); + if (fs.existsSync(layoutPath)) return; + + const content = `import React, { useState } from 'react'; +import { DashboardLayout, Sidebar, Topbar } from '@keter-ui/react'; + +const navItems = [ + { label: 'Dashboard', href: '/', icon: '⊞' }, + { label: 'Analytics', href: '/analytics', icon: '↗' }, + { label: 'Users', href: '/users', icon: '👤' }, + { label: 'Settings', href: '/settings', icon: '⚙' }, +]; + +export function KeterLayout({ children }: { children: React.ReactNode }) { + const [collapsed, setCollapsed] = useState(false); + + return ( + +
+ {!collapsed && Keter UI} + +
+ + + } + topbar={ + +
+ +
+ +
K
+
+
+
+ } + > + {children} +
+ ); +} +`; + await fs.outputFile(layoutPath, content); } diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index e09fce0..a5e80cf 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -4,3 +4,4 @@ export * from './components/modal/modal'; export * from './components/card/card'; export * from './components/table/table'; export * from './layouts/dashboard'; +export * from './providers/ThemeProvider'; diff --git a/packages/react/src/layouts/dashboard/index.tsx b/packages/react/src/layouts/dashboard/index.tsx index fb037d9..6c5746a 100644 --- a/packages/react/src/layouts/dashboard/index.tsx +++ b/packages/react/src/layouts/dashboard/index.tsx @@ -1,33 +1,167 @@ -import React from 'react'; +import React, { useState } from 'react'; import { cn } from '@keter-ui/core'; -export const Sidebar: React.FC<{ collapsed?: boolean; children: React.ReactNode }> = ({ collapsed, children }) => ( -