├── .changeset ├── README.md └── config.json ├── .github └── workflows │ └── publish.yml ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── biome.json ├── package.json ├── packages ├── color-mode │ ├── package.json │ ├── src │ │ ├── color-mode-provider.tsx │ │ ├── color-mode-types.ts │ │ └── color-mode.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── components │ ├── .gitignore │ ├── demo │ │ ├── color-mode-switch.tsx │ │ ├── color-mode.ts │ │ ├── demo.css │ │ ├── main.tsx │ │ └── sandbox.tsx │ ├── index.html │ ├── package.json │ ├── panda.demo.config.ts │ ├── panda.static.ts │ ├── postcss.config.cjs │ ├── src │ │ ├── components.ts │ │ ├── hooks │ │ │ ├── use-callback-ref.ts │ │ │ ├── use-disclosure.ts │ │ │ ├── use-media-query.ts │ │ │ ├── use-merge-refs.ts │ │ │ └── use-safe-layout-effect.ts │ │ └── ui │ │ │ ├── alert.tsx │ │ │ ├── avatar.tsx │ │ │ ├── badge.tsx │ │ │ ├── button-icon.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── checkbox-icon.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── code.tsx │ │ │ ├── collapse.tsx │ │ │ ├── context.ts │ │ │ ├── create-form-element.tsx │ │ │ ├── create-style-context.tsx │ │ │ ├── form-control-context.ts │ │ │ ├── form-control.tsx │ │ │ ├── heading.tsx │ │ │ ├── icon.tsx │ │ │ ├── image.ts │ │ │ ├── input-addon.tsx │ │ │ ├── input.tsx │ │ │ ├── kbd.tsx │ │ │ ├── link.tsx │ │ │ ├── list.tsx │ │ │ ├── modal.tsx │ │ │ ├── popover.tsx │ │ │ ├── portal.tsx │ │ │ ├── select.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── tag.tsx │ │ │ ├── text.ts │ │ │ ├── textarea.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── transition-utils.ts │ │ │ ├── types.ts │ │ │ └── use-form-control.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── tsup.config.ts │ └── vite.config.ts ├── preset-chakra │ ├── package.json │ ├── reset.css │ ├── src │ │ ├── conditions.ts │ │ ├── default-config.ts │ │ ├── foundations.ts │ │ ├── global-styles.ts │ │ ├── keyframes.ts │ │ ├── preflight.ts │ │ ├── preset.ts │ │ ├── recipes.ts │ │ ├── recipes │ │ │ ├── alert.recipe.ts │ │ │ ├── avatar.recipe.ts │ │ │ ├── badge.recipe.ts │ │ │ ├── button.recipe.ts │ │ │ ├── card.recipe.ts │ │ │ ├── checkbox.recipe.ts │ │ │ ├── close-button.recipe.ts │ │ │ ├── code.recipe.ts │ │ │ ├── form-control.recipe.ts │ │ │ ├── heading.recipe.ts │ │ │ ├── icon.recipe.ts │ │ │ ├── input.recipe.ts │ │ │ ├── kbd.recipe.ts │ │ │ ├── link.recipe.ts │ │ │ ├── modal.recipe.ts │ │ │ ├── popover.recipe.ts │ │ │ ├── select.recipe.ts │ │ │ ├── skeleton.recipe.tsx │ │ │ ├── switch.recipe.tsx │ │ │ ├── table.recipe.ts │ │ │ ├── tabs.recipe.ts │ │ │ ├── tag.recipe.tsx │ │ │ ├── textarea.recipe.ts │ │ │ └── tooltip.recipe.ts │ │ ├── semantic-tokens.ts │ │ ├── text-styles.ts │ │ ├── tokens.ts │ │ ├── utilities.ts │ │ └── vars.ts │ ├── tsconfig.json │ └── tsup.config.ts ├── shared │ ├── package.json │ ├── src │ │ ├── assign-inline-vars.ts │ │ ├── color-mix.ts │ │ ├── css-calc.ts │ │ ├── css-var.ts │ │ ├── shared.ts │ │ ├── traverse.ts │ │ ├── utils.ts │ │ └── wrap-value.ts │ ├── tsconfig.json │ └── tsup.config.ts └── styled-system │ ├── package.json │ └── panda.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── storybook ├── .eslintrc.cjs ├── .gitignore ├── .storybook │ ├── main.ts │ └── preview.ts ├── README.md ├── index.html ├── package.json ├── panda.config.ts ├── postcss.config.cjs ├── stories │ ├── Button.stories.tsx │ ├── OverridenToken.stories.tsx │ └── index.css ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── tsconfig.build.json └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": ["storybook"] 11 | } 12 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | concurrency: ${{ github.workflow }}-${{ github.ref }} 9 | 10 | jobs: 11 | release: 12 | name: Release 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Repo 16 | uses: actions/checkout@v4 17 | 18 | - name: Setup Node.js 20.x 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 20.x 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v2 25 | with: 26 | version: 8 27 | 28 | - name: Install dependencies 29 | run: pnpm install --frozen-lockfile --ignore-scripts 30 | 31 | - name: Build 32 | run: pnpm build 33 | 34 | - name: Create Release Pull Request or Publish to npm 35 | id: changesets 36 | uses: changesets/action@v1 37 | with: 38 | publish: pnpm release 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 42 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Dependency directory 7 | **/node_modules/** 8 | node_modules 9 | 10 | dist 11 | dist-ssr 12 | out 13 | build 14 | **/buil 15 | tsconfig.tsbuildinfo 16 | .tsbuildinfo 17 | .DS_Store 18 | __generated__ 19 | *.env 20 | .idea 21 | *.vsix 22 | 23 | ## Panda 24 | styled-system 25 | styled-system-static 26 | panda 27 | panda-static 28 | outdir 29 | 30 | !packages/styled-system 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "[typescript]": { 4 | "editor.defaultFormatter": "biomejs.biome" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | repo archived cause Chakra-ui V3 is very close and now uses the same styling API as Panda, which means you can just pick recipe/slot recipes from there and almost copy/paste components ! 2 | -> Chakra imports recipes using `useRecipe`, with Panda those will be generated in `styled-system/recipes` 3 | 4 | ex: button recipe https://github.com/chakra-ui/chakra-ui/blob/1195171b9ec8b351daf7d36be3f012ccf63ba61d/packages/react/src/theme/recipes/button.ts 5 | button component https://github.com/chakra-ui/chakra-ui/blob/1195171b9ec8b351daf7d36be3f012ccf63ba61d/packages/react/src/components/button/button.tsx 6 | 7 | # 🥞 crepe-ui, a Chakra UI port using Panda+Ark-UI 8 | 9 | - `pnpm i` 10 | - `pnpm dev` 11 | - `cd packages/components` 12 | - `pnpm demo` 13 | 14 | Screenshot 2024-01-03 at 23 48 32 15 | Screenshot 2024-01-03 at 23 48 40 16 | 17 | --- 18 | 19 | We only need to watch/rebuild the `packages/preset-chakra` due to using 20 | [custom conditions](https://nodejs.org/api/packages.html#conditional-exports), 21 | since Panda doesn't support them yet. 22 | 23 | Updating the `packages/preset-chakra` will trigger a `static.css` file 24 | generation, which is used by the demo. 25 | 26 | ## TODO 27 | 28 | - update Ark-UI and remove all the pasted UseXXXProps types when v1 is released 29 | 30 | - create other frameworks implementation, only the React one is done 31 | 32 | - for each frameworks, export 2 different versions: 33 | 34 | 1. for Panda users, with `@crepe-ui/styled-system` as external dependency 35 | 2. for non-Panda users, with everything bundled 36 | 37 | - make a version of the components that is RSC-compatible = without 38 | `createStyleContext` 39 | - could use `defineParts` 40 | - or could just keep using slots but with explicit props passing 41 | 42 | ## Missing Chakra components 43 | 44 | Editable, Number Input, Pin Input, Radio, Range Slider, Slider, Stat, Circular 45 | Progress, Spinner, Toast, Highlight, Drawer, Menu, Accordion, Breadcrumb, 46 | Stepper 47 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", 3 | "organizeImports": { 4 | "enabled": true 5 | }, 6 | "formatter": { 7 | "enabled": true, 8 | "lineWidth": 120, 9 | "indentStyle": "tab", 10 | "formatWithErrors": true 11 | }, 12 | "javascript": { 13 | "formatter": { 14 | "quoteStyle": "single", 15 | "jsxQuoteStyle": "double", 16 | "bracketSpacing": true, 17 | "semicolons": "asNeeded", 18 | "trailingComma": "all" 19 | } 20 | }, 21 | "linter": { 22 | "enabled": false, 23 | "rules": { 24 | "recommended": true 25 | } 26 | }, 27 | "vcs": { 28 | "clientKind": "git", 29 | "enabled": true, 30 | "useIgnoreFile": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "crepe-ui", 3 | "private": true, 4 | "scripts": { 5 | "dev": "pnpm --parallel --filter=./packages/shared --filter=./packages/preset-chakra dev", 6 | "build": "pnpm -r --filter=./packages/** build", 7 | "fmt": "biome format . --write", 8 | "lint": "biome lint .", 9 | "typecheck": "tsc --noEmit", 10 | "release": "changeset publish", 11 | "storybook": "pnpm --filter=./storybook dev" 12 | }, 13 | "devDependencies": { 14 | "@biomejs/biome": "1.4.1", 15 | "@changesets/cli": "^2.27.1", 16 | "typescript": "^5.2.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /packages/color-mode/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@crepe-ui/color-mode", 3 | "version": "0.0.1", 4 | "types": "./src/color-mode.ts", 5 | "main": "./dist/color-mode.js", 6 | "exports": { 7 | ".": { 8 | "source": "./src/color-mode.ts", 9 | "types": "./dist/color-mode.d.ts", 10 | "import": { 11 | "types": "./dist/color-mode.d.mts", 12 | "default": "./dist/color-mode.mjs" 13 | }, 14 | "require": "./dist/color-mode.js" 15 | } 16 | }, 17 | "scripts": { 18 | "dev": "tsup --watch", 19 | "build": "tsup" 20 | }, 21 | "peerDependencies": { 22 | "react": "^17.0.2 || ^18.2.0", 23 | "react-dom": "^17.0.2 || ^18.2.0" 24 | }, 25 | "dependencies": { 26 | "react": "^18.2.0", 27 | "react-dom": "^18.2.0" 28 | }, 29 | "devDependencies": { 30 | "@types/react": "^18.2.31", 31 | "@types/react-dom": "^18.2.14", 32 | "tsup": "^7.2.0", 33 | "typescript": "^5.2.2" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/astahmer/crepe-ui", 38 | "directory": "packages/color-mode" 39 | }, 40 | "sideEffects": false, 41 | "files": [ 42 | "dist" 43 | ] 44 | } 45 | -------------------------------------------------------------------------------- /packages/color-mode/src/color-mode-types.ts: -------------------------------------------------------------------------------- 1 | /** Adapted from https://github.com/pacocoursey/next-theme/blob/a385b8d865bbb317ff73a5b6c1319ae566f7d6f1/src/types.ts */ 2 | import type { ReactNode } from 'react' 3 | 4 | interface ValueObject { 5 | [colorModeName: string]: string 6 | } 7 | 8 | export interface UseColorModeProps { 9 | /** List of all available colorMode names */ 10 | colorModes: string[] 11 | /** Forced colorMode name for the current page */ 12 | forcedColorMode?: string 13 | /** Update the colorMode */ 14 | setColorMode: (colorMode: string) => void 15 | /** Active colorMode name */ 16 | colorMode?: string 17 | /** If `enableSystem` is true and the active colorMode is "system", this returns whether the system preference resolved to "dark" or "light". Otherwise, identical to `colorMode` */ 18 | resolvedColorMode?: string 19 | /** If enableSystem is true, returns the System colorMode preference ("dark" or "light"), regardless what the active colorMode is */ 20 | systemColorMode?: 'dark' | 'light' 21 | } 22 | 23 | export interface ColorModeProviderProps { 24 | /** List of all available colorMode names */ 25 | colorModes?: string[] 26 | /** Forced colorMode name for the current page */ 27 | forcedColorMode?: string 28 | /** Whether to switch between dark and light colorModes based on prefers-color-scheme */ 29 | enableSystem?: boolean 30 | /** Disable all CSS transitions when switching colorModes */ 31 | disableTransitionOnChange?: boolean 32 | /** Whether to indicate to browsers which color scheme is used (dark or light) for built-in UI like inputs and buttons */ 33 | enableColorScheme?: boolean 34 | /** Key used to store colorMode setting in localStorage */ 35 | storageKey?: string 36 | /** Default colorMode name (for v0.0.12 and lower the default was light). If `enableSystem` is false, the default colorMode is light */ 37 | defaultColorMode?: string 38 | /** HTML attribute modified based on the active colorMode. Accepts `class` and `data-*` (meaning any data attribute, `data-mode`, `data-color`, etc.) */ 39 | attribute?: 'class' | (string & {}) 40 | /** Mapping of colorMode name to HTML attribute value. Object where key is the colorMode name and value is the attribute value */ 41 | value?: ValueObject 42 | /** Nonce string to pass to the inline script for CSP headers */ 43 | nonce?: string 44 | 45 | shadowHostId?: string 46 | rootId?: string 47 | children?: ReactNode 48 | } 49 | -------------------------------------------------------------------------------- /packages/color-mode/src/color-mode.ts: -------------------------------------------------------------------------------- 1 | export * from './color-mode-provider' 2 | export * from './color-mode-types' 3 | -------------------------------------------------------------------------------- /packages/color-mode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "include": ["src"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/color-mode/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsup' 2 | 3 | export default defineConfig({ 4 | entry: ['src/color-mode.ts'], 5 | clean: true, 6 | dts: true, 7 | format: ['esm', 'cjs'], 8 | }) 9 | -------------------------------------------------------------------------------- /packages/components/.gitignore: -------------------------------------------------------------------------------- 1 | static.css 2 | -------------------------------------------------------------------------------- /packages/components/demo/color-mode-switch.tsx: -------------------------------------------------------------------------------- 1 | import { useColorMode } from "./color-mode"; 2 | import { Button } from "@crepe-ui/components"; 3 | import { css } from "@crepe-ui/styled-system/css"; 4 | import { useEffect, useState } from "react"; 5 | 6 | export const ColorModeSwitch = () => { 7 | const [mounted, setMounted] = useState(false); 8 | const colorMode = useColorMode(); 9 | 10 | useEffect(() => { 11 | setMounted(true); 12 | }, []); 13 | 14 | const { setColorMode, resolvedColorMode } = colorMode; 15 | 16 | if (!mounted) { 17 | return null; 18 | } 19 | 20 | const isDark = resolvedColorMode === "dark"; 21 | 22 | const toggleColorMode = () => setColorMode(isDark ? "light" : "dark"); 23 | 24 | const IconToUse = isDark ? "🌙" : "☀️"; 25 | const iconText = isDark ? "Dark" : "Light"; 26 | 27 | return ( 28 | 38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /packages/components/demo/color-mode.ts: -------------------------------------------------------------------------------- 1 | export * from '../../color-mode' 2 | -------------------------------------------------------------------------------- /packages/components/demo/demo.css: -------------------------------------------------------------------------------- 1 | @layer reset, base, tokens, recipes, utilities; 2 | -------------------------------------------------------------------------------- /packages/components/demo/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { Sandbox } from './sandbox' 4 | import { ColorModeProvider } from './color-mode' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | 10 | 11 | , 12 | ) 13 | -------------------------------------------------------------------------------- /packages/components/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | components 7 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/components/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@crepe-ui/components", 3 | "version": "0.0.1", 4 | "types": "./src/components.ts", 5 | "exports": { 6 | ".": { 7 | "source": "./src/components.ts", 8 | "types": "./dist/components.d.ts", 9 | "import": { 10 | "types": "./dist/components.d.mts", 11 | "default": "./dist/components.mjs" 12 | }, 13 | "require": "./dist/components.js" 14 | }, 15 | "./static.css": "./static.css" 16 | }, 17 | "scripts": { 18 | "static": "panda cssgen static -o static.css -c panda.static.ts", 19 | "dev": "tsup --watch", 20 | "demo": "vite", 21 | "build": "tsup", 22 | "tslint": "tsc --project tsconfig.json", 23 | "typecheck": "tsc --noEmit" 24 | }, 25 | "peerDependencies": { 26 | "@ark-ui/react": ">=1.0.0", 27 | "react": "^17.0.2 || ^18.2.0", 28 | "react-dom": "^17.0.2 || ^18.2.0" 29 | }, 30 | "dependencies": { 31 | "@ark-ui/react": "^1.2.1", 32 | "@crepe-ui/preset-chakra": "workspace:^", 33 | "@crepe-ui/shared": "workspace:^", 34 | "@crepe-ui/styled-system": "workspace:^", 35 | "@pandacss/dev": "^0.27.1", 36 | "framer-motion": "^10.16.4", 37 | "react": "^18.2.0", 38 | "react-dom": "^18.2.0" 39 | }, 40 | "devDependencies": { 41 | "@types/react": "^18.2.31", 42 | "@types/react-dom": "^18.2.14", 43 | "@zag-js/avatar": "^0.32.0", 44 | "@zag-js/checkbox": "^0.32.0", 45 | "@zag-js/presence": "^0.32.0", 46 | "@zag-js/react": "^0.32.0", 47 | "@zag-js/select": "^0.32.0", 48 | "@zag-js/switch": "^0.32.0", 49 | "@zag-js/tabs": "^0.32.0", 50 | "tsup": "^7.2.0", 51 | "typescript": "^5.2.2", 52 | "vite": "^4.5.0", 53 | "vite-plugin-dts": "^3.6.0" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "https://github.com/astahmer/crepe-ui", 58 | "directory": "packages/components" 59 | }, 60 | "sideEffects": false, 61 | "files": [ 62 | "dist" 63 | ] 64 | } 65 | -------------------------------------------------------------------------------- /packages/components/panda.demo.config.ts: -------------------------------------------------------------------------------- 1 | import { defaultConfig } from '@crepe-ui/preset-chakra' 2 | import { defineConfig } from '@pandacss/dev' 3 | 4 | // This panda config is only used for the vite dev server, not for the component library itself 5 | export default defineConfig({ 6 | presets: defaultConfig.presets, 7 | preflight: false, 8 | include: ['demo/**/*'], 9 | importMap: '@crepe-ui/styled-system', 10 | jsxFramework: 'react', 11 | }) 12 | -------------------------------------------------------------------------------- /packages/components/panda.static.ts: -------------------------------------------------------------------------------- 1 | import { defaultConfig } from '@crepe-ui/preset-chakra' 2 | import { defineConfig } from '@pandacss/dev' 3 | 4 | // This panda/config only exists to generate the recipes variants staticCSS 5 | export default defineConfig({ 6 | preflight: false, 7 | presets: defaultConfig.presets, 8 | staticCss: defaultConfig.staticCss, 9 | }) 10 | -------------------------------------------------------------------------------- /packages/components/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | autoprefixer: {}, 4 | "@pandacss/dev/postcss": { 5 | configPath: "./panda.demo.config.ts", 6 | }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /packages/components/src/components.ts: -------------------------------------------------------------------------------- 1 | export * from './ui/alert' 2 | export * from './ui/avatar' 3 | export * from './ui/badge' 4 | export * from './ui/button' 5 | export * from './ui/button-icon' 6 | export * from './ui/card' 7 | export * from './ui/checkbox' 8 | export * from './ui/code' 9 | export * from './ui/collapse' 10 | export * from './ui/context' 11 | export * from './ui/create-style-context' 12 | export * from './ui/form-control' 13 | export * from './ui/heading' 14 | export * from './ui/icon' 15 | export * from './ui/image' 16 | export * from './ui/input' 17 | export * from './ui/kbd' 18 | export * from './ui/link' 19 | export * from './ui/list' 20 | export * from './ui/modal' 21 | export * from './ui/popover' 22 | export * from './ui/portal' 23 | export * from './ui/select' 24 | export * from './ui/skeleton' 25 | export * from './ui/switch' 26 | export * from './ui/table' 27 | export * from './ui/tabs' 28 | export * from './ui/tag' 29 | export * from './ui/text' 30 | export * from './ui/textarea' 31 | export * from './ui/tooltip' 32 | // 33 | export * from './hooks/use-disclosure' 34 | export * from './hooks/use-media-query' 35 | -------------------------------------------------------------------------------- /packages/components/src/hooks/use-callback-ref.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useRef } from 'react' 2 | 3 | export function useCallbackRef any>( 4 | callback: T | undefined, 5 | deps: React.DependencyList = [], 6 | ) { 7 | const callbackRef = useRef(callback) 8 | 9 | useEffect(() => { 10 | callbackRef.current = callback 11 | }) 12 | 13 | // eslint-disable-next-line react-hooks/exhaustive-deps 14 | return useCallback(((...args) => callbackRef.current?.(...args)) as T, deps) 15 | } 16 | -------------------------------------------------------------------------------- /packages/components/src/hooks/use-disclosure.ts: -------------------------------------------------------------------------------- 1 | import { useCallbackRef } from './use-callback-ref' 2 | import { useCallback, useState, useId } from 'react' 3 | 4 | export interface UseDisclosureProps { 5 | isOpen?: boolean 6 | defaultIsOpen?: boolean 7 | onClose?(): void 8 | onOpen?(): void 9 | id?: string 10 | } 11 | 12 | type HTMLProps = React.HTMLAttributes 13 | 14 | /** 15 | * `useDisclosure` is a custom hook used to help handle common open, close, or toggle scenarios. 16 | * It can be used to control feedback component such as `Modal`, `AlertDialog`, `Drawer`, etc. 17 | * 18 | * @see Docs https://chakra-ui.com/docs/hooks/use-disclosure 19 | */ 20 | export function useDisclosure(props: UseDisclosureProps = {}) { 21 | const { onClose: onCloseProp, onOpen: onOpenProp, isOpen: isOpenProp, id: idProp } = props 22 | 23 | const handleOpen = useCallbackRef(onOpenProp) 24 | const handleClose = useCallbackRef(onCloseProp) 25 | 26 | const [isOpenState, setIsOpen] = useState(props.defaultIsOpen || false) 27 | 28 | const isOpen = isOpenProp !== undefined ? isOpenProp : isOpenState 29 | 30 | const isControlled = isOpenProp !== undefined 31 | 32 | const uid = useId() 33 | const id = idProp ?? `disclosure-${uid}` 34 | 35 | const onClose = useCallback(() => { 36 | if (!isControlled) { 37 | setIsOpen(false) 38 | } 39 | handleClose?.() 40 | }, [isControlled, handleClose]) 41 | 42 | const onOpen = useCallback(() => { 43 | if (!isControlled) { 44 | setIsOpen(true) 45 | } 46 | handleOpen?.() 47 | }, [isControlled, handleOpen]) 48 | 49 | const onToggle = useCallback(() => { 50 | if (isOpen) { 51 | onClose() 52 | } else { 53 | onOpen() 54 | } 55 | }, [isOpen, onOpen, onClose]) 56 | 57 | function getButtonProps(props: HTMLProps = {}): HTMLProps { 58 | return { 59 | ...props, 60 | 'aria-expanded': isOpen, 61 | 'aria-controls': id, 62 | onClick(event) { 63 | props.onClick?.(event) 64 | onToggle() 65 | }, 66 | } 67 | } 68 | 69 | function getDisclosureProps(props: HTMLProps = {}): HTMLProps { 70 | return { 71 | ...props, 72 | hidden: !isOpen, 73 | id, 74 | } 75 | } 76 | 77 | return { 78 | isOpen, 79 | onOpen, 80 | onClose, 81 | onToggle, 82 | isControlled, 83 | getButtonProps, 84 | getDisclosureProps, 85 | } 86 | } 87 | 88 | export type UseDisclosureReturn = ReturnType 89 | -------------------------------------------------------------------------------- /packages/components/src/hooks/use-media-query.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useEffect, useState } from 'react' 2 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/media-query/use-media-query.ts 3 | 4 | export type UseMediaQueryOptions = { 5 | fallback?: boolean | boolean[] 6 | ssr?: boolean 7 | } 8 | 9 | /** 10 | * React hook that tracks state of a CSS media query 11 | * 12 | * @param query the media query to match 13 | * @param options the media query options { fallback, ssr } 14 | * 15 | * @see Docs https://chakra-ui.com/docs/hooks/use-media-query 16 | */ 17 | export function useMediaQuery(query: string | string[], options: UseMediaQueryOptions = {}): boolean[] { 18 | const { ssr = true, fallback } = options 19 | 20 | const getWindow = useCallback(() => window, []) 21 | const queries = Array.isArray(query) ? query : [query] 22 | 23 | let fallbackValues = Array.isArray(fallback) ? fallback : [fallback] 24 | fallbackValues = fallbackValues.filter((v) => v != null) as boolean[] 25 | 26 | const [value, setValue] = useState(() => { 27 | return queries.map((query, index) => ({ 28 | media: query, 29 | matches: ssr ? !!fallbackValues[index] : getWindow().matchMedia(query).matches, 30 | })) 31 | }) 32 | 33 | useEffect(() => { 34 | const win = getWindow() 35 | setValue( 36 | queries.map((query) => ({ 37 | media: query, 38 | matches: win.matchMedia(query).matches, 39 | })), 40 | ) 41 | 42 | const mql = queries.map((query) => win.matchMedia(query)) 43 | 44 | const handler = (evt: MediaQueryListEvent) => { 45 | setValue((prev) => { 46 | return prev.slice().map((item) => { 47 | if (item.media === evt.media) return { ...item, matches: evt.matches } 48 | return item 49 | }) 50 | }) 51 | } 52 | 53 | mql.forEach((mql) => { 54 | if (typeof mql.addListener === 'function') { 55 | mql.addListener(handler) 56 | } else { 57 | mql.addEventListener('change', handler) 58 | } 59 | }) 60 | 61 | return () => { 62 | mql.forEach((mql) => { 63 | if (typeof mql.removeListener === 'function') { 64 | mql.removeListener(handler) 65 | } else { 66 | mql.removeEventListener('change', handler) 67 | } 68 | }) 69 | } 70 | // eslint-disable-next-line react-hooks/exhaustive-deps 71 | }, [getWindow]) 72 | 73 | return value.map((item) => item.matches) 74 | } 75 | -------------------------------------------------------------------------------- /packages/components/src/hooks/use-merge-refs.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | 3 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/hooks/src/use-merge-refs.ts 4 | 5 | export type ReactRef = React.RefCallback | React.MutableRefObject 6 | 7 | export function assignRef(ref: ReactRef | null | undefined, value: T) { 8 | if (ref == null) return 9 | 10 | if (typeof ref === 'function') { 11 | ref(value) 12 | return 13 | } 14 | 15 | try { 16 | ref.current = value 17 | } catch (error) { 18 | throw new Error(`Cannot assign value '${value}' to ref '${ref}'`) 19 | } 20 | } 21 | 22 | export function mergeRefs(...refs: (ReactRef | null | undefined)[]) { 23 | return (node: T | null) => { 24 | refs.forEach((ref) => { 25 | assignRef(ref, node) 26 | }) 27 | } 28 | } 29 | 30 | export function useMergeRefs(...refs: (ReactRef | null | undefined)[]) { 31 | // eslint-disable-next-line react-hooks/exhaustive-deps 32 | return useMemo(() => mergeRefs(...refs), refs) 33 | } 34 | -------------------------------------------------------------------------------- /packages/components/src/hooks/use-safe-layout-effect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useLayoutEffect } from 'react' 2 | 3 | export const useSafeLayoutEffect = Boolean(globalThis?.document) ? useLayoutEffect : useEffect 4 | -------------------------------------------------------------------------------- /packages/components/src/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentPropsWithoutRef } from "react"; 2 | import { css } from "@crepe-ui/styled-system/css"; 3 | import { styled } from "@crepe-ui/styled-system/jsx"; 4 | import { alert } from "@crepe-ui/styled-system/recipes"; 5 | import type { SystemStyleObject } from "@crepe-ui/styled-system/types"; 6 | import { createStyleContext } from "./create-style-context"; 7 | 8 | const { withProvider, withContext } = createStyleContext(alert); 9 | 10 | const AlertRoot = withProvider(styled("div"), "container"); 11 | const AlertIcon = withContext(styled("div"), "icon"); 12 | 13 | interface IconProps extends ComponentPropsWithoutRef<"svg"> { 14 | css?: SystemStyleObject; 15 | } 16 | 17 | function CheckIcon({ css: cssProp, ...props }: IconProps) { 18 | return ( 19 | 20 | 25 | 29 | 30 | 31 | ); 32 | } 33 | 34 | function InfoIcon({ css: cssProp, ...props }: IconProps) { 35 | return ( 36 | 37 | 42 | 46 | 47 | 48 | ); 49 | } 50 | 51 | function WarningIcon({ css: cssProp, ...props }: IconProps) { 52 | return ( 53 | 54 | 59 | 63 | 64 | 65 | ); 66 | } 67 | 68 | export const Alert = Object.assign(AlertRoot, { 69 | Root: AlertRoot, 70 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/alert/alert-context.ts#L20 71 | InfoIcon: InfoIcon, 72 | WarningIcon: WarningIcon, 73 | SuccessIcon: CheckIcon, 74 | ErrorIcon: WarningIcon, 75 | }); 76 | -------------------------------------------------------------------------------- /packages/components/src/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as Ark from "@ark-ui/react/avatar"; 2 | import { HTMLStyledProps, styled } from "@crepe-ui/styled-system/jsx"; 3 | import { 4 | avatar, 5 | type AvatarVariantProps, 6 | } from "@crepe-ui/styled-system/recipes"; 7 | import { createStyleContext } from "./create-style-context"; 8 | import { Assign } from "@crepe-ui/styled-system/types"; 9 | 10 | import { ComponentProps, ForwardRefExoticComponent } from "react"; 11 | import type * as zag from "@zag-js/avatar"; 12 | import { type Optional } from "./types"; 13 | 14 | const { withProvider, withContext } = createStyleContext(avatar); 15 | 16 | // export * from '@ark-ui/react/avatar'; 17 | 18 | interface StyleProps extends HTMLStyledProps {} 19 | interface JsxProps extends Assign {} 20 | 21 | export interface AvatarProps extends JsxProps, AvatarVariantProps {} 22 | 23 | // Ark-UI doesn't (yet ?) expose the UseXXXProps and we need it for tsc .d.ts 24 | // https://github.com/microsoft/TypeScript/issues/47663 25 | // https://github.com/chakra-ui/ark/blob/ba18a28ac8dae026d2489e6fb19d4064beaeb407/packages/frameworks/react/src/avatar/use-avatar.ts 26 | interface UseAvatarProps extends Optional {} 27 | 28 | // and that means we also have to cast this one 29 | const AvatarRoot = withProvider( 30 | styled( 31 | Ark.Avatar.Root as ForwardRefExoticComponent< 32 | ComponentProps<"div"> & UseAvatarProps 33 | > 34 | ), 35 | "root" 36 | ); 37 | 38 | const AvatarFallback = withContext(styled(Ark.Avatar.Fallback), "fallback"); 39 | const AvatarImage = withContext(styled(Ark.Avatar.Image), "image"); 40 | 41 | export const Avatar = Object.assign(AvatarRoot, { 42 | Root: AvatarRoot, 43 | Fallback: AvatarFallback, 44 | Image: AvatarImage, 45 | }); 46 | -------------------------------------------------------------------------------- /packages/components/src/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@crepe-ui/styled-system/jsx"; 2 | import { badge } from "@crepe-ui/styled-system/recipes"; 3 | 4 | export const Badge = styled("span", badge); 5 | -------------------------------------------------------------------------------- /packages/components/src/ui/button-icon.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@crepe-ui/styled-system/jsx"; 2 | import type { ComponentPropsWithoutRef } from "react"; 3 | 4 | export interface ButtonIconProps 5 | extends ComponentPropsWithoutRef {} 6 | export const ButtonIcon = styled(styled.span, { 7 | base: { 8 | display: "inline-flex", 9 | alignSelf: "center", 10 | flexShrink: 0, 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/components/src/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, type ComponentProps, type ReactNode } from "react"; 2 | 3 | import { ark } from "@ark-ui/react"; 4 | import { css, cx } from "@crepe-ui/styled-system/css"; 5 | import { styled } from "@crepe-ui/styled-system/jsx"; 6 | import { 7 | button, 8 | type ButtonVariantProps, 9 | } from "@crepe-ui/styled-system/recipes"; 10 | import { ButtonIcon } from "./button-icon"; 11 | import { JsxStyleProps } from "@crepe-ui/styled-system/types"; 12 | 13 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/theme/src/components/button.ts 14 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/button/button.tsx#L60 15 | 16 | const ButtonRoot = styled(ark.button, button); 17 | 18 | interface StyleProps extends ComponentProps {} 19 | 20 | export interface ButtonProps 21 | extends Omit, 22 | Omit { 23 | leftIcon?: ReactNode; 24 | colorPalette?: JsxStyleProps["colorPalette"]; 25 | } 26 | 27 | /** 28 | * Button component is used to trigger an action or event, such as submitting a form, opening a Dialog, canceling an action, or performing a delete operation. 29 | * 30 | * @see Docs https://chakra-ui.com/docs/components/button 31 | * @see WAI-ARIA https://www.w3.org/WAI/ARIA/apg/patterns/button/ 32 | */ 33 | export const Button = forwardRef( 34 | ({ children, leftIcon, className, ...props }, ref) => { 35 | return ( 36 | // Little trick to get the colorPalette prop to work 37 | // While keeping the colorPalette used for the compoundVariants 38 | 43 | {children} 44 | 45 | ); 46 | } 47 | ); 48 | 49 | Button.displayName = "Button"; 50 | 51 | type ButtonContentProps = Pick; 52 | 53 | function ButtonContent(props: ButtonContentProps) { 54 | const { leftIcon, children } = props; 55 | return ( 56 | <> 57 | {leftIcon && {leftIcon}} 58 | {children} 59 | 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /packages/components/src/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@crepe-ui/styled-system/jsx"; 2 | import { card } from "@crepe-ui/styled-system/recipes"; 3 | import { createStyleContext } from "./create-style-context"; 4 | 5 | const { withProvider, withContext } = createStyleContext(card); 6 | 7 | const CardRoot = withProvider(styled("div"), "container"); 8 | const CardBody = withContext(styled("div"), "body"); 9 | const CardHeader = withContext(styled("div"), "header"); 10 | const CardFooter = withContext(styled("div"), "footer"); 11 | 12 | export const Card = Object.assign(CardRoot, { 13 | Root: CardRoot, 14 | Body: CardBody, 15 | Header: CardHeader, 16 | Footer: CardFooter, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/components/src/ui/checkbox-icon.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentPropsWithoutRef } from "react"; 2 | import { HTMLStyledProps, styled } from "@crepe-ui/styled-system/jsx"; 3 | 4 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/checkbox/checkbox-icon.tsx 5 | 6 | function CheckIcon(props: ComponentPropsWithoutRef) { 7 | return ( 8 | 19 | 20 | 21 | ); 22 | } 23 | 24 | function IndeterminateIcon(props: ComponentPropsWithoutRef) { 25 | return ( 26 | 31 | 32 | 33 | ); 34 | } 35 | 36 | export interface CheckboxIconProps extends HTMLStyledProps<"svg"> { 37 | /** 38 | * @default false 39 | */ 40 | isIndeterminate?: boolean; 41 | /** 42 | * @default false 43 | */ 44 | isChecked?: boolean; 45 | } 46 | 47 | /** 48 | * CheckboxIcon is used to visually indicate the checked or indeterminate 49 | * state of a checkbox. 50 | * 51 | * @todo allow users pass their own icon svgs 52 | */ 53 | export function CheckboxIcon(props: CheckboxIconProps) { 54 | const { className, isIndeterminate, isChecked, ...rest } = props; 55 | if (!isChecked && !isIndeterminate) return null; 56 | 57 | const BaseIcon = isIndeterminate ? IndeterminateIcon : CheckIcon; 58 | 59 | return ( 60 | 71 | 72 | 73 | ); 74 | } 75 | -------------------------------------------------------------------------------- /packages/components/src/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as Ark from "@ark-ui/react/checkbox"; 2 | import { HTMLStyledProps, styled } from "@crepe-ui/styled-system/jsx"; 3 | import { 4 | checkbox, 5 | type CheckboxVariantProps, 6 | } from "@crepe-ui/styled-system/recipes"; 7 | import { CheckboxIcon } from "./checkbox-icon"; 8 | import { createStyleContext } from "./create-style-context"; 9 | 10 | import { Assign } from "@crepe-ui/styled-system/types"; 11 | import type * as zag from "@zag-js/checkbox"; 12 | import type { PropTypes } from "@zag-js/react"; 13 | import { ComponentProps, ForwardRefExoticComponent, ReactNode } from "react"; 14 | import { type Optional } from "./types"; 15 | 16 | const { withProvider, withContext } = createStyleContext(checkbox); 17 | 18 | // export * from '@ark-ui/react/checkbox'; 19 | 20 | interface StyleProps extends HTMLStyledProps<"label"> {} 21 | interface JsxProps extends Assign {} 22 | 23 | export interface CheckboxProps extends JsxProps, CheckboxVariantProps {} 24 | 25 | // Ark-UI doesn't (yet ?) expose the UseXXXProps and we need it for tsc .d.ts 26 | // https://github.com/microsoft/TypeScript/issues/47663 27 | // https://github.com/chakra-ui/ark/blob/ba18a28ac8dae026d2489e6fb19d4064beaeb407/packages/frameworks/react/src/checkbox/use-checkbox.ts 28 | interface UseCheckboxProps extends Optional { 29 | defaultChecked?: zag.Context["checked"]; 30 | } 31 | 32 | // and that means we also have to cast this one 33 | const CheckboxRoot = withProvider( 34 | styled( 35 | Ark.Checkbox.Root as ForwardRefExoticComponent< 36 | Assign< 37 | ComponentProps<"label"> & UseCheckboxProps, 38 | { 39 | children?: ReactNode | ((pages: zag.Api) => ReactNode); 40 | } 41 | > 42 | > 43 | ), 44 | "container" 45 | ); 46 | 47 | const CheckboxControl = withContext(styled(Ark.Checkbox.Control), "control"); 48 | const CheckboxLabel = withContext(styled(Ark.Checkbox.Label), "label"); 49 | 50 | export const Checkbox = Object.assign(CheckboxRoot, { 51 | Root: CheckboxRoot, 52 | Control: CheckboxControl, 53 | Label: CheckboxLabel, 54 | Icon: withContext(CheckboxIcon, "icon"), 55 | }); 56 | -------------------------------------------------------------------------------- /packages/components/src/ui/code.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@crepe-ui/styled-system/jsx"; 2 | import { code } from "@crepe-ui/styled-system/recipes"; 3 | 4 | export const Code = styled("code", code); 5 | -------------------------------------------------------------------------------- /packages/components/src/ui/collapse.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef, useEffect, useState } from "react"; 2 | import { 3 | AnimatePresence, 4 | HTMLMotionProps, 5 | motion, 6 | Variants as _Variants, 7 | } from "framer-motion"; 8 | import { cx } from "@crepe-ui/styled-system/css"; 9 | import { 10 | TRANSITION_EASINGS, 11 | Variants, 12 | withDelay, 13 | WithTransitionConfig, 14 | } from "./transition-utils"; 15 | 16 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/transition/collapse.tsx 17 | 18 | const isNumeric = (value?: number | string) => 19 | value != null && Number.parseInt(value.toString(), 10) > 0; 20 | 21 | export interface CollapseOptions { 22 | /** 23 | * If `true`, the opacity of the content will be animated 24 | * @default true 25 | */ 26 | animateOpacity?: boolean; 27 | /** 28 | * The height you want the content in its collapsed state. 29 | * @default 0 30 | */ 31 | startingHeight?: number | string; 32 | /** 33 | * The height you want the content in its expanded state. 34 | * @default "auto" 35 | */ 36 | endingHeight?: number | string; 37 | } 38 | 39 | const defaultTransitions = { 40 | exit: { 41 | height: { duration: 0.2, ease: TRANSITION_EASINGS.ease }, 42 | opacity: { duration: 0.3, ease: TRANSITION_EASINGS.ease }, 43 | }, 44 | enter: { 45 | height: { duration: 0.3, ease: TRANSITION_EASINGS.ease }, 46 | opacity: { duration: 0.4, ease: TRANSITION_EASINGS.ease }, 47 | }, 48 | }; 49 | 50 | const variants: Variants = { 51 | exit: ({ 52 | animateOpacity, 53 | startingHeight, 54 | transition, 55 | transitionEnd, 56 | delay, 57 | }) => ({ 58 | ...(animateOpacity && { opacity: isNumeric(startingHeight) ? 1 : 0 }), 59 | height: startingHeight, 60 | transitionEnd: transitionEnd?.exit, 61 | transition: 62 | transition?.exit ?? withDelay.exit(defaultTransitions.exit, delay), 63 | }), 64 | enter: ({ 65 | animateOpacity, 66 | endingHeight, 67 | transition, 68 | transitionEnd, 69 | delay, 70 | }) => ({ 71 | ...(animateOpacity && { opacity: 1 }), 72 | height: endingHeight, 73 | transitionEnd: transitionEnd?.enter, 74 | transition: 75 | transition?.enter ?? withDelay.enter(defaultTransitions.enter, delay), 76 | }), 77 | }; 78 | 79 | export type ICollapse = CollapseProps; 80 | 81 | export interface CollapseProps 82 | extends WithTransitionConfig>, 83 | CollapseOptions {} 84 | 85 | export const Collapse = forwardRef( 86 | (props, ref) => { 87 | const { 88 | in: isOpen, 89 | unmountOnExit, 90 | animateOpacity = true, 91 | startingHeight = 0, 92 | endingHeight = "auto", 93 | style, 94 | className, 95 | transition, 96 | transitionEnd, 97 | ...rest 98 | } = props; 99 | 100 | const [mounted, setMounted] = useState(false); 101 | useEffect(() => { 102 | const timeout = setTimeout(() => { 103 | setMounted(true); 104 | }); 105 | return () => void clearTimeout(timeout); 106 | }, []); 107 | 108 | /** 109 | * Warn 🚨: `startingHeight` and `unmountOnExit` are mutually exclusive 110 | * 111 | * If you specify a starting height, the collapsed needs to be mounted 112 | * for the height to take effect. 113 | */ 114 | const condition = Number(startingHeight) > 0 && !!unmountOnExit; 115 | if (condition) { 116 | console.warn({ 117 | condition: Number(startingHeight) > 0 && !!unmountOnExit, 118 | message: `startingHeight and unmountOnExit are mutually exclusive. You can't use them together`, 119 | }); 120 | } 121 | 122 | const hasStartingHeight = Number.parseFloat(startingHeight.toString()) > 0; 123 | 124 | const custom = { 125 | startingHeight, 126 | endingHeight, 127 | animateOpacity, 128 | transition: mounted ? transition : { enter: { duration: 0 } }, 129 | transitionEnd: { 130 | enter: transitionEnd?.enter, 131 | exit: unmountOnExit 132 | ? transitionEnd?.exit 133 | : { 134 | ...transitionEnd?.exit, 135 | display: hasStartingHeight ? "block" : "none", 136 | }, 137 | }, 138 | }; 139 | 140 | const show = unmountOnExit ? isOpen : true; 141 | const animate = isOpen || unmountOnExit ? "enter" : "exit"; 142 | 143 | return ( 144 | 145 | {show && ( 146 | 161 | )} 162 | 163 | ); 164 | } 165 | ); 166 | 167 | Collapse.displayName = "Collapse"; 168 | -------------------------------------------------------------------------------- /packages/components/src/ui/context.ts: -------------------------------------------------------------------------------- 1 | import { createContext as createReactContext, useContext as useReactContext } from 'react' 2 | 3 | export interface CreateContextOptions { 4 | strict?: boolean 5 | hookName?: string 6 | providerName?: string 7 | errorMessage?: string 8 | name?: string 9 | defaultValue?: T 10 | } 11 | 12 | export type CreateContextReturn = [React.Provider, () => T, React.Context] 13 | 14 | function getErrorMessage(hook: string, provider: string) { 15 | return `${hook} returned \`undefined\`. Seems you forgot to wrap component within ${provider}` 16 | } 17 | 18 | export function createContext(options: CreateContextOptions = {}) { 19 | const { 20 | name, 21 | strict = true, 22 | hookName = 'useContext', 23 | providerName = 'Provider', 24 | errorMessage, 25 | defaultValue, 26 | } = options 27 | 28 | const Context = createReactContext(defaultValue) 29 | 30 | Context.displayName = name 31 | 32 | function useContext() { 33 | const context = useReactContext(Context) 34 | 35 | if (!context && strict) { 36 | const error = new Error(errorMessage ?? getErrorMessage(hookName, providerName)) 37 | error.name = 'ContextError' 38 | // @ts-ignore 39 | Error.captureStackTrace?.(error, useContext) 40 | throw error 41 | } 42 | 43 | return context 44 | } 45 | 46 | return [Context.Provider, useContext, Context] as CreateContextReturn 47 | } 48 | -------------------------------------------------------------------------------- /packages/components/src/ui/create-form-element.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react' 2 | import { FormContextGetterProps, FormControlProviderContext, useFormControlContext } from './form-control-context' 3 | 4 | export const createFormContextElement = ( 5 | BaseElement: React.ComponentType, 6 | elementName: string, 7 | getterName: FormContextGetterProps, 8 | conditionalCheck?: (field: FormControlProviderContext) => boolean, 9 | ) => { 10 | const Component = forwardRef((props: any, ref: any) => { 11 | const field = useFormControlContext() 12 | 13 | if (conditionalCheck && !conditionalCheck(field)) { 14 | return null 15 | } 16 | 17 | const formProps = field?.[getterName](props, ref) 18 | 19 | return 20 | }) 21 | 22 | Component.displayName = elementName 23 | 24 | return Component 25 | } 26 | -------------------------------------------------------------------------------- /packages/components/src/ui/create-style-context.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, forwardRef, useContext, type ComponentProps, type ElementType } from 'react' 2 | 3 | // https://github.com/kumaaa-inc/shadow-panda/blob/2e8fa2cb37c66d97bd0ae705297c471ef5c2d548/packages/style-context/src/style-context.tsx#L15 4 | 5 | type AnyProps = Record 6 | type AnyRecipe = { 7 | (props?: AnyProps): Record 8 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 9 | splitVariantProps: (props: AnyProps) => any 10 | } 11 | 12 | type Slot = keyof ReturnType 13 | type SlotRecipe = Record, string> 14 | type VariantProps = Parameters[0] 15 | 16 | export interface StyledContextProvider { 17 | (props: ComponentProps & VariantProps): JSX.Element 18 | } 19 | 20 | const cx = (...args: (string | undefined)[]) => args.filter(Boolean).join(' ') 21 | 22 | export const createStyleContext = (recipe: R) => { 23 | const StyleContext = createContext | null>(null) 24 | 25 | const withProvider = ( 26 | Component: T, 27 | slot?: Slot, 28 | defaultProps?: Partial & { className?: string }, 29 | ) => { 30 | const Comp = forwardRef((props: ComponentProps & VariantProps, ref) => { 31 | const [variantProps, otherProps] = recipe.splitVariantProps(props as AnyProps) 32 | const { className = '', ...rest } = otherProps 33 | const styles = recipe(variantProps) as SlotRecipe 34 | const slotClass = styles?.[slot ?? ''] 35 | const classNames = cx(defaultProps?.className, slotClass, className) 36 | 37 | return ( 38 | 39 | 48 | 49 | ) 50 | }) 51 | 52 | // @ts-expect-error it's fine 53 | Comp.displayName = Component.displayName || Component.name 54 | return Comp as unknown as StyledContextProvider 55 | } 56 | 57 | const withContext = ( 58 | Component: T, 59 | slot?: Slot, 60 | defaultProps?: Partial & { className?: string }, 61 | ) => { 62 | if (!slot) return Component 63 | 64 | const Comp = forwardRef( 65 | ( 66 | { className, ...rest }: ComponentProps & { className?: string }, 67 | ref, 68 | ) => { 69 | const styles = useContext(StyleContext); 70 | const slotClass = styles?.[slot ?? ""]; 71 | const classNames = cx(defaultProps?.className, slotClass, className); 72 | 73 | return ( 74 | 83 | ); 84 | }, 85 | ); 86 | 87 | // @ts-expect-error it's fine 88 | Comp.displayName = Component.displayName || Component.name 89 | return Comp as unknown as T 90 | } 91 | 92 | return { 93 | withProvider, 94 | withContext, 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /packages/components/src/ui/form-control-context.ts: -------------------------------------------------------------------------------- 1 | import { useCallback, useId, useState } from 'react' 2 | import { createContext } from './context' 3 | import { mergeRefs } from '../hooks/use-merge-refs' 4 | import { dataAttr } from '@crepe-ui/shared' 5 | 6 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/form-control/form-control.tsx 7 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/form-control/form-control.tsx 8 | 9 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/shared/types.ts 10 | 11 | type Merge = N extends Record ? M : Omit & N 12 | interface DOMElement extends Element, HTMLOrSVGElement {} 13 | type DataAttributes = { 14 | [dataAttr: string]: any 15 | } 16 | 17 | interface DOMAttributes extends React.AriaAttributes, React.DOMAttributes, DataAttributes { 18 | id?: string 19 | role?: React.AriaRole 20 | tabIndex?: number 21 | style?: React.CSSProperties 22 | } 23 | 24 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/components/src/shared/types.ts 25 | type PropGetter

, R = DOMAttributes> = ( 26 | props?: Merge, 27 | ref?: React.Ref, 28 | ) => R & React.RefAttributes 29 | 30 | export interface FormControlOptions { 31 | /** 32 | * If `true`, the form control will be required. This has 2 side effects: 33 | * - The `FormLabel` will show a required indicator 34 | * - The form element (e.g, Input) will have `aria-required` set to `true` 35 | * 36 | * @default false 37 | */ 38 | isRequired?: boolean 39 | /** 40 | * If `true`, the form control will be disabled. This has 2 side effects: 41 | * - The `FormLabel` will have `data-disabled` attribute 42 | * - The form element (e.g, Input) will be disabled 43 | * 44 | * @default false 45 | */ 46 | isDisabled?: boolean 47 | /** 48 | * If `true`, the form control will be invalid. This has 2 side effects: 49 | * - The `FormLabel` and `FormErrorIcon` will have `data-invalid` set to `true` 50 | * - The form element (e.g, Input) will have `aria-invalid` set to `true` 51 | * 52 | * @default false 53 | */ 54 | isInvalid?: boolean 55 | /** 56 | * If `true`, the form control will be readonly 57 | * 58 | * @default false 59 | */ 60 | isReadOnly?: boolean 61 | } 62 | 63 | interface FormControlContext extends FormControlOptions { 64 | /** 65 | * The label text used to inform users as to what information is 66 | * requested for a text field. 67 | */ 68 | label?: string 69 | /** 70 | * The custom `id` to use for the form control. This is passed directly to the form element (e.g, Input). 71 | * - The form element (e.g. Input) gets the `id` 72 | * - The form label id: `form-label-${id}` 73 | * - The form error text id: `form-error-text-${id}` 74 | * - The form helper text id: `form-helper-text-${id}` 75 | */ 76 | id?: string 77 | } 78 | 79 | export type FormControlProviderContext = Omit, 'getRootProps' | 'htmlProps'> 80 | 81 | export const [FormControlProvider, useFormControlContext] = createContext({ 82 | strict: false, 83 | name: 'FormControlContext', 84 | }) 85 | 86 | export function useFormControlProvider(props: FormControlContext) { 87 | const { id: idProp, isRequired, isInvalid, isDisabled, isReadOnly, ...htmlProps } = props 88 | 89 | // Generate all the required ids 90 | const uuid = useId() 91 | const id = idProp || `field-${uuid}` 92 | 93 | const labelId = `${id}-label` 94 | const feedbackId = `${id}-feedback` 95 | const helpTextId = `${id}-helptext` 96 | 97 | /** 98 | * Track whether the `FormErrorMessage` has been rendered. 99 | * We use this to append its id the `aria-describedby` of the `input`. 100 | */ 101 | const [hasFeedbackText, setHasFeedbackText] = useState(false) 102 | 103 | /** 104 | * Track whether the `FormHelperText` has been rendered. 105 | * We use this to append its id the `aria-describedby` of the `input`. 106 | */ 107 | const [hasHelpText, setHasHelpText] = useState(false) 108 | 109 | // Track whether the form element (e.g, `input`) has focus. 110 | const [isFocused, setFocus] = useState(false) 111 | 112 | const getHelpTextProps = useCallback( 113 | (props = {}, forwardedRef = null) => ({ 114 | id: helpTextId, 115 | ...props, 116 | /** 117 | * Notify the field context when the help text is rendered on screen, 118 | * so we can apply the correct `aria-describedby` to the field (e.g. input, textarea). 119 | */ 120 | ref: mergeRefs(forwardedRef, (node) => { 121 | if (!node) return 122 | setHasHelpText(true) 123 | }), 124 | }), 125 | [helpTextId], 126 | ) 127 | 128 | const getLabelProps = useCallback( 129 | (props = {}, forwardedRef = null) => ({ 130 | ...props, 131 | ref: forwardedRef, 132 | 'data-focus': dataAttr(isFocused), 133 | 'data-disabled': dataAttr(isDisabled), 134 | 'data-invalid': dataAttr(isInvalid), 135 | 'data-readonly': dataAttr(isReadOnly), 136 | id: props.id !== undefined ? props.id : labelId, 137 | htmlFor: props.htmlFor !== undefined ? props.htmlFor : id, 138 | }), 139 | [id, isDisabled, isFocused, isInvalid, isReadOnly, labelId], 140 | ) 141 | 142 | const getErrorMessageProps = useCallback( 143 | (props = {}, forwardedRef = null) => ({ 144 | id: feedbackId, 145 | ...props, 146 | /** 147 | * Notify the field context when the error message is rendered on screen, 148 | * so we can apply the correct `aria-describedby` to the field (e.g. input, textarea). 149 | */ 150 | ref: mergeRefs(forwardedRef, (node) => { 151 | if (!node) return 152 | setHasFeedbackText(true) 153 | }), 154 | 'aria-live': 'polite', 155 | }), 156 | [feedbackId], 157 | ) 158 | 159 | const getRootProps = useCallback( 160 | (props = {}, forwardedRef = null) => ({ 161 | ...props, 162 | ...htmlProps, 163 | ref: forwardedRef, 164 | role: 'group', 165 | }), 166 | [htmlProps], 167 | ) 168 | 169 | const getRequiredIndicatorProps = useCallback( 170 | (props = {}, forwardedRef = null) => ({ 171 | ...props, 172 | ref: forwardedRef, 173 | role: 'presentation', 174 | 'aria-hidden': true, 175 | children: props.children || '*', 176 | }), 177 | [], 178 | ) 179 | 180 | return { 181 | isRequired: !!isRequired, 182 | isInvalid: !!isInvalid, 183 | isReadOnly: !!isReadOnly, 184 | isDisabled: !!isDisabled, 185 | isFocused: !!isFocused, 186 | onFocus: () => setFocus(true), 187 | onBlur: () => setFocus(false), 188 | hasFeedbackText, 189 | setHasFeedbackText, 190 | hasHelpText, 191 | setHasHelpText, 192 | id, 193 | labelId, 194 | feedbackId, 195 | helpTextId, 196 | htmlProps, 197 | getHelpTextProps, 198 | getErrorMessageProps, 199 | getRootProps, 200 | getLabelProps, 201 | getRequiredIndicatorProps, 202 | } 203 | } 204 | 205 | export type FormContextGetterProps = Extract 206 | -------------------------------------------------------------------------------- /packages/components/src/ui/form-control.tsx: -------------------------------------------------------------------------------- 1 | import { Assign } from "@crepe-ui/styled-system/types"; 2 | import { forwardRef } from "react"; 3 | import { HTMLStyledProps, styled } from "@crepe-ui/styled-system/jsx"; 4 | import { 5 | formControl, 6 | type FormControlVariantProps, 7 | } from "@crepe-ui/styled-system/recipes"; 8 | import { ComponentProps } from "@crepe-ui/styled-system/types"; 9 | import { createFormContextElement } from "./create-form-element"; 10 | import { createStyleContext } from "./create-style-context"; 11 | import { 12 | FormControlOptions, 13 | FormControlProvider, 14 | useFormControlContext, 15 | useFormControlProvider, 16 | } from "./form-control-context"; 17 | import { Icon } from "./icon"; 18 | 19 | const { withProvider, withContext } = createStyleContext(formControl); 20 | 21 | // export * from '@ark-ui/react/formControl'; 22 | interface StyleProps extends HTMLStyledProps<"div"> {} 23 | interface JsxProps extends Assign, StyleProps> {} 24 | 25 | interface FormControlContext extends FormControlOptions { 26 | /** 27 | * The label text used to inform users as to what information is 28 | * requested for a text field. 29 | */ 30 | label?: string; 31 | /** 32 | * The custom `id` to use for the form control. This is passed directly to the form element (e.g, Input). 33 | * - The form element (e.g. Input) gets the `id` 34 | * - The form label id: `form-label-${id}` 35 | * - The form error text id: `form-error-text-${id}` 36 | * - The form helper text id: `form-helper-text-${id}` 37 | */ 38 | id?: string; 39 | } 40 | 41 | export interface FormControlProps 42 | extends Omit, 43 | FormControlVariantProps, 44 | FormControlContext {} 45 | 46 | const StyledContainer = withProvider(styled("div"), "container"); 47 | 48 | /** 49 | * FormControl provides context such as 50 | * `isInvalid`, `isDisabled`, and `isRequired` to form elements. 51 | * 52 | * This is commonly used in form elements such as `input`, 53 | * `select`, `textarea`, etc. 54 | * 55 | * @see Docs https://chakra-ui.com/docs/components/form-control 56 | */ 57 | const FormControlContainer = forwardRef<"div", FormControlProps>( 58 | function FormControl(props, ref) { 59 | const { 60 | getRootProps, 61 | htmlProps: _, 62 | ...context 63 | } = useFormControlProvider(props); 64 | 65 | return ( 66 | 67 | 68 | 69 | ); 70 | } 71 | ); 72 | FormControlContainer.displayName = "FormControl"; 73 | 74 | // 75 | 76 | export interface FormLabelProps extends HTMLStyledProps<"label"> {} 77 | const StyledLabel = withContext(styled("label"), "label"); 78 | 79 | // Styles taken from `packages/preset-chakra/src/recipes/form-control.recipe.ts` 80 | // because there's no recipe extension yet 81 | // TODO recipe extension 82 | export const FormLabel = styled(StyledLabel, { 83 | base: { 84 | display: "block", 85 | textAlign: "start", 86 | // 87 | fontSize: "md", 88 | marginEnd: "3", 89 | mb: "2", 90 | fontWeight: "medium", 91 | transitionProperty: "common", 92 | transitionDuration: "normal", 93 | opacity: 1, 94 | _disabled: { 95 | opacity: 0.4, 96 | }, 97 | }, 98 | }); 99 | 100 | /** 101 | * Used to enhance the usability of form controls. 102 | * 103 | * It is used to inform users as to what information 104 | * is requested for a form field. 105 | * 106 | * ♿️ Accessibility: Every form field should have a form label. 107 | */ 108 | const FormControlLabel = createFormContextElement( 109 | StyledLabel, 110 | "FormLabel", 111 | "getLabelProps" 112 | ); 113 | 114 | // 115 | 116 | export interface FormRequiredIndicatorProps extends HTMLStyledProps<"span"> {} 117 | const StyledRequiredIndicator = withContext( 118 | styled("span"), 119 | "required-indicator" 120 | ); 121 | 122 | /** 123 | * Used to enhance the usability of form controls. 124 | * 125 | * It is used to inform users as to what information 126 | * is requested for a form field. 127 | * 128 | * ♿️ Accessibility: Every form field should have a form requiredIndicator. 129 | */ 130 | const FormRequiredIndicator = createFormContextElement( 131 | StyledRequiredIndicator, 132 | "FormRequiredIndicator", 133 | "getRequiredIndicatorProps", 134 | (field) => field?.isRequired 135 | ); 136 | 137 | // 138 | 139 | export interface FormHelperTextProps extends HTMLStyledProps<"div"> {} 140 | const StyledHelperText = withContext(styled("div"), "helper"); 141 | 142 | /** 143 | * FormHelperText 144 | * 145 | * Assistive component that conveys additional guidance 146 | * about the field, such as how it will be used and what 147 | * types in values should be provided. 148 | */ 149 | const FormHelperText = createFormContextElement( 150 | StyledHelperText, 151 | "FormHelperText", 152 | "getHelpTextProps" 153 | ); 154 | // const FormHelperText = StyledHelperText; 155 | 156 | // 157 | 158 | export interface FormErrorProps extends HTMLStyledProps<"div"> {} 159 | const StyledError = withContext(styled("div"), "error"); 160 | 161 | /** 162 | * FormError 163 | * 164 | * Assistive component that conveys additional guidance 165 | * about the field, such as how it will be used and what 166 | * types in values should be provided. 167 | */ 168 | const FormError = createFormContextElement( 169 | StyledError, 170 | "FormError", 171 | "getErrorMessageProps", 172 | (field) => field?.isInvalid 173 | ); 174 | 175 | // 176 | 177 | const ErrorIcon = () => ( 178 | 182 | ); 183 | 184 | export interface FormErrorIconProps extends HTMLStyledProps {} 185 | const StyledErrorIcon = withContext(Icon, "error-icon"); 186 | 187 | /** 188 | * FormErrorIcon 189 | * 190 | * Assistive component that conveys additional guidance 191 | * about the field, such as how it will be used and what 192 | * types in values should be provided. 193 | */ 194 | const FormErrorIcon = forwardRef( 195 | function FormErrorIcon(props, ref) { 196 | const field = useFormControlContext(); 197 | if (!field?.isInvalid) return null; 198 | 199 | return ( 200 | 201 | 202 | 203 | ); 204 | } 205 | ); 206 | FormErrorIcon.displayName = "FormErrorIcon"; 207 | 208 | // 209 | 210 | export const FormControl = Object.assign(FormControlContainer, { 211 | Container: FormControlContainer, 212 | Label: FormControlLabel, 213 | Helper: FormHelperText, 214 | RequiredIndicator: FormRequiredIndicator, 215 | Error: FormError, 216 | ErrorIcon: FormErrorIcon, 217 | }); 218 | -------------------------------------------------------------------------------- /packages/components/src/ui/heading.tsx: -------------------------------------------------------------------------------- 1 | import { styled } from "@crepe-ui/styled-system/jsx"; 2 | import { heading } from "@crepe-ui/styled-system/recipes"; 3 | 4 | // https://github.com/chakra-ui/chakra-ui/blob/f4b1ad66be1ada4b2728faef4c68a82a76f02532/packages/theme/src/components/heading.ts 5 | 6 | export const Heading = styled("h2", heading); 7 | -------------------------------------------------------------------------------- /packages/components/src/ui/icon.tsx: -------------------------------------------------------------------------------- 1 | import { ark } from "@ark-ui/react"; 2 | import { styled } from "@crepe-ui/styled-system/jsx"; 3 | import { icon } from "@crepe-ui/styled-system/recipes"; 4 | import { forwardRef } from "react"; 5 | import { Button, ButtonProps } from "./button"; 6 | 7 | export const Icon = styled(ark.svg, icon, { 8 | defaultProps: { 9 | // TODO this one seems to cause issues for some icons 10 | // viewBox: '0 0 24 24', 11 | role: "presentation", 12 | "aria-hidden": true, 13 | focusable: false, 14 | }, 15 | }); 16 | 17 | export const IconButton = forwardRef( 18 | (props, ref) => { 19 | return 47 | ); 48 | })} 49 | 50 | 51 | ); 52 | })} 53 | 54 | ); 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /storybook/stories/OverridenToken.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from "@storybook/react"; 2 | import { css } from "@crepe-ui/styled-system/css"; 3 | import { PropsWithChildren } from "react"; 4 | 5 | const ButtonWithOverridenToken = ({ children }: PropsWithChildren) => { 6 | return ( 7 | 19 | ); 20 | }; 21 | 22 | const meta = { 23 | title: "Example/OverridenToken", 24 | component: ButtonWithOverridenToken, 25 | tags: ["autodocs"], 26 | decorators: [ 27 | (Story) => ( 28 |

29 | 30 |
31 | ), 32 | ], 33 | } satisfies Meta; 34 | 35 | export default meta; 36 | type Story = StoryObj; 37 | 38 | export const Default: Story = { 39 | args: { 40 | children: "Hello 🐼!", 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /storybook/stories/index.css: -------------------------------------------------------------------------------- 1 | @layer reset, base, tokens, recipes, utilities; 2 | -------------------------------------------------------------------------------- /storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src", "stories"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /storybook/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /storybook/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "strictNullChecks": true, 5 | "moduleResolution": "Bundler", 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "skipDefaultLibCheck": true, 9 | "jsx": "react-jsx", 10 | "incremental": true, 11 | "tsBuildInfoFile": ".tsbuildinfo", 12 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 13 | "verbatimModuleSyntax": false, 14 | "resolveJsonModule": true, 15 | "noEmit": true 16 | }, 17 | "exclude": ["**/node_modules", "example-theme.ts", "old"] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.build.json", 3 | "compilerOptions": { 4 | "moduleResolution": "Bundler", 5 | "module": "ESNext", 6 | "customConditions": ["source"] 7 | }, 8 | "exclude": ["**/node_modules", "example-theme.ts", "old"] 9 | } 10 | --------------------------------------------------------------------------------