├── .gitignore ├── .prettierrc ├── README.md ├── apps ├── example-nextjs │ ├── .eslintrc.js │ ├── .gitignore │ ├── README.md │ ├── components │ │ ├── billing.css.ts │ │ └── billing.tsx │ ├── next-env.d.ts │ ├── next.config.js │ ├── package.json │ ├── pages │ │ ├── _app.tsx │ │ ├── _document.tsx │ │ ├── index.spec.tsx │ │ └── index.tsx │ └── tsconfig.json └── example-vite │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── src │ ├── App.tsx │ ├── billing.css.ts │ ├── billing.tsx │ ├── main.tsx │ └── vite-env.d.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── package.json ├── packages ├── eslint-config │ ├── base.js │ ├── next.js │ └── package.json ├── tsconfig │ ├── README.md │ ├── base.json │ ├── nextjs.json │ ├── package.json │ └── react-library.json └── ui │ ├── .eslintrc.js │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ ├── docs │ │ ├── intro.stories.mdx │ │ └── scope.stories.mdx │ ├── foundation │ │ ├── Box │ │ │ ├── Box.stories.css.ts │ │ │ ├── Box.stories.tsx │ │ │ └── Box.tsx │ │ ├── Typography │ │ │ ├── Typography.stories.tsx │ │ │ ├── Typography.tsx │ │ │ ├── index.ts │ │ │ └── styles.css.ts │ │ └── index.ts │ ├── index.ts │ ├── inputs │ │ ├── Button │ │ │ ├── Button.stories.tsx │ │ │ ├── Button.tsx │ │ │ ├── index.ts │ │ │ └── styles.css.ts │ │ ├── Range │ │ │ ├── Range.stories.tsx │ │ │ ├── Range.tsx │ │ │ └── styles.css.ts │ │ ├── ToggleButton │ │ │ ├── ToggleButton.stories.tsx │ │ │ ├── ToggleButton.tsx │ │ │ ├── index.ts │ │ │ └── styles.css.ts │ │ └── index.ts │ ├── layout │ │ ├── Table │ │ │ ├── Table.stories.tsx │ │ │ ├── Table.tsx │ │ │ ├── index.ts │ │ │ └── styles.css.ts │ │ └── index.ts │ ├── theme │ │ ├── color-util.ts │ │ ├── color.stories.mdx │ │ ├── color.test.ts │ │ ├── color.ts │ │ ├── index.ts │ │ ├── scale.ts │ │ ├── scrollbar.ts │ │ ├── space.ts │ │ ├── sprinkles.css.ts │ │ ├── theme.css.ts │ │ └── tokens.json │ └── util │ │ ├── functional.ts │ │ ├── math.test.ts │ │ ├── math.ts │ │ └── styled.ts │ └── tsconfig.json ├── turbo.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | .turbo 5 | .next 6 | *.tsbuildinfo -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "singleQuote": true, 4 | "semi": true, 5 | "useTabs": false, 6 | "arrowParens": "avoid", 7 | "trailingComma": "es5", 8 | "printWidth": 100 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vanilla-extract-rollup-example 2 | 3 | This is an example for 4 | 5 | - a component library using Vanilla Extract (built using the [Rollup Plugin](https://vanilla-extract.style/documentation/setup/#rollup)) 6 | - two example apps consuming the component library (Vite and Next.js) 7 | 8 | ## Setup 9 | 10 | yarn 11 | 12 | ## Run 13 | 14 | yarn dev 15 | 16 | This uses Turborepo to first build the UI package and then launch the two example apps. 17 | 18 | You can also run build / dev commands in their respective directories manually without using Turborepo. -------------------------------------------------------------------------------- /apps/example-nextjs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@vanilla-extract-rollup-example/eslint-config/next.js'); 2 | -------------------------------------------------------------------------------- /apps/example-nextjs/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env.local 28 | .env.development.local 29 | .env.test.local 30 | .env.production.local 31 | -------------------------------------------------------------------------------- /apps/example-nextjs/README.md: -------------------------------------------------------------------------------- 1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn). -------------------------------------------------------------------------------- /apps/example-nextjs/components/billing.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@vanilla-extract/css'; 2 | 3 | export const pageContainer = style({ 4 | maxWidth: 840, 5 | }); 6 | 7 | export const card = style([ 8 | { 9 | boxShadow: 'rgb(0 0 0 / 10%) 2px 4px 10px', 10 | borderRadius: '2px', 11 | }, 12 | ]); 13 | 14 | export const usageGrid = style([ 15 | { 16 | gridTemplateColumns: '1fr 1fr 1fr', 17 | display: 'grid', 18 | gap: '24px', 19 | }, 20 | ]); -------------------------------------------------------------------------------- /apps/example-nextjs/components/billing.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { ComponentProps } from 'react'; 3 | 4 | import { Body, Box, Button, Caption, Cell, Row, styled, Table, Typography } from '@vanilla-extract-rollup-example/ui'; 5 | 6 | import { card, pageContainer, usageGrid } from './billing.css'; 7 | 8 | const Card = styled(Box, card); 9 | Card.displayName = 'Card'; 10 | const UsageGrid = styled(Box, usageGrid); 11 | UsageGrid.displayName = 'UsageGrid'; 12 | 13 | function LabelValue({ label, value, valueVariant = 'm-regular' }: { label: string; value: string; valueVariant?: ComponentProps['variant'] }) { 14 | return 15 | {label} 16 | {value} 17 | ; 18 | } 19 | 20 | function InvoiceTable() { 21 | return 22 | 23 | 24 | 25 | Not Paid 26 | Oct 14, 2020 ~ Oct 14, 2020 27 | $ 125,000 28 | 29 | 30 | 31 | Not Paid 32 | Oct 14, 2020 ~ Oct 14, 2020 33 | $ 125,000 34 | 35 | 36 | 37 |
Invoices
; 38 | } 39 | 40 | export function BillingPage() { 41 | return 42 | 43 | 44 | 45 | Enterprise 46 | {' '}Plan 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ; 74 | } -------------------------------------------------------------------------------- /apps/example-nextjs/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /apps/example-nextjs/next.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-var-requires */ 2 | const { createVanillaExtractPlugin } = require('@vanilla-extract/next-plugin'); 3 | const withVanillaExtract = createVanillaExtractPlugin(); 4 | const withPlugins = require('next-compose-plugins'); 5 | const withTM = require('next-transpile-modules')(['@vanilla-extract-rollup-example/ui']); 6 | 7 | /** @type {import('next').NextConfig} */ 8 | const nextConfig = { 9 | reactStrictMode: true, 10 | }; 11 | 12 | module.exports = withPlugins([withVanillaExtract, withTM], nextConfig); 13 | -------------------------------------------------------------------------------- /apps/example-nextjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanilla-extract-rollup-example/example-nextjs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "private": true, 6 | "scripts": { 7 | "dev": "next dev --port 3101", 8 | "build": "next build", 9 | "start": "next start", 10 | "lint": "eslint './**/*.{ts,tsx}'", 11 | "test": "tsc && jest" 12 | }, 13 | "dependencies": { 14 | "@vanilla-extract-rollup-example/ui": "*", 15 | "@vanilla-extract/css": "^1.6.3", 16 | "next": "12.1.0", 17 | "react": "17.0.2", 18 | "react-dom": "17.0.2" 19 | }, 20 | "devDependencies": { 21 | "@vanilla-extract-rollup-example/eslint-config": "*", 22 | "@vanilla-extract-rollup-example/tsconfig": "*", 23 | "@testing-library/jest-dom": "^5.16.2", 24 | "@testing-library/react": "^12.1.3", 25 | "@types/react": "^17.0.14", 26 | "@types/react-dom": "^17.0.9", 27 | "@vanilla-extract/babel-plugin": "^1.1.4", 28 | "@vanilla-extract/next-plugin": "^2.0.1", 29 | "next-compose-plugins": "^2.2.1", 30 | "next-transpile-modules": "^9.0.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /apps/example-nextjs/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import { themeClass } from '@vanilla-extract-rollup-example/ui'; 2 | import { AppProps } from 'next/app'; 3 | 4 | function MyApp({ Component, pageProps }: AppProps) { 5 | return
; 6 | } 7 | 8 | export default MyApp; 9 | -------------------------------------------------------------------------------- /apps/example-nextjs/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import Document, { Head, Html, Main, NextScript } from 'next/document'; 2 | 3 | class MyDocument extends Document { 4 | static async getInitialProps(ctx) { 5 | const initialProps = await Document.getInitialProps(ctx); 6 | return { ...initialProps }; 7 | } 8 | 9 | render() { 10 | return ( 11 | 12 | 13 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | ); 24 | } 25 | } 26 | 27 | export default MyDocument; -------------------------------------------------------------------------------- /apps/example-nextjs/pages/index.spec.tsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | 3 | import Home from './index'; 4 | 5 | describe('Home', () => { 6 | it('renders a heading', () => { 7 | render(); 8 | 9 | const button = screen.getByRole('button', { 10 | name: 'Change Plan', 11 | }); 12 | 13 | expect(button).toBeInTheDocument(); 14 | }); 15 | }); -------------------------------------------------------------------------------- /apps/example-nextjs/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | 3 | import { BillingPage } from '../components/billing'; 4 | 5 | export default function Home() { 6 | return ( 7 |
8 | 9 | Next.js App 10 | 11 | 12 | 13 |
14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /apps/example-nextjs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo" 4 | }, 5 | "types": ["node", "jest", "@testing-library/jest-dom"], 6 | "extends": "@vanilla-extract-rollup-example/tsconfig/nextjs.json", 7 | "include": [ 8 | "next-env.d.ts", 9 | "**/*.ts", 10 | "**/*.tsx" 11 | ], 12 | } 13 | -------------------------------------------------------------------------------- /apps/example-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /apps/example-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /apps/example-vite/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanilla-extract-rollup-example/example-vite", 3 | "private": true, 4 | "version": "0.0.0", 5 | "scripts": { 6 | "dev": "vite --port 3100", 7 | "build": "tsc && vite build", 8 | "preview": "vite preview" 9 | }, 10 | "dependencies": { 11 | "@vanilla-extract-rollup-example/ui": "*", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2" 14 | }, 15 | "devDependencies": { 16 | "@types/react": "^17.0.33", 17 | "@types/react-dom": "^17.0.10", 18 | "@vanilla-extract/vite-plugin": "^3.1.4", 19 | "@vitejs/plugin-react": "^1.0.7", 20 | "typescript": "^4.5.4", 21 | "vite": "^2.8.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /apps/example-vite/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { themeClass } from '@vanilla-extract-rollup-example/ui'; 2 | import { BillingPage } from './billing'; 3 | 4 | function App() { 5 | return ( 6 |
7 | 8 | 9 | ) 10 | } 11 | 12 | export default App 13 | -------------------------------------------------------------------------------- /apps/example-vite/src/billing.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@vanilla-extract/css'; 2 | 3 | export const pageContainer = style({ 4 | maxWidth: 840, 5 | }); 6 | 7 | export const card = style([ 8 | { 9 | boxShadow: 'rgb(0 0 0 / 10%) 2px 4px 10px', 10 | borderRadius: '2px', 11 | }, 12 | ]); 13 | 14 | export const usageGrid = style([ 15 | { 16 | gridTemplateColumns: '1fr 1fr 1fr', 17 | display: 'grid', 18 | gap: '24px', 19 | }, 20 | ]); -------------------------------------------------------------------------------- /apps/example-vite/src/billing.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { ComponentProps } from 'react'; 3 | 4 | import { Body, Box, Button, Caption, Cell, Row, styled, Table, Typography } from '@vanilla-extract-rollup-example/ui'; 5 | 6 | import { card, pageContainer, usageGrid } from './billing.css'; 7 | 8 | const Card = styled(Box, card); 9 | Card.displayName = 'Card'; 10 | const UsageGrid = styled(Box, usageGrid); 11 | UsageGrid.displayName = 'UsageGrid'; 12 | 13 | function LabelValue({ label, value, valueVariant = 'm-regular' }: { label: string; value: string; valueVariant?: ComponentProps['variant'] }) { 14 | return 15 | {label} 16 | {value} 17 | ; 18 | } 19 | 20 | function InvoiceTable() { 21 | return 22 | 23 | 24 | 25 | Not Paid 26 | Oct 14, 2020 ~ Oct 14, 2020 27 | $ 125,000 28 | 29 | 30 | 31 | Not Paid 32 | Oct 14, 2020 ~ Oct 14, 2020 33 | $ 125,000 34 | 35 | 36 | 37 |
Invoices
; 38 | } 39 | 40 | export function BillingPage() { 41 | return 42 | 43 | 44 | 45 | Enterprise 46 | {' '}Plan 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ; 74 | } -------------------------------------------------------------------------------- /apps/example-vite/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import App from './App' 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ) 11 | -------------------------------------------------------------------------------- /apps/example-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /apps/example-vite/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "useDefineForClassFields": true, 5 | "lib": ["DOM", "DOM.Iterable", "ESNext"], 6 | "allowJs": false, 7 | "skipLibCheck": false, 8 | "esModuleInterop": false, 9 | "allowSyntheticDefaultImports": true, 10 | "strict": true, 11 | "forceConsistentCasingInFileNames": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Node", 14 | "resolveJsonModule": true, 15 | "isolatedModules": true, 16 | "noEmit": true, 17 | "jsx": "react-jsx" 18 | }, 19 | "include": ["src"], 20 | "references": [{ "path": "./tsconfig.node.json" }] 21 | } 22 | -------------------------------------------------------------------------------- /apps/example-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "module": "esnext", 5 | "moduleResolution": "node" 6 | }, 7 | "include": ["vite.config.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /apps/example-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import { vanillaExtractPlugin } from '@vanilla-extract/vite-plugin' 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react(), vanillaExtractPlugin()] 8 | }) 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vanilla-extract-rollup-example", 3 | "version": "1.0.0", 4 | "private": true, 5 | "workspaces": [ 6 | "packages/*", 7 | "apps/*" 8 | ], 9 | "scripts": { 10 | "build": "turbo run build", 11 | "dev": "turbo run dev", 12 | "test": "turbo run test", 13 | "lint": "turbo run lint" 14 | }, 15 | "devDependencies": { 16 | "turbo": "^1.2.6" 17 | }, 18 | "packageManager": "yarn@1.22.17" 19 | } 20 | -------------------------------------------------------------------------------- /packages/eslint-config/base.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | env: { 4 | browser: true, 5 | es6: true, 6 | }, 7 | extends: [ 8 | 'plugin:@typescript-eslint/eslint-recommended', 9 | 'plugin:@typescript-eslint/recommended', 10 | 'plugin:import/errors', 11 | 'plugin:import/warnings', 12 | 'plugin:import/typescript', 13 | 'plugin:react/recommended', 14 | 'plugin:react-hooks/recommended', 15 | 'plugin:react/jsx-runtime', 16 | ], 17 | parserOptions: { 18 | ecmaVersion: 2018, 19 | ecmaFeatures: { 20 | jsx: true, 21 | modules: true, 22 | experimentalObjectRestSpread: true, 23 | }, 24 | }, 25 | plugins: [ 26 | '@typescript-eslint', 27 | 'react', 28 | 'react-hooks', 29 | 'unused-imports', 30 | 'simple-import-sort', 31 | ], 32 | settings: { 33 | react: { 34 | version: 'detect', 35 | }, 36 | }, 37 | rules: { 38 | 'comma-spacing': 'error', 39 | 'object-curly-spacing': ['error', 'always'], 40 | 'no-trailing-spaces': 'error', 41 | quotes: ['error', 'single'], 42 | semi: 'off', 43 | '@typescript-eslint/semi': ['error', 'always'], 44 | 'comma-dangle': ['error', 'always-multiline'], 45 | 'react/jsx-boolean-value': 'error', 46 | 'import/no-useless-path-segments': 'error', 47 | 'import/namespace': 'off', 48 | 'import/default': 'off', 49 | 'import/no-named-as-default': 'off', 50 | 'import/no-named-as-default-member': 'off', 51 | '@typescript-eslint/ban-ts-comment': [ 52 | 'error', 53 | { 54 | 'ts-ignore': 'allow-with-description', 55 | 'ts-nocheck': false, 56 | }, 57 | ], 58 | '@typescript-eslint/no-empty-function': 'warn', 59 | '@typescript-eslint/indent': [ 60 | 'error', 61 | 2, 62 | { 63 | ignoredNodes: [ 64 | 'TSTypeAliasDeclaration', 65 | 'TSTypeReference *', 66 | 'TSUnionType', 67 | 'TemplateLiteral', 68 | 'TemplateLiteral *', 69 | 'TSTypeParameterInstantiation', 70 | ], 71 | SwitchCase: 1, 72 | }, 73 | ], 74 | '@typescript-eslint/member-delimiter-style': [ 75 | 'error', 76 | { 77 | multiline: { 78 | delimiter: 'semi', 79 | requireLast: true, 80 | }, 81 | singleline: { 82 | delimiter: 'semi', 83 | requireLast: false, 84 | }, 85 | }, 86 | ], 87 | '@typescript-eslint/no-unused-vars': 'off', 88 | 'unused-imports/no-unused-vars-ts': [ 89 | 'warn', 90 | { 91 | vars: 'all', 92 | varsIgnorePattern: '^_', 93 | args: 'after-used', 94 | argsIgnorePattern: '^_', 95 | }, 96 | ], 97 | 'unused-imports/no-unused-imports-ts': 'warn', 98 | 'simple-import-sort/imports': [ 99 | 'warn', 100 | { 101 | groups: [ 102 | ['^\\u0000'], 103 | ['^react', '^reakit'], 104 | ['^@?\\w'], 105 | ['^'], 106 | ['^\\.'], 107 | ], 108 | }, 109 | ], 110 | 'simple-import-sort/exports': 'warn', 111 | }, 112 | }; 113 | -------------------------------------------------------------------------------- /packages/eslint-config/next.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@vanilla-extract-rollup-example/eslint-config/base', 'next'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/eslint-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanilla-extract-rollup-example/eslint-config", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "@typescript-eslint/eslint-plugin": "^5.4.0", 6 | "@typescript-eslint/parser": "^5.4.0", 7 | "eslint-config-next": "^12.0.8", 8 | "eslint-plugin-import": "^2.25.3", 9 | "eslint-plugin-react": "^7.27.1", 10 | "eslint-plugin-react-hooks": "^4.3.0", 11 | "eslint-plugin-simple-import-sort": "^7.0.0", 12 | "eslint-plugin-unused-imports": "^2.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/tsconfig/README.md: -------------------------------------------------------------------------------- 1 | # `tsconfig` 2 | 3 | These are base shared `tsconfig.json`s from which all other `tsconfig.json`'s inherit from. 4 | -------------------------------------------------------------------------------- /packages/tsconfig/base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Default", 4 | "compilerOptions": { 5 | "composite": false, 6 | "declaration": true, 7 | "declarationMap": true, 8 | "esModuleInterop": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "incremental": true, 11 | "inlineSources": false, 12 | "isolatedModules": true, 13 | "moduleResolution": "node", 14 | "noEmit": true, 15 | "noUnusedLocals": false, 16 | "noUnusedParameters": false, 17 | "preserveWatchOutput": true, 18 | "resolveJsonModule": true, 19 | "skipLibCheck": true, 20 | "strict": true 21 | }, 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /packages/tsconfig/nextjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "Next.js", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": false, 11 | "forceConsistentCasingInFileNames": true, 12 | "noEmit": true, 13 | "incremental": true, 14 | "esModuleInterop": true, 15 | "module": "esnext", 16 | "isolatedModules": true, 17 | "jsx": "preserve" 18 | }, 19 | "include": ["src", "next-env.d.ts"], 20 | "exclude": ["node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/tsconfig/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanilla-extract-rollup-example/tsconfig", 3 | "version": "0.0.0", 4 | "private": true, 5 | "main": "index.js", 6 | "files": [ 7 | "base.json", 8 | "nextjs.json", 9 | "react-library.json" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/tsconfig/react-library.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "display": "React Library", 4 | "extends": "./base.json", 5 | "compilerOptions": { 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "module": "ESNext", 8 | "target": "ES6", 9 | "jsx": "preserve" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/ui/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@vanilla-extract-rollup-example/eslint-config/base.js'); 2 | -------------------------------------------------------------------------------- /packages/ui/README.md: -------------------------------------------------------------------------------- 1 | # UI Library 2 | 3 | ## Build 4 | 5 | yarn build 6 | -------------------------------------------------------------------------------- /packages/ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@vanilla-extract-rollup-example/ui", 3 | "version": "1.0.0", 4 | "description": "", 5 | "scripts": { 6 | "build": "rollup -c", 7 | "build:prod": "NODE_ENV=production rollup -c", 8 | "lint": "eslint './src/**/*.{ts,tsx}'", 9 | "lint:fix": "yarn lint --fix", 10 | "test": "tsc && jest" 11 | }, 12 | "main": "dist/index.js", 13 | "module": "dist/index.js", 14 | "types": "dist/index.d.ts", 15 | "author": "Paul Grau", 16 | "license": "MIT", 17 | "dependencies": { 18 | "@capsizecss/core": "^3.0.0", 19 | "@capsizecss/metrics": "^0.1.0", 20 | "@dessert-box/react": "^0.2.0", 21 | "@react-aria/utils": "^3.10.0", 22 | "@vanilla-extract/css": "^1.7.0", 23 | "@vanilla-extract/dynamic": "^2.0.2", 24 | "@vanilla-extract/recipes": "^0.2.4", 25 | "@vanilla-extract/sprinkles": "^1.4.0", 26 | "reakit": "^1.3.11" 27 | }, 28 | "peerDependencies": { 29 | "react": "^17.0.2", 30 | "react-dom": "^17.0.2" 31 | }, 32 | "devDependencies": { 33 | "@babel/preset-env": "^7.14.7", 34 | "@babel/preset-react": "^7.14.5", 35 | "@babel/preset-typescript": "^7.14.5", 36 | "@rollup/plugin-json": "^4.1.0", 37 | "@vanilla-extract-rollup-example/eslint-config": "*", 38 | "@vanilla-extract-rollup-example/tsconfig": "*", 39 | "@types/jest": "^27.0.3", 40 | "@types/react": "^17.0.14", 41 | "@types/react-dom": "^17.0.9", 42 | "@vanilla-extract/rollup-plugin": "^1.0.1", 43 | "eslint": "^8.3.0", 44 | "jest": "^27.4.5", 45 | "react": "^17.0.2", 46 | "react-dom": "^17.0.2", 47 | "rollup": "^2.70.1", 48 | "rollup-plugin-dts": "^4.2.0", 49 | "rollup-plugin-esbuild": "^4.8.2", 50 | "rollup-plugin-node-externals": "^4.0.0", 51 | "typescript": "^4.3.5" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /packages/ui/rollup.config.js: -------------------------------------------------------------------------------- 1 | import json from '@rollup/plugin-json'; 2 | import { vanillaExtractPlugin } from '@vanilla-extract/rollup-plugin'; 3 | import path from 'path'; 4 | import dts from 'rollup-plugin-dts'; 5 | import esbuild from 'rollup-plugin-esbuild'; 6 | import depsExternal from 'rollup-plugin-node-externals'; 7 | import ts from 'typescript'; 8 | 9 | const loadCompilerOptions = tsconfig => { 10 | if (!tsconfig) return {}; 11 | const configFile = ts.readConfigFile(tsconfig, ts.sys.readFile); 12 | const { options } = ts.parseJsonConfigFileContent(configFile.config, ts.sys, './'); 13 | return options; 14 | }; 15 | 16 | const compilerOptions = loadCompilerOptions('tsconfig.json'); 17 | 18 | const plugins = [vanillaExtractPlugin(), depsExternal(), esbuild(), json()]; 19 | 20 | export default [ 21 | { 22 | input: 'src/index.ts', 23 | plugins, 24 | output: [ 25 | { 26 | dir: 'dist', 27 | format: 'esm', 28 | preserveModules: true, 29 | preserveModulesRoot: 'src', 30 | 31 | // Change .css.js files to something else so that they don't get re-processed by consumer's setup 32 | entryFileNames({ name }) { 33 | return `${name.replace(/\.css$/, '.css.vanilla')}.js`; 34 | }, 35 | 36 | // Apply preserveModulesRoot to asset names 37 | assetFileNames({ name }) { 38 | return name.replace(/^src\//, ''); 39 | }, 40 | 41 | exports: 'named', 42 | }, 43 | ], 44 | }, 45 | // Declaration files 46 | { 47 | input: 'src/index.ts', 48 | plugins: [ 49 | ...plugins, 50 | dts({ 51 | compilerOptions: { 52 | ...compilerOptions, 53 | baseUrl: path.resolve(compilerOptions.baseUrl || '.'), 54 | declaration: true, 55 | noEmit: false, 56 | emitDeclarationOnly: true, 57 | noEmitOnError: true, 58 | target: ts.ScriptTarget.ESNext, 59 | }, 60 | }), 61 | ], 62 | output: [ 63 | { 64 | dir: 'dist', 65 | format: 'esm', 66 | preserveModules: true, 67 | preserveModulesRoot: 'src', 68 | }, 69 | ], 70 | }, 71 | ]; 72 | -------------------------------------------------------------------------------- /packages/ui/src/docs/intro.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 4 | 5 | 6 | 7 | # Welcome 8 | 9 | This is a sample SCSS code block example highlighted in Storybook 10 | 11 | ```scss 12 | $font-stack: Helvetica, sans-serif; 13 | $primary-color: #333; 14 | 15 | body { 16 | font: 100% $font-stack; 17 | color: $primary-color; 18 | } 19 | ``` 20 | 21 | export const Component = () => { 22 | return ; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ui/src/docs/scope.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta } from '@storybook/addon-docs'; 2 | 3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; 4 | 5 | 6 | 7 | # Scope 8 | 9 | This article describes the scope and organization of the design system. 10 | -------------------------------------------------------------------------------- /packages/ui/src/foundation/Box/Box.stories.css.ts: -------------------------------------------------------------------------------- 1 | import { style } from '@vanilla-extract/css'; 2 | import { recipe } from '@vanilla-extract/recipes'; 3 | 4 | import { atoms, vars } from '../../theme'; 5 | 6 | /** 7 | * Example for using a custom recipe, 8 | * combination of theme tokens and custom styles. 9 | */ 10 | export const overloadRecipe = recipe({ 11 | base: [ 12 | atoms({ 13 | display: 'flex', 14 | justifyContent: 'center', 15 | padding: 'medium', 16 | backgroundColor: { 17 | default: 'gray-060', 18 | hover: 'gray-100', 19 | }, 20 | }), 21 | { 22 | position: 'relative', 23 | left: vars.space[2], 24 | cursor: 'pointer', 25 | }, 26 | ], 27 | }); 28 | 29 | /** 30 | * Example for totally manualy style overload 31 | * using theme tokens for undefined properties. 32 | */ 33 | export const overloadStyle = style({ 34 | position: 'relative', 35 | left: vars.space[2], 36 | }); 37 | 38 | -------------------------------------------------------------------------------- /packages/ui/src/foundation/Box/Box.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 2 | 3 | import { atoms } from '../../theme'; 4 | import { Box } from './Box'; 5 | import { overloadRecipe, overloadStyle } from './Box.stories.css'; 6 | 7 | const props = Array.from(atoms.properties); 8 | 9 | // Set controls 10 | const argTypes = props.reduce( 11 | (all, a) => { all[a] = { table: { category: 'atoms' } }; return all; }, 12 | {} as Record<(typeof props)[number], unknown>, 13 | ); 14 | 15 | export default { 16 | title: 'Foundation/Box', 17 | component: Box, 18 | argTypes, 19 | parameters: { 20 | controls: { 21 | // Let's hide the non-atoms props since those are rarely useful in Storybook 22 | include: props, 23 | }, 24 | }, 25 | } as ComponentMeta; 26 | 27 | export const WithArgs: ComponentStory = (args) => Welcome to the box! Box can be used to abstract certain often-used style properties.; 28 | 29 | export const BasicUsage = () => Hello, world!; 30 | 31 | export const StyleWithConditions = () => Some atomic styles can be defined conditionally, e.g. for hover effects.; 34 | 35 | /** 36 | * Combining sprinkles and a recipe can be useful. 37 | */ 38 | export const StyleWithRecipe = () => { 39 | const className = overloadRecipe(); 40 | return For biggest flexibility, define a recipe.; 41 | }; 42 | 43 | /** 44 | * Using sprinkles like this is unnecessary for Box, you can just pass the properties to the Box component. 45 | * But this can be a useful example for other components. 46 | */ 47 | export const StyleWithAtoms = () => { 48 | const className = atoms({ 49 | display: 'flex', 50 | justifyContent: 'center', 51 | padding: 'medium', // this overrides the Box prop 52 | backgroundColor: { 53 | hover: 'blue-050', // this overrides the Box prop on hover 54 | }, 55 | }); 56 | return Use atoms directly.; 57 | }; 58 | 59 | /** 60 | * Setting custom styles manually is also possible. 61 | */ 62 | export const StyleCustom = () => { 63 | return Define custom styles.; 64 | }; 65 | 66 | export const Flex = () => { 67 | // Non-static properties like width are not part of the atomic styles. You can either define them 68 | // using style() in a style.css.ts file or use the inline style escape hatch like here. 69 | return 70 | Left 71 | Right 72 | ; 73 | }; 74 | 75 | export const Grid = () => { 76 | return 77 | 1 78 | 2 79 | 3 80 | 4 81 | 5 82 | 6 83 | ; 84 | }; 85 | 86 | export const GridCenter = () => { 87 | return 88 | The easiest way to center both horizontally and vertically! 89 | ; 90 | }; 91 | 92 | export const ConditionHover = () => { 93 | return 94 | Hover me 95 | ; 96 | }; 97 | 98 | export const ConditionFocusWithin = () => { 99 | return 100 | 101 | ; 102 | }; 103 | 104 | export const Scroll = () => { 105 | return 106 | 107 | Scroll me. Notice how the scrollbar appears outside of the box content. 108 | 109 | ; 110 | }; 111 | 112 | export const ScrollOverlay = () => { 113 | return 114 | 115 | Scroll me. Notice how the scrollbar overlays. 116 | 117 | ; 118 | }; 119 | 120 | export const Sticky = () => { 121 | return 122 | 123 | Scroll me 124 | 125 | 126 | ; 127 | }; -------------------------------------------------------------------------------- /packages/ui/src/foundation/Box/Box.tsx: -------------------------------------------------------------------------------- 1 | import { createBox } from '@dessert-box/react'; 2 | 3 | import { atoms } from '../../theme/sprinkles.css'; 4 | 5 | /** 6 | * Box is a basic component that gives access to atomic styles (Sprinkles). 7 | */ 8 | export const Box = createBox({ atoms }); 9 | Box.displayName = 'Box'; -------------------------------------------------------------------------------- /packages/ui/src/foundation/Typography/Typography.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 2 | 3 | import { colors, namedColors } from '../../theme/color-util'; 4 | import { Box } from '../Box/Box'; 5 | import { Paragraph, Typography } from './Typography'; 6 | 7 | export default { 8 | title: 'Foundation/Typography', 9 | component: Typography, 10 | parameters: { 11 | controls: { 12 | include: ['variant', 'color'], 13 | }, 14 | }, 15 | argTypes: { 16 | color: { 17 | control: { 18 | type: 'select', 19 | options: [...namedColors, ...colors], 20 | }, 21 | }, 22 | }, 23 | } as ComponentMeta; 24 | 25 | export const WithArgs: ComponentStory = (args) => { 26 | return Automate data preparation.
Deliver better datasets.
; 27 | }; 28 | 29 | WithArgs.args = { 30 | color: 'red', 31 | variant: 'h1', 32 | }; 33 | 34 | export const AsParagraph = () => { 35 | return 36 | Toggle this ON to open-source this dataset to the public. Anyone can read and download data from public projects. 37 | Toggle this OFF to privately host proprietary data on this project. Private project is only accessible to users under your account. 38 | ; 39 | }; 40 | 41 | export const ParagraphWithBoxStyles = () => { 42 | return You can also use any Box style on Typography and Paragraph.; 43 | }; 44 | -------------------------------------------------------------------------------- /packages/ui/src/foundation/Typography/Typography.tsx: -------------------------------------------------------------------------------- 1 | import React, { forwardRef } from 'react'; 2 | 3 | import { mergeProps } from '@react-aria/utils'; 4 | 5 | import { Box } from '../Box/Box'; 6 | import { typoRecipe, Variants } from './styles.css'; 7 | 8 | type Props = React.ComponentProps & Variants; 9 | 10 | /** 11 | * Typography is a `` with a variant and support for all props from Box 12 | */ 13 | export const Typography = forwardRef(function Typography({ variant, ...props }: Props, ref) { 14 | return ; 15 | }); 16 | 17 | /** 18 | * Paragraph is a `

` with a variant and support for all props from Box 19 | */ 20 | export const Paragraph = forwardRef(function Paragraph({ variant, ...props }: Props, ref) { 21 | return ; 22 | }); -------------------------------------------------------------------------------- /packages/ui/src/foundation/Typography/index.ts: -------------------------------------------------------------------------------- 1 | export { typoVariants } from './styles.css'; 2 | export { Paragraph, Typography } from './Typography'; -------------------------------------------------------------------------------- /packages/ui/src/foundation/Typography/styles.css.ts: -------------------------------------------------------------------------------- 1 | import { createStyleObject } from '@capsizecss/core'; 2 | import fontMetrics from '@capsizecss/metrics/inter'; 3 | import { styleVariants } from '@vanilla-extract/css'; 4 | import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; 5 | 6 | /** 7 | * Calculate properties for correct leading. See https://seek-oss.github.io/capsize/ 8 | */ 9 | function capSize(fontSize: number, leading: number) { 10 | return createStyleObject({ fontSize, leading, fontMetrics }); 11 | } 12 | 13 | export const typoVariants = styleVariants({ 14 | h1: { ...capSize(24, 36), fontWeight: 600 }, 15 | h2: { ...capSize(20, 30), fontWeight: 600 }, 16 | h3: { ...capSize(16, 24), fontWeight: 600 }, 17 | 18 | 'l-strong': { fontSize: 14, lineHeight: 1.5, fontWeight: 600 }, 19 | 'l-regular': { fontSize: 14, lineHeight: 1.5, fontWeight: 400 }, 20 | 21 | 'm-strong': { fontSize: 12, lineHeight: 1.5, fontWeight: 600 }, 22 | 'm-medium': { fontSize: 12, lineHeight: 1.5, fontWeight: 500 }, 23 | 'm-regular': { fontSize: 12, lineHeight: 1.5, fontWeight: 400 }, 24 | 25 | 's-strong': { fontSize: 10, lineHeight: 1.5, fontWeight: 600 }, 26 | 's-regular': { fontSize: 10, lineHeight: 1.5, fontWeight: 400 }, 27 | }); 28 | 29 | 30 | export const typoRecipe = recipe({ 31 | base: [], 32 | variants: { 33 | variant: typoVariants, 34 | }, 35 | defaultVariants: { 36 | variant: 'm-regular', 37 | }, 38 | }); 39 | 40 | export type Variants = RecipeVariants; 41 | -------------------------------------------------------------------------------- /packages/ui/src/foundation/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Box/Box'; 2 | export * from './Typography'; -------------------------------------------------------------------------------- /packages/ui/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './foundation'; 2 | export * from './inputs'; 3 | export * from './layout'; 4 | export * from './theme'; 5 | export * from './util/styled'; -------------------------------------------------------------------------------- /packages/ui/src/inputs/Button/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 2 | 3 | import { Button } from './Button'; 4 | 5 | export default { 6 | title: 'Foundation/Inputs/Button', 7 | component: Button, 8 | } as ComponentMeta; 9 | 10 | export const WithArgs: ComponentStory = (args) => ; 11 | 12 | WithArgs.args = { 13 | color: 'red', 14 | size: 'm', 15 | variant: 'stroke', 16 | }; 17 | 18 | -------------------------------------------------------------------------------- /packages/ui/src/inputs/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Button as RKButton } from 'reakit/Button'; 3 | 4 | import { assignInlineVars } from '@vanilla-extract/dynamic'; 5 | 6 | import { getColor, NamedColor } from '../../theme'; 7 | import { styled } from '../../util/styled'; 8 | import { buttonColor, buttonRecipe, Variants } from './styles.css'; 9 | 10 | type Props = React.ComponentProps & Variants & { 11 | color?: NamedColor; 12 | }; 13 | 14 | export const Button = styled( 15 | RKButton, 16 | ({ size, variant }: Props) => buttonRecipe({ size, variant }), 17 | ({ color }: Props) => color ? assignInlineVars({ [buttonColor]: getColor(color) }) : {}, 18 | ); 19 | Button.displayName = 'Button'; -------------------------------------------------------------------------------- /packages/ui/src/inputs/Button/index.ts: -------------------------------------------------------------------------------- 1 | export { Button } from './Button'; 2 | export { buttonColor, buttonRecipe } from './styles.css'; -------------------------------------------------------------------------------- /packages/ui/src/inputs/Button/styles.css.ts: -------------------------------------------------------------------------------- 1 | import { createVar } from '@vanilla-extract/css'; 2 | import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; 3 | 4 | import { atoms, vars } from '../../theme'; 5 | import { scalePx } from '../../theme/scale'; 6 | 7 | export const buttonColor = createVar(); 8 | 9 | export const buttonRecipe = recipe({ 10 | base: [atoms({ 11 | display: 'inline-flex', 12 | }), 13 | { 14 | background: 'transparent', 15 | border: 0, 16 | borderRadius: 30, 17 | padding: '0 1em', 18 | cursor: 'pointer', 19 | ':hover': { 20 | outline: '1px solid currentColor', 21 | }, 22 | color: buttonColor, 23 | vars: { 24 | [buttonColor]: vars.color['gray'], 25 | }, 26 | }], 27 | 28 | variants: { 29 | size: { 30 | s: { lineHeight: scalePx(3), fontSize: '10px' }, 31 | m: { lineHeight: scalePx(4), fontSize: '12px' }, 32 | l: { lineHeight: scalePx(5), fontSize: '14px' }, 33 | }, 34 | variant: { 35 | stroke: { boxShadow: `inset 0 0 0 1px ${buttonColor}` }, 36 | 'strong-fill': { backgroundColor: buttonColor, color: '#fff' }, 37 | }, 38 | }, 39 | 40 | defaultVariants: { 41 | variant: 'stroke', 42 | size: 'm', 43 | }, 44 | }); 45 | 46 | export type Variants = RecipeVariants; 47 | -------------------------------------------------------------------------------- /packages/ui/src/inputs/Range/Range.stories.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 4 | 5 | import { Range } from './Range'; 6 | 7 | export default { 8 | title: 'Foundation/Inputs/Range', 9 | component: Range, 10 | parameters: { 11 | controls: { 12 | include: ['variant', 'color', 'min', 'max', 'value', 'step', 'minSymbol', 'maxSymbol'], 13 | }, 14 | }, 15 | } as ComponentMeta; 16 | 17 | export const WithArgs: ComponentStory = (args) => { 18 | const [value, setValue] = useState(args.value); 19 | const [valueSettled, setValueSettled] = useState(args.value); 20 | return <> 21 | 22 | {value} {valueSettled} 23 | ; 24 | }; 25 | 26 | WithArgs.args = { 27 | variant: undefined, 28 | color: 'red', 29 | min: 0, 30 | max: 100, 31 | value: 50, 32 | step: 1, 33 | minSymbol: '-', 34 | maxSymbol: '+', 35 | }; -------------------------------------------------------------------------------- /packages/ui/src/inputs/Range/Range.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { clamp, mergeProps } from '@react-aria/utils'; 4 | import { assignInlineVars } from '@vanilla-extract/dynamic'; 5 | 6 | import { getColor, NamedColor } from '../../theme'; 7 | import { relative } from '../../util/math'; 8 | import { currentValue, inputRecipe, selectedColor, symbol, Variants, wrap } from './styles.css'; 9 | 10 | type RangeValue = { 11 | value: number; 12 | min?: number; 13 | max?: number; 14 | }; 15 | 16 | type RangeProps = React.ComponentProps<'input'> & RangeValue & Variants & { 17 | color: NamedColor; 18 | }; 19 | 20 | type UseRangeProps = RangeValue & { 21 | // Continous updates 22 | onValueInput?: (value: number) => void; 23 | // Updates when value settled 24 | onValueChange?: (value: number) => void; 25 | }; 26 | 27 | type Props = RangeProps & UseRangeProps & { 28 | minSymbol?: React.ReactNode; 29 | maxSymbol?: React.ReactNode; 30 | step?: number; 31 | }; 32 | 33 | export function useRange({ value, min, max, onValueChange, onValueInput }: UseRangeProps) { 34 | /** 35 | * Trigger onValueInput with a numerical value for input.onChange. 36 | * Browser/React are inconsistent with regards to onChange/onInput, but let's do it correctly here. 37 | */ 38 | function onChange(e: React.ChangeEvent) { 39 | onValueInput?.(Number(e.currentTarget.value)); 40 | } 41 | 42 | /** 43 | * Trigger onValueChange with a numerical value for input.onMouseUp. 44 | */ 45 | function onMouseUp(e: React.ChangeEvent) { 46 | onValueChange?.(Number(e.currentTarget.value)); 47 | } 48 | 49 | function getStepClickHandler(change: number) { 50 | return () => { 51 | const newValue = clamp(value + change, min, max); 52 | onValueInput?.(newValue); 53 | onValueChange?.(newValue); 54 | }; 55 | } 56 | 57 | return { 58 | onChange, onMouseUp, getStepClickHandler, 59 | }; 60 | } 61 | 62 | export function getRangeProps({ variant, color = 'red', ...props }: RangeProps) { 63 | const { value, min, max } = props; 64 | const relativeValue = relative({ value, min, max }); 65 | 66 | const style = assignInlineVars({ 67 | [selectedColor]: getColor(color), 68 | [currentValue]: `${100 * relativeValue}%`, 69 | }); 70 | 71 | return mergeProps(props, { 72 | type: 'range', 73 | className: inputRecipe({ variant }), 74 | style, 75 | }); 76 | } 77 | 78 | export function RangeInput(props: RangeProps) { 79 | return ; 80 | } 81 | 82 | export function Range({ onValueChange, onValueInput, minSymbol, maxSymbol, ...props }: Props) { 83 | const { value, min, max, step = 1 } = props; 84 | const { onChange, onMouseUp, getStepClickHandler } = useRange({ 85 | value, min, max, onValueChange, onValueInput, 86 | }); 87 | 88 | return

89 | {minSymbol && ( 90 | {minSymbol} 91 | )} 92 | 96 | {maxSymbol && ( 97 | {maxSymbol} 98 | )} 99 |
; 100 | } -------------------------------------------------------------------------------- /packages/ui/src/inputs/Range/styles.css.ts: -------------------------------------------------------------------------------- 1 | import { createVar, style } from '@vanilla-extract/css'; 2 | import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; 3 | 4 | import { atoms, vars } from '../../theme'; 5 | 6 | export const currentValue = createVar(); 7 | export const selectedColor = createVar(); 8 | 9 | export const wrap = style([ 10 | atoms({ 11 | display: 'flex', 12 | alignItems: 'center', 13 | }), 14 | { 15 | height: vars.space.medium, 16 | }, 17 | ]); 18 | 19 | export const symbol = style([ 20 | atoms({ 21 | display: 'flex', 22 | alignItems: 'center', 23 | }), 24 | { 25 | height: vars.space.medium, 26 | minWidth: vars.space.medium, 27 | padding: '0 5px', 28 | boxSizing: 'border-box', 29 | fontSize: '13px', 30 | flexShrink: 0, 31 | cursor: 'pointer', 32 | userSelect: 'none', 33 | }, 34 | ]); 35 | 36 | export const inputRecipe = recipe({ 37 | base: [ 38 | atoms({ 39 | backgroundColor: 'gray-060', 40 | }), { 41 | width: '100%', 42 | height: '3px', 43 | WebkitAppearance: 'none', 44 | appearance: 'none', 45 | cursor: 'pointer', 46 | 47 | vars: { 48 | [currentValue]: '0', 49 | }, 50 | 51 | ':focus': { 52 | outline: 'none', 53 | }, 54 | 55 | selectors: { 56 | '&::-webkit-slider-thumb': { 57 | WebkitAppearance: 'none', 58 | appearance: 'none', 59 | width: '12px', 60 | height: '12px', 61 | borderRadius: '50%', 62 | border: 'solid 2px #fff', 63 | background: `${selectedColor}`, 64 | }, 65 | }, 66 | }], 67 | 68 | variants: { 69 | variant: { 70 | bigThumb: { 71 | height: '2px', 72 | background: `linear-gradient(to right, ${selectedColor} ${currentValue}, #f0f0f0 0)`, 73 | selectors: { 74 | '&::-webkit-slider-thumb': { 75 | background: '#FFFFFF', 76 | boxShadow: '0px 1px 4px rgba(0, 0, 0, 0.2)', 77 | width: '16px', 78 | height: '16px', 79 | }, 80 | }, 81 | }, 82 | }, 83 | }, 84 | }); 85 | 86 | export type Variants = RecipeVariants; 87 | 88 | -------------------------------------------------------------------------------- /packages/ui/src/inputs/ToggleButton/ToggleButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 4 | 5 | import { ToggleButton } from './ToggleButton'; 6 | 7 | export default { 8 | title: 'Foundation/Inputs/ToggleButton', 9 | component: ToggleButton, 10 | } as ComponentMeta; 11 | 12 | export const WithArgs: ComponentStory = (args) => { 13 | const [checked, setChecked] = useState(false); 14 | return setChecked(!checked)} {...args} />; 15 | }; 16 | 17 | WithArgs.args = { 18 | color: 'red', 19 | size: 'm', 20 | variant: 'strong-fill', 21 | }; 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/ui/src/inputs/ToggleButton/ToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Checkbox } from 'reakit/Checkbox'; 3 | 4 | import { mergeProps } from '@react-aria/utils'; 5 | import { assignInlineVars } from '@vanilla-extract/dynamic'; 6 | 7 | import { getColor, NamedColor } from '../../theme'; 8 | import { buttonColor, toggleButtonRecipe, Variants } from './styles.css'; 9 | 10 | type Props = Omit, keyof NonNullable> & Variants & { 11 | color: NamedColor; 12 | }; 13 | 14 | export function getToggleButtonProps({ color, size, variant, ...props }: Props) { 15 | const className = toggleButtonRecipe({ size, variant }); 16 | const style = assignInlineVars({ [buttonColor]: getColor(color) }); 17 | return mergeProps(props, { className, style }); 18 | } 19 | 20 | export function ToggleButton(props: Props) { 21 | return ( 22 | 23 | ); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /packages/ui/src/inputs/ToggleButton/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ToggleButton'; -------------------------------------------------------------------------------- /packages/ui/src/inputs/ToggleButton/styles.css.ts: -------------------------------------------------------------------------------- 1 | 2 | import { createVar } from '@vanilla-extract/css'; 3 | import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; 4 | 5 | import { atoms, vars } from '../../theme'; 6 | import { scale } from '../../theme/scale'; 7 | 8 | export const buttonColor = createVar(); 9 | 10 | export const toggleButtonRecipe = recipe({ 11 | base: [atoms({ 12 | display: 'inline-flex', 13 | }), 14 | { 15 | background: 'transparent', 16 | WebkitAppearance: 'none', 17 | appearance: 'none', 18 | border: 0, 19 | borderRadius: '1em', 20 | height: '1em', 21 | width: '1.8333333em', 22 | padding: 0, 23 | margin: 0, 24 | cursor: 'pointer', 25 | position: 'relative', 26 | flex: '0 0 1.8333333em', 27 | opacity: '0.9', 28 | selectors: { 29 | '&:before': { 30 | content: '\'\'', 31 | display: 'block', 32 | width: '0.8em', 33 | height: '0.8em', 34 | borderRadius: '100%', 35 | backgroundColor: '#fff', 36 | position: 'absolute', 37 | left: '0.1em', 38 | top: '0.1em', 39 | transition: 'all 0.1s', 40 | }, 41 | '&:checked:before': { 42 | left: '0.9333em', 43 | }, 44 | '&:disabled': { 45 | opacity: '0.7', 46 | }, 47 | '&:hover:not(:disabled)': { 48 | opacity: '1', 49 | }, 50 | }, 51 | color: buttonColor, 52 | vars: { 53 | [buttonColor]: vars.color['gray'], 54 | }, 55 | }], 56 | 57 | variants: { 58 | size: { 59 | xs: { fontSize: scale(1.5) }, 60 | s: { fontSize: scale(2) }, 61 | m: { fontSize: scale(2.5) }, 62 | l: { fontSize: scale(3) }, 63 | }, 64 | variant: { 65 | stroke: { boxShadow: `inset 0 0 1px 1px ${buttonColor}`, ':before': { backgroundColor: buttonColor } }, 66 | 'strong-fill': { backgroundColor: buttonColor }, 67 | }, 68 | }, 69 | 70 | defaultVariants: { 71 | variant: 'strong-fill', 72 | size: 'm', 73 | }, 74 | }); 75 | 76 | export type Variants = RecipeVariants; -------------------------------------------------------------------------------- /packages/ui/src/inputs/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Button'; 2 | export * from './Range/Range'; 3 | export * from './ToggleButton'; 4 | -------------------------------------------------------------------------------- /packages/ui/src/layout/Table/Table.stories.tsx: -------------------------------------------------------------------------------- 1 | import { ComponentMeta, ComponentStory } from '@storybook/react'; 2 | 3 | import { Box } from '../../foundation/Box/Box'; 4 | import { Body, Caption, Cell, Footer, Head, HeadCell, Row, Table } from './Table'; 5 | 6 | export default { 7 | title: 'Contrib/Layout/Table', 8 | component: Table, 9 | parameters: { 10 | controls: { 11 | include: ['variant'], 12 | }, 13 | }, 14 | } as ComponentMeta; 15 | 16 | export const BasicUsage: ComponentStory = (args) => 17 | 18 | Object NameAnnotation TypeAuto Label AICount 19 | 20 | 21 | Vehicle_CarBoxCommon Objects1,123 22 | Vehicle_VanBoxApple watch project › Custom Auto Label AI 2
› Custom Auto Label AI 2 › Custom Auto Label AI 2
12,123
23 | 24 |
25 | 13,246 26 |
27 |
; 28 | 29 | BasicUsage.args = { 30 | variant: 'simple', 31 | }; 32 | 33 | export const Sticky = () => 34 | 35 | 36 | 123 37 | 38 | 39 | 123 40 | 123 41 | 123 42 | 123 43 | 123 44 | 45 |
46 | 13,246 47 |
48 |
49 |
; 50 | 51 | export const WithCaption = () => 52 | 53 | 54 | 55 | 56 | Object NameAnnotation TypeAuto Label AICount 57 | 58 | 59 | Vehicle_CarBoxCommon Objects1,123 60 | Vehicle_VanBoxApple watch project › Custom Auto Label AI 212,123 61 | 62 |
Object ClassesCaption can be on the bottom
63 |
; -------------------------------------------------------------------------------- /packages/ui/src/layout/Table/Table.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { atoms } from '../../theme'; 3 | import { styled } from '../../util/styled'; 4 | import { captionRecipe, CaptionVariants, tableRecipe, TableVariants } from './styles.css'; 5 | 6 | const stickyTop = ({ sticky }: { sticky?: boolean }) => sticky ? atoms({ position: 'sticky', top: '0', backgroundColor: 'white' }) : ''; 7 | const stickyBottom = ({ sticky }: { sticky?: boolean }) => sticky ? atoms({ position: 'sticky', bottom: '0', backgroundColor: 'white' }) : ''; 8 | 9 | export const Table = styled('table', ({ variant }: NonNullable) => tableRecipe({ variant })); 10 | 11 | export const Head = styled('thead', stickyTop); 12 | 13 | export const Footer = styled('tfoot', stickyBottom); 14 | 15 | export const Body = styled('tbody'); 16 | 17 | export const Row = styled('tr', stickyTop); 18 | 19 | export const Cell = styled('td'); 20 | 21 | export const HeadCell = styled('th'); 22 | 23 | export const Caption = styled('caption', ({ side }: NonNullable) => captionRecipe({ side })); 24 | -------------------------------------------------------------------------------- /packages/ui/src/layout/Table/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Table'; -------------------------------------------------------------------------------- /packages/ui/src/layout/Table/styles.css.ts: -------------------------------------------------------------------------------- 1 | import { globalStyle, style, styleVariants } from '@vanilla-extract/css'; 2 | import { recipe, RecipeVariants } from '@vanilla-extract/recipes'; 3 | 4 | import { atoms, vars } from '../../theme'; 5 | 6 | const baseStyle = style({ 7 | fontSize: '12px', 8 | lineHeight: 1.5, 9 | }); 10 | 11 | globalStyle(`${baseStyle} thead th, ${baseStyle} tfoot td`, { 12 | textAlign: 'inherit', 13 | padding: '8px 24px', 14 | fontWeight: 'normal', 15 | }); 16 | globalStyle(`${baseStyle} thead th[align=right], ${baseStyle} tfoot td[align=right]`, { 17 | textAlign: 'right', 18 | }); 19 | globalStyle(`${baseStyle} td`, { 20 | padding: '16px 24px', 21 | }); 22 | 23 | const tableVariants = styleVariants({ 24 | simple: { 25 | 26 | }, 27 | }); 28 | 29 | globalStyle(`${tableVariants.simple} thead, ${tableVariants.simple} tfoot`, { 30 | color: vars.color['gray-800'], 31 | backgroundColor: vars.color['gray-050'], 32 | }); 33 | 34 | globalStyle(`${tableVariants.simple} thead`, { 35 | fontSize: '10px', 36 | }); 37 | 38 | export const tableRecipe = recipe({ 39 | base: [ 40 | atoms({ 41 | position: 'relative', 42 | borderCollapse: 'collapse', 43 | width: '100%', 44 | }), 45 | baseStyle, 46 | ], 47 | variants: { 48 | variant: tableVariants, 49 | }, 50 | }); 51 | 52 | export type TableVariants = RecipeVariants; 53 | 54 | export const captionRecipe = recipe({ 55 | base: [ 56 | { 57 | fontSize: '14px', 58 | fontWeight: 500, 59 | padding: '16px 24px', 60 | textAlign: 'left', 61 | }, 62 | ], 63 | variants: { 64 | side: { 65 | top: { 66 | captionSide: 'top', 67 | borderBottom: `1px solid ${vars.color['gray-080']}`, 68 | }, 69 | bottom: { 70 | captionSide: 'bottom', 71 | borderTop: `1px solid ${vars.color['gray-080']}`, 72 | }, 73 | }, 74 | }, 75 | defaultVariants: { 76 | side: 'top', 77 | }, 78 | }); 79 | 80 | export type CaptionVariants = RecipeVariants; 81 | -------------------------------------------------------------------------------- /packages/ui/src/layout/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Table'; -------------------------------------------------------------------------------- /packages/ui/src/theme/color-util.ts: -------------------------------------------------------------------------------- 1 | import tokens from './tokens.json'; 2 | 3 | type Tokens = typeof tokens; 4 | type Concat = `${Lowercase}-${Lowercase}`; 5 | export type NamedColor = Lowercase; 6 | export type NamedColorWeight = { [K in keyof Tokens]: K extends string ? keyof Tokens[K] extends string ? Concat : K : K }[keyof Tokens]; 7 | export type ColorWeightMap = { [K in keyof Tokens as Lowercase]: K extends string ? keyof Tokens[K] extends string ? keyof Tokens[K] : K : K }; 8 | 9 | /** 10 | * Translate JSON from Figma into usable object structure and types 11 | **/ 12 | function colorsFromTokens() { 13 | const colors: Partial> = {}; 14 | for (const [color, colorDef] of Object.entries(tokens)) { 15 | for (const [weight, weightDef] of Object.entries(colorDef)) { 16 | const key = `${color.toLowerCase()}-${weight}` as NamedColorWeight; 17 | colors[key] = weightDef.value; 18 | } 19 | } 20 | return colors as Record; 21 | } 22 | 23 | export const colorMap = colorsFromTokens(); 24 | export const colors = Object.keys(colorMap) as NamedColorWeight[]; 25 | 26 | export const namedColors = Object.keys(tokens).map(str => str.toLowerCase()) as NamedColor[]; 27 | export const namedColorMap = Object.fromEntries(namedColors.map(color => 28 | [color, colorMap[`${color}-600` as NamedColorWeight] ?? colorMap[`${color}-100`]], 29 | )) as Record; 30 | export const colorWeightMap = Object.fromEntries(Object.keys(tokens).map(color => 31 | [color.toLowerCase(), Object.keys(tokens[color as keyof Tokens])], 32 | )) as Record; 33 | -------------------------------------------------------------------------------- /packages/ui/src/theme/color.stories.mdx: -------------------------------------------------------------------------------- 1 | import { Meta, Story } from '@storybook/addon-docs'; 2 | import { Box, Typography } from '../foundation'; 3 | import { colorWeightMap } from './color-util'; 4 | 5 | 6 | 7 | export const Template = () => { 8 | return Object.entries(colorWeightMap).map(([color, weights]) => { 9 | weights.sort(); 10 | return ( 11 | 12 | {color} 13 | 20 | {weights.map((weight) => { 21 | return ( 22 | 23 | 28 | {weight} 29 | 30 | 31 | {color}-{weight} 32 | 33 | 34 | ); 35 | })} 36 | 37 | 38 | ); 39 | }); 40 | }; 41 | 42 | # Color Palette 43 | 44 | {Template.bind({})} 45 | -------------------------------------------------------------------------------- /packages/ui/src/theme/color.test.ts: -------------------------------------------------------------------------------- 1 | import { getColor } from './color'; 2 | 3 | describe('getColor', () => { 4 | it('returns named color with default weight', () => { 5 | expect(getColor('red')).toContain('var(--color-red'); 6 | expect(getColor('cloud')).toContain('var(--color-cloud'); 7 | }); 8 | it('returns named color with weight in string', () => { 9 | expect(getColor('red-400')).toContain('var(--color-red-400'); 10 | expect(getColor('cloud-030')).toContain('var(--color-cloud-030'); 11 | }); 12 | it('returns named color with weight in arg', () => { 13 | expect(getColor('red', '400')).toContain('var(--color-red-400'); 14 | expect(getColor('cloud', '030')).toContain('var(--color-cloud-030'); 15 | }); 16 | it('returns custom color', () => { 17 | expect(getColor('#123456')).toBe('#123456'); 18 | }); 19 | it('returns undefined for unknown color', () => { 20 | // @ts-expect-error: Argument of type '"lol"' is not assignable to parameter of type 'Color'. 21 | expect(() => getColor('lol')).toThrow(new Error('undefined color')); 22 | // @ts-expect-error: Argument of type '"red-123"' is not assignable to parameter of type 'Color'. 23 | expect(() => getColor('red-123')).toThrow(new Error('undefined color')); 24 | }); 25 | }); -------------------------------------------------------------------------------- /packages/ui/src/theme/color.ts: -------------------------------------------------------------------------------- 1 | import { ColorWeightMap, NamedColor, namedColors, NamedColorWeight } from './color-util'; 2 | import { vars } from './theme.css'; 3 | export { namedColors }; 4 | export type { NamedColor, NamedColorWeight }; 5 | 6 | type ColorWeights = C extends NamedColor ? ColorWeightMap[C] : never; 7 | type RgbColor = `#${string}`; 8 | 9 | export type Color = RgbColor | NamedColor | NamedColorWeight; 10 | 11 | /** 12 | * Translates color tokens into CSS values. 13 | * getColor('red') -> 'var(--color-red)' 14 | * getColor('red-600') -> 'var(--color-red-600)' 15 | * getColor('red', '600') -> 'var(--color-red-600)' 16 | * getColor('#abcdef') -> ''#abcdef' 17 | */ 18 | export function getColor(color: C, weight?: ColorWeights): string { 19 | if (weight && `${color}-${weight}` in vars.color) { 20 | return vars.color[`${color}-${weight}` as NamedColorWeight]; 21 | } 22 | if (color in vars.color) { 23 | return vars.color[color as NamedColorWeight]; 24 | } 25 | if (color && color.startsWith('#')) { 26 | return color; 27 | } 28 | throw new Error('undefined color'); 29 | } 30 | -------------------------------------------------------------------------------- /packages/ui/src/theme/index.ts: -------------------------------------------------------------------------------- 1 | export * from './color'; 2 | export * from './sprinkles.css'; 3 | export * from './theme.css'; 4 | -------------------------------------------------------------------------------- /packages/ui/src/theme/scale.ts: -------------------------------------------------------------------------------- 1 | export function scale(n: number, base = 8) { 2 | return n * base; 3 | } 4 | 5 | export function scalePx(n: number, base?: number) { 6 | return `${scale(n, base)}px`; 7 | } -------------------------------------------------------------------------------- /packages/ui/src/theme/scrollbar.ts: -------------------------------------------------------------------------------- 1 | interface ScollbarConfig { 2 | width: number; // scrollbar width 3 | outerWidth?: number; // track width 4 | foreground?: string; // scrollbar color 5 | background?: string; // track color 6 | onHover?: boolean; // only show scrollbar on hover 7 | } 8 | 9 | export function getScrollbarStyles({ 10 | width, 11 | outerWidth = width * 2, 12 | foreground = 'rgba(0, 0, 0, 0.35)', 13 | background = 'rgba(0, 0, 0, 0.075)', 14 | onHover = false, 15 | }: ScollbarConfig) { 16 | const margin = `${(outerWidth - width) / 2}px`; 17 | const trackSize = `${outerWidth}px`; 18 | const hover = !onHover 19 | ? {} 20 | : { 21 | boxShadow: 'none', 22 | }; 23 | return { 24 | scrollbarColor: `${foreground} transparent`, // fallback 25 | scrollbarWidth: 'thin', // fallback 26 | selectors: { 27 | '&::-webkit-scrollbar': { 28 | width: trackSize, 29 | height: trackSize, 30 | }, 31 | '&::-webkit-scrollbar-track, &::-webkit-scrollbar-thumb': { 32 | borderRadius: trackSize, 33 | border: `solid ${margin} transparent`, 34 | }, 35 | '&::-webkit-scrollbar-track': { 36 | boxShadow: `inset 0 0 ${margin} ${margin} ${background}`, 37 | }, 38 | '&::-webkit-scrollbar-thumb': { 39 | boxShadow: `inset 0 0 ${margin} ${margin} ${foreground}`, 40 | }, 41 | '&:not(:hover)::-webkit-scrollbar-thumb': hover, 42 | }, 43 | } as const; 44 | } 45 | 46 | export const defaultScrollbarStyles = getScrollbarStyles({ 47 | width: 6, 48 | outerWidth: 18, 49 | onHover: false, 50 | background: 'transparent', 51 | foreground: 'rgba(0, 0, 0, 0.2)', 52 | }); 53 | -------------------------------------------------------------------------------- /packages/ui/src/theme/space.ts: -------------------------------------------------------------------------------- 1 | import { scalePx } from './scale'; 2 | 3 | const numerics = [0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 7, 8] as const; 4 | const numericalDef = Object.fromEntries(numerics.map(n => [`${n}`, scalePx(n)])) as Record; 5 | 6 | export const space = { 7 | 'small': scalePx(1), 8 | 'medium': scalePx(2), 9 | 'large': scalePx(4), 10 | ...numericalDef, 11 | }; 12 | 13 | export const spaces = Object.keys(space) as (keyof typeof space)[]; 14 | export type Space = typeof spaces[number]; -------------------------------------------------------------------------------- /packages/ui/src/theme/sprinkles.css.ts: -------------------------------------------------------------------------------- 1 | import { createSprinkles, defineProperties } from '@vanilla-extract/sprinkles'; 2 | 3 | import { defaultScrollbarStyles } from './scrollbar'; 4 | import { vars } from './theme.css'; 5 | 6 | /** 7 | * Maps several keys to the same value 8 | */ 9 | function mapTo(keys: readonly Key[], value: Value) { 10 | return Object.fromEntries(keys.map(key => [key, value])) as Record; 11 | } 12 | 13 | const spaceProperties = [ 14 | 'padding', 'paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight', 15 | 'margin', 'marginTop', 'marginBottom', 'marginLeft', 'marginRight', 16 | 'gap', 'rowGap', 'columnGap', 17 | ] as const; 18 | 19 | const sizeProperties = [ 20 | 'width', 'maxWidth', 21 | 'height', 'maxHeight', 22 | 'left', 'top', 'right', 'bottom', 23 | ] as const; 24 | 25 | const layoutStyles = defineProperties({ 26 | properties: { 27 | display: ['none', 'flex', 'grid', 'block', 'inline', 'inline-flex'], 28 | visibility: ['hidden', 'visible'], 29 | flexDirection: ['row', 'column'], 30 | flexWrap: ['wrap'], 31 | flexFlow: ['row wrap', 'column wrap'], 32 | justifyContent: ['stretch', 'flex-start', 'center', 'flex-end', 'space-around', 'space-between'], 33 | alignItems: ['stretch', 'flex-start', 'center', 'flex-end', 'baseline'], 34 | placeItems: ['center'], 35 | textAlign: ['center', 'right'], 36 | boxSizing: ['border-box'], 37 | position: ['absolute', 'relative', 'fixed', 'sticky'], 38 | scrollbarGutter: ['stable'], 39 | aspectRatio: ['1'], 40 | overflow: { 41 | hidden: 'hidden', 42 | auto: { 43 | overflow: 'auto', 44 | ...defaultScrollbarStyles, 45 | }, 46 | overlay: { 47 | overflow: ['auto', 'overlay'], // in Firefox and IE `overlay` will be ignored and `auto` will be applied 48 | ...defaultScrollbarStyles, 49 | }, 50 | }, 51 | ...mapTo(spaceProperties, { ...vars.space, auto: 'auto', full: '100%' }), 52 | ...mapTo(sizeProperties, ['0', '0%', '50%', '100%', 'auto', '100vh', '100vw', '100vmin', '100vmax']), 53 | }, 54 | shorthands: { 55 | p: ['padding'], 56 | pl: ['paddingLeft'], 57 | pr: ['paddingRight'], 58 | pt: ['paddingTop'], 59 | pb: ['paddingBottom'], 60 | ...mapTo(['px', 'paddingX'], ['paddingLeft', 'paddingRight']), 61 | ...mapTo(['py', 'paddingY'], ['paddingTop', 'paddingBottom']), 62 | m: ['margin'], 63 | ml: ['marginLeft'], 64 | mr: ['marginRight'], 65 | mt: ['marginTop'], 66 | mb: ['marginBottom'], 67 | ...mapTo(['mx', 'marginX'], ['marginLeft', 'marginRight']), 68 | ...mapTo(['my', 'marginY'], ['marginTop', 'marginBottom']), 69 | }, 70 | }); 71 | 72 | const decorationStyles = defineProperties({ 73 | properties: { 74 | ...mapTo(['border', 'borderLeft', 'borderRight', 'borderTop', 'borderBottom'], ['1px solid', '2px solid', '1px solid transparent']), 75 | borderRadius: ['2px', '4px', '9999px', '100%'], 76 | borderCollapse: ['collapse'], 77 | cursor: ['pointer'], 78 | }, 79 | shorthands: { 80 | bb: ['borderBottom'], 81 | bt: ['borderTop'], 82 | bl: ['borderLeft'], 83 | br: ['borderRight'], 84 | b: ['border'], 85 | }, 86 | }); 87 | 88 | const colorStyles = defineProperties({ 89 | conditions: { 90 | default: {}, 91 | hover: { selector: '&:hover' }, 92 | focusWithin: { selector: '&:focus-within' }, 93 | }, 94 | defaultCondition: 'default', 95 | properties: { 96 | ...mapTo(['color', 'borderColor', 'backgroundColor'], { ...vars.color, transparent: 'transparent' }), 97 | }, 98 | }); 99 | 100 | export const atoms = createSprinkles(layoutStyles, decorationStyles, colorStyles); 101 | 102 | export type Atoms = Parameters[0]; -------------------------------------------------------------------------------- /packages/ui/src/theme/theme.css.ts: -------------------------------------------------------------------------------- 1 | import { createTheme, globalStyle } from '@vanilla-extract/css'; 2 | 3 | import { colorMap, namedColorMap } from './color-util'; 4 | import { space } from './space'; 5 | export type { Space } from './space'; 6 | export { spaces } from './space'; 7 | 8 | export const [themeClass, vars] = createTheme({ 9 | color: { ...colorMap, ...namedColorMap }, 10 | space, 11 | }); 12 | 13 | globalStyle('body', { 14 | fontFamily: 'Inter, sans-serif', 15 | WebkitFontSmoothing: 'antialiased', 16 | }); 17 | 18 | globalStyle('*, *::after, *::before', { 19 | margin: 0, 20 | padding: 0, 21 | boxSizing: 'inherit', 22 | }); -------------------------------------------------------------------------------- /packages/ui/src/theme/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "Red": { 3 | "100": { 4 | "value": "#ffecec", 5 | "type": "color", 6 | "description": "FFECEC" 7 | }, 8 | "200": { 9 | "value": "#ffdada", 10 | "type": "color", 11 | "description": "FFDADA" 12 | }, 13 | "300": { 14 | "value": "#ffbcbc", 15 | "type": "color", 16 | "description": "FFBCBC" 17 | }, 18 | "400": { 19 | "value": "#ff9ea7", 20 | "type": "color", 21 | "description": "FF9EA7" 22 | }, 23 | "500": { 24 | "value": "#ff828a", 25 | "type": "color", 26 | "description": "FF828A" 27 | }, 28 | "600": { 29 | "value": "#ff625a", 30 | "type": "color", 31 | "description": "FF625A" 32 | }, 33 | "700": { 34 | "value": "#ff4539", 35 | "type": "color", 36 | "description": "FF4539" 37 | }, 38 | "800": { 39 | "value": "#e53d30", 40 | "type": "color", 41 | "description": "E53D30" 42 | }, 43 | "900": { 44 | "value": "#c23c33", 45 | "type": "color", 46 | "description": "C23C33" 47 | }, 48 | "050": { 49 | "value": "#fff6f6", 50 | "type": "color", 51 | "description": "FFF6F6" 52 | } 53 | }, 54 | "Blue": { 55 | "100": { 56 | "value": "#e6ebff", 57 | "type": "color", 58 | "description": "E6EBFF" 59 | }, 60 | "200": { 61 | "value": "#d6defd", 62 | "type": "color", 63 | "description": "D6DEFD" 64 | }, 65 | "300": { 66 | "value": "#becafc", 67 | "type": "color", 68 | "description": "BECAFC" 69 | }, 70 | "400": { 71 | "value": "#a3b6fe", 72 | "type": "color", 73 | "description": "A3B6FE" 74 | }, 75 | "500": { 76 | "value": "#7691ff", 77 | "type": "color", 78 | "description": "7691FF" 79 | }, 80 | "600": { 81 | "value": "#5a7bff", 82 | "type": "color", 83 | "description": "5A7AFF" 84 | }, 85 | "700": { 86 | "value": "#2a42e4", 87 | "type": "color", 88 | "description": "2A42E4" 89 | }, 90 | "800": { 91 | "value": "#1226aa", 92 | "type": "color", 93 | "description": "1226AA" 94 | }, 95 | "900": { 96 | "value": "#0f206c", 97 | "type": "color", 98 | "description": "0F206C" 99 | }, 100 | "050": { 101 | "value": "#f3f5ff", 102 | "type": "color", 103 | "description": "F3F5FF" 104 | } 105 | }, 106 | "Gray": { 107 | "100": { 108 | "value": "#d4d4d4", 109 | "type": "color", 110 | "description": "D4D4D4" 111 | }, 112 | "200": { 113 | "value": "#bcbcbc", 114 | "type": "color", 115 | "description": "BCBCBC" 116 | }, 117 | "300": { 118 | "value": "#adadad", 119 | "type": "color", 120 | "description": "ADADAD" 121 | }, 122 | "400": { 123 | "value": "#929292", 124 | "type": "color", 125 | "description": "929292" 126 | }, 127 | "500": { 128 | "value": "#7b7b7b", 129 | "type": "color", 130 | "description": "7B7B7B" 131 | }, 132 | "600": { 133 | "value": "#6e6e6e", 134 | "type": "color", 135 | "description": "6E6E6E" 136 | }, 137 | "700": { 138 | "value": "#555555", 139 | "type": "color", 140 | "description": "555555" 141 | }, 142 | "800": { 143 | "value": "#333333", 144 | "type": "color", 145 | "description": "333333" 146 | }, 147 | "900": { 148 | "value": "#101010", 149 | "type": "color", 150 | "description": "101010" 151 | }, 152 | "050": { 153 | "value": "#fbfbfb", 154 | "type": "color", 155 | "description": "FBFBFB" 156 | }, 157 | "060": { 158 | "value": "#f3f3f3", 159 | "type": "color", 160 | "description": "F3F3F3" 161 | }, 162 | "080": { 163 | "value": "#e5e5e5", 164 | "type": "color", 165 | "description": "E5E5E5" 166 | } 167 | }, 168 | "Orange": { 169 | "100": { 170 | "value": "#fff4ed", 171 | "type": "color", 172 | "description": "FFF4ED" 173 | }, 174 | "200": { 175 | "value": "#ffeadd", 176 | "type": "color", 177 | "description": "FFEADD" 178 | }, 179 | "300": { 180 | "value": "#ffdbc5", 181 | "type": "color", 182 | "description": "FFDBC5" 183 | }, 184 | "400": { 185 | "value": "#fdcdb0", 186 | "type": "color", 187 | "description": "FDCDB0" 188 | }, 189 | "500": { 190 | "value": "#ffb692", 191 | "type": "color", 192 | "description": "FFB692" 193 | }, 194 | "600": { 195 | "value": "#fe9573", 196 | "type": "color", 197 | "description": "FE9573" 198 | }, 199 | "700": { 200 | "value": "#ff8041", 201 | "type": "color", 202 | "description": "FF8041" 203 | }, 204 | "800": { 205 | "value": "#ff681d", 206 | "type": "color", 207 | "description": "FF681D" 208 | }, 209 | "900": { 210 | "value": "#e65400", 211 | "type": "color", 212 | "description": "E65400" 213 | }, 214 | "050": { 215 | "value": "#fff8f4", 216 | "type": "color", 217 | "description": "FFF8F4" 218 | } 219 | }, 220 | "Black": { 221 | "100": { 222 | "value": "#000000", 223 | "type": "color", 224 | "description": "000000" 225 | }, 226 | "006": { 227 | "value": "#f0f0f0", 228 | "type": "color", 229 | "description": "F0F0F0" 230 | }, 231 | "012": { 232 | "value": "#e0e0e0", 233 | "type": "color", 234 | "description": "E0E0E0" 235 | }, 236 | "030": { 237 | "value": "#b3b3b3", 238 | "type": "color", 239 | "description": "B3B3B3" 240 | } 241 | }, 242 | "Carrot": { 243 | "100": { 244 | "value": "#ffa749", 245 | "type": "color", 246 | "description": "FFAF5A" 247 | }, 248 | "120": { 249 | "value": "#ea7700", 250 | "type": "color", 251 | "description": "E97700" 252 | }, 253 | "006": { 254 | "value": "#fffaf5", 255 | "type": "color", 256 | "description": "FFFAF5" 257 | }, 258 | "012": { 259 | "value": "#fff5eb", 260 | "type": "color", 261 | "description": "FFF5EB" 262 | }, 263 | "030": { 264 | "value": "#ffe7ce", 265 | "type": "color", 266 | "description": "FFE7CE" 267 | } 268 | }, 269 | "Cloud": { 270 | "100": { 271 | "value": "#a6a6a6", 272 | "type": "color", 273 | "description": "A6A6A6" 274 | }, 275 | "120": { 276 | "value": "#7b7b7b", 277 | "type": "color", 278 | "description": "7B7B7B" 279 | }, 280 | "006": { 281 | "value": "#fafafa", 282 | "type": "color", 283 | "description": "F9F9F9" 284 | }, 285 | "012": { 286 | "value": "#f4f4f4", 287 | "type": "color", 288 | "description": "F4F4F4" 289 | }, 290 | "030": { 291 | "value": "#e4e4e4", 292 | "type": "color", 293 | "description": "E4E4E4" 294 | } 295 | }, 296 | "Cobalt": { 297 | "100": { 298 | "value": "#5a7bff", 299 | "type": "color", 300 | "description": "5A7AFF" 301 | }, 302 | "120": { 303 | "value": "#0028db", 304 | "type": "color", 305 | "description": "0028DA" 306 | }, 307 | "006": { 308 | "value": "#f5f7ff", 309 | "type": "color", 310 | "description": "F5F7FF" 311 | }, 312 | "012": { 313 | "value": "#ebefff", 314 | "type": "color", 315 | "description": "EBEFFF" 316 | }, 317 | "030": { 318 | "value": "#ced7ff", 319 | "type": "color", 320 | "description": "CED7FF" 321 | } 322 | }, 323 | "Coral": { 324 | "100": { 325 | "value": "#fe9573", 326 | "type": "color", 327 | "description": "FE9573" 328 | }, 329 | "120": { 330 | "value": "#e88a6c", 331 | "type": "color" 332 | }, 333 | "006": { 334 | "value": "#fff9f7", 335 | "type": "color", 336 | "description": "FFF9F7" 337 | }, 338 | "012": { 339 | "value": "#fff2ee", 340 | "type": "color", 341 | "description": "FFF2EE" 342 | }, 343 | "030": { 344 | "value": "#ffdfd5", 345 | "type": "color", 346 | "description": "FFDFD5" 347 | } 348 | }, 349 | "Green": { 350 | "100": { 351 | "value": "#82db24", 352 | "type": "color", 353 | "description": "A3EB57" 354 | }, 355 | "120": { 356 | "value": "#6ebb1f", 357 | "type": "color", 358 | "description": "6EBB1F" 359 | }, 360 | "006": { 361 | "value": "#f7fdf2", 362 | "type": "color", 363 | "description": "F9FEF5" 364 | }, 365 | "012": { 366 | "value": "#f0fbe5", 367 | "type": "color", 368 | "description": "F4FDEB" 369 | }, 370 | "030": { 371 | "value": "#daf4bd", 372 | "type": "color", 373 | "description": "E3F9CD" 374 | } 375 | }, 376 | "Lime": { 377 | "100": { 378 | "value": "#def00f", 379 | "type": "color", 380 | "description": "DEF00F" 381 | }, 382 | "120": { 383 | "value": "#bbcd00", 384 | "type": "color", 385 | "description": "BBCD00" 386 | }, 387 | "006": { 388 | "value": "#fdfef1", 389 | "type": "color", 390 | "description": "FDFEF1" 391 | }, 392 | "012": { 393 | "value": "#fbfde2", 394 | "type": "color", 395 | "description": "FBFDE2" 396 | }, 397 | "030": { 398 | "value": "#f5fbb7", 399 | "type": "color", 400 | "description": "F5FBB7" 401 | } 402 | }, 403 | "Mint": { 404 | "100": { 405 | "value": "#4ae2b9", 406 | "type": "color", 407 | "description": "4AE2B9" 408 | }, 409 | "120": { 410 | "value": "#00cece", 411 | "type": "color", 412 | "description": "00CECE" 413 | }, 414 | "006": { 415 | "value": "#f4fdfb", 416 | "type": "color", 417 | "description": "F4FDFB" 418 | }, 419 | "012": { 420 | "value": "#e9fcf7", 421 | "type": "color", 422 | "description": "E9FCF7" 423 | }, 424 | "030": { 425 | "value": "#c9f6ea", 426 | "type": "color", 427 | "description": "C9F6EA" 428 | } 429 | }, 430 | "Pink": { 431 | "100": { 432 | "value": "#ff4881", 433 | "type": "color", 434 | "description": "FF4881" 435 | }, 436 | "120": { 437 | "value": "#cf0040", 438 | "type": "color", 439 | "description": "CF0040" 440 | }, 441 | "006": { 442 | "value": "#fff4f7", 443 | "type": "color", 444 | "description": "FFF4F7" 445 | }, 446 | "012": { 447 | "value": "#ffe9f0", 448 | "type": "color", 449 | "description": "FFE9F0" 450 | }, 451 | "030": { 452 | "value": "#ffc8d9", 453 | "type": "color", 454 | "description": "FFC8D9" 455 | } 456 | }, 457 | "Purple": { 458 | "100": { 459 | "value": "#af48ff", 460 | "type": "color", 461 | "description": "AF48FF" 462 | }, 463 | "120": { 464 | "value": "#560099", 465 | "type": "color", 466 | "description": "560099" 467 | }, 468 | "006": { 469 | "value": "#faf4ff", 470 | "type": "color", 471 | "description": "FAF4FF" 472 | }, 473 | "012": { 474 | "value": "#f5e9ff", 475 | "type": "color", 476 | "description": "F5E9FF" 477 | }, 478 | "030": { 479 | "value": "#e7c8ff", 480 | "type": "color", 481 | "description": "E7C8FF" 482 | } 483 | }, 484 | "Skyblue": { 485 | "100": { 486 | "value": "#72bbff", 487 | "type": "color", 488 | "description": "72BBFF" 489 | }, 490 | "120": { 491 | "value": "#0073db", 492 | "type": "color", 493 | "description": "0073DB" 494 | }, 495 | "006": { 496 | "value": "#f7fbff", 497 | "type": "color", 498 | "description": "F7FBFF" 499 | }, 500 | "012": { 501 | "value": "#eef7ff", 502 | "type": "color", 503 | "description": "EEF7FF" 504 | }, 505 | "030": { 506 | "value": "#d5ebff", 507 | "type": "color", 508 | "description": "D5EBFF" 509 | } 510 | }, 511 | "Strawberry": { 512 | "100": { 513 | "value": "#ff625a", 514 | "type": "color", 515 | "description": "FF625A" 516 | }, 517 | "120": { 518 | "value": "#ee0a00", 519 | "type": "color", 520 | "description": "EE0A00" 521 | }, 522 | "006": { 523 | "value": "#fff6f5", 524 | "type": "color", 525 | "description": "FFF6F5" 526 | }, 527 | "012": { 528 | "value": "#ffeceb", 529 | "type": "color", 530 | "description": "FFECEB" 531 | }, 532 | "030": { 533 | "value": "#ffd0ce", 534 | "type": "color", 535 | "description": "FFD0CE" 536 | } 537 | }, 538 | "Violet": { 539 | "100": { 540 | "value": "#6648ff", 541 | "type": "color", 542 | "description": "6648FF" 543 | }, 544 | "120": { 545 | "value": "#1d00b2", 546 | "type": "color", 547 | "description": "1D00B2" 548 | }, 549 | "006": { 550 | "value": "#f6f4ff", 551 | "type": "color", 552 | "description": "F6F4FF" 553 | }, 554 | "012": { 555 | "value": "#ede9ff", 556 | "type": "color", 557 | "description": "EDE9FF" 558 | }, 559 | "030": { 560 | "value": "#d1c8ff", 561 | "type": "color", 562 | "description": "D1C8FF" 563 | } 564 | }, 565 | "White": { 566 | "100": { 567 | "value": "#ffffff", 568 | "type": "color", 569 | "description": "FFFFFF" 570 | } 571 | }, 572 | "Yellow": { 573 | "100": { 574 | "value": "#ffcc00", 575 | "type": "color", 576 | "description": "FFCC00" 577 | }, 578 | "120": { 579 | "value": "#b89300", 580 | "type": "color", 581 | "description": "B89300" 582 | }, 583 | "006": { 584 | "value": "#fffcf0", 585 | "type": "color", 586 | "description": "FFFCF0" 587 | }, 588 | "012": { 589 | "value": "#fff9e0", 590 | "type": "color", 591 | "description": "FFF9E0" 592 | }, 593 | "030": { 594 | "value": "#fff0b3", 595 | "type": "color", 596 | "description": "FFF0B3" 597 | } 598 | } 599 | } -------------------------------------------------------------------------------- /packages/ui/src/util/functional.ts: -------------------------------------------------------------------------------- 1 | export type Evaluatable = T | ((...args: C) => T); 2 | 3 | /** 4 | * Take a value or a function that returns a value and call it 5 | * with rest arguments if it is a function. 6 | * Example: 7 | * evaluate(value) -> value 8 | * evaluate(func, context) -> func(context) 9 | */ 10 | export function evaluate(valueOrFn: Evaluatable): ((...args: C) => T) { 11 | return (...args: C) => { 12 | if (typeof valueOrFn === 'function') { 13 | return (valueOrFn as (...args: C) => T)(...args); 14 | } 15 | return valueOrFn; 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /packages/ui/src/util/math.test.ts: -------------------------------------------------------------------------------- 1 | import { relative } from './math'; 2 | 3 | describe('math.relative', () => { 4 | it('returns ratio for positive numbers', () => { 5 | expect(relative({ value: 10, min: 0, max: 10 })).toBeCloseTo(1); 6 | expect(relative({ value: 5, min: 0, max: 20 })).toBeCloseTo(0.25); 7 | expect(relative({ value: -5, min: 20, max: 0 })).toBeCloseTo(0); 8 | }); 9 | it('returns ratio for range spanning negative and positive', () => { 10 | expect(relative({ value: -50, min: -100, max: 100 })).toBeCloseTo(0.25); 11 | expect(relative({ value: 0, min: -100, max: 100 })).toBeCloseTo(0.5); 12 | expect(relative({ value: 20, min: -100, max: 100 })).toBeCloseTo(0.6); 13 | }); 14 | it('returns ratio for negative numbers', () => { 15 | expect(relative({ value: -10, min: -20, max: -10 })).toBeCloseTo(1); 16 | expect(relative({ value: -17, min: -20, max: -10 })).toBeCloseTo(0.3); 17 | expect(relative({ value: -5, min: -20, max: -10 })).toBeCloseTo(1); 18 | expect(relative({ value: -25, min: -20, max: -10 })).toBeCloseTo(0); 19 | }); 20 | it('uses default values', () => { 21 | expect(relative({ value: 10, max: 100 })).toBe(0.1); 22 | expect(relative({ value: -10, max: 100 })).toBe(0); 23 | expect(relative({ value: -10, min: -100 })).toBe(0.9); 24 | }); 25 | it('returns garbage when min > max', () => { 26 | expect(relative({ value: 5, min: 20, max: 0 })).toBeCloseTo(0); 27 | }); 28 | it('returns garbage for default values', () => { 29 | expect(relative({ value: -1 })).toBe(0); 30 | expect(relative({ value: 10 })).toBe(1); 31 | }); 32 | }); -------------------------------------------------------------------------------- /packages/ui/src/util/math.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns relative value (ratio) between min and max, e.g. 3 | * relative({ value: 10, min: 0, max: 20 }) -> 0.5 4 | */ 5 | export function relative({ value, max = 0, min = 0 }: { value: number; max?: number; min?: number }) { 6 | if (value <= min) { 7 | return 0; 8 | } 9 | if (value >= max) { 10 | return 1; 11 | } 12 | return Math.abs(value - min) / Math.abs(max - min); 13 | } -------------------------------------------------------------------------------- /packages/ui/src/util/styled.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { ComponentProps, createElement, forwardRef, JSXElementConstructor, NamedExoticComponent, PropsWithoutRef } from 'react'; 3 | 4 | import { mergeProps } from '@react-aria/utils'; 5 | import { CSSProperties } from '@vanilla-extract/css'; 6 | 7 | import { Evaluatable, evaluate } from './functional'; 8 | 9 | type ClassName = Evaluatable; 10 | type Style = Evaluatable; 11 | 12 | /** 13 | * Combine a component with className and style, which can be static or computed from props. 14 | */ 15 | export function styled | undefined, C extends keyof JSX.IntrinsicElements | JSXElementConstructor>( 16 | base: C, 17 | className?: ClassName & ExtraProps>, 18 | style?: Style & ExtraProps>, 19 | ): NamedExoticComponent & ExtraProps>> { 20 | const StyledComponent = forwardRef((props: ComponentProps & ExtraProps, ref) => { 21 | return createElement(base, mergeProps( 22 | props as ComponentProps, 23 | { 24 | className: evaluate(className)(props), 25 | style: evaluate(style)(props), 26 | ref, 27 | }, 28 | )); 29 | }); 30 | const name = (base as any).displayName ?? (base as any).name ?? base; 31 | StyledComponent.displayName = `styled(${name})`; 32 | return StyledComponent; 33 | } -------------------------------------------------------------------------------- /packages/ui/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "tsBuildInfoFile": "./tsconfig.tsbuildinfo", 5 | "declarationDir": "dist" 6 | }, 7 | "extends": "@vanilla-extract-rollup-example/tsconfig/react-library.json", 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseBranch": "origin/main", 3 | "pipeline": { 4 | "build": { 5 | "dependsOn": ["^build"], 6 | "outputs": ["dist/**", ".next/**"] 7 | }, 8 | "lint": { 9 | "dependsOn": ["^build"], 10 | "outputs": [] 11 | }, 12 | "test": { 13 | "dependsOn": ["^build"], 14 | "outputs": [] 15 | }, 16 | "dev": { 17 | "dependsOn": ["^build"], 18 | "cache": false 19 | } 20 | } 21 | } 22 | --------------------------------------------------------------------------------