├── .eslintrc.cjs ├── .gitignore ├── README.md ├── components.json ├── index.html ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── react-querybuilder-shadcn-ui.png ├── src ├── App.jsx ├── assets │ └── react.svg ├── components │ ├── mode-toggle.tsx │ ├── react-querybuilder-shadcn-ui │ │ ├── ShadcnUiActionElement.tsx │ │ ├── ShadcnUiActionElementIcon.tsx │ │ ├── ShadcnUiDragHandle.tsx │ │ ├── ShadcnUiNotToggle.tsx │ │ ├── ShadcnUiValueEditor.tsx │ │ ├── ShadcnUiValueSelector.tsx │ │ ├── index.tsx │ │ ├── multiselect.tsx │ │ ├── styles.scss │ │ └── utils.tsx │ ├── theme-provider.tsx │ └── ui │ │ ├── button.tsx │ │ ├── checkbox.tsx │ │ ├── dropdown-menu.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── radio-group.tsx │ │ ├── select.tsx │ │ ├── switch.tsx │ │ └── textarea.tsx ├── index.css ├── lib │ └── utils.ts └── main.jsx ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## @react-querybuilder/shadcn-ui 2 | 3 |  4 | 5 | Unofficial [react-querybuilder](https://npmjs.com/package/react-querybuilder) components for [shadcn/ui](https://ui.shadcn.com). 6 | 7 | ## Installation 8 | 9 | Copy and paste the [src/components/react-querybuilder-shadcn-ui](https://github.com/jide/react-querybuilder-shadcn-ui/tree/main/src/components/react-querybuilder-shadcn-ui) in your project. 10 | 11 | ## Usage 12 | 13 | To render shadcn-ui-compatible components in the query builder, wrap the `` element in ``. 14 | 15 | ```tsx 16 | import { QueryBuilderShadcnUi } from "@/components/react-querybuilder-shadcn-ui"; 17 | import { QueryBuilder, RuleGroupType } from "react-querybuilder"; 18 | 19 | const fields = [ 20 | { name: "firstName", label: "First Name" }, 21 | { name: "lastName", label: "Last Name" }, 22 | ]; 23 | 24 | const App = () => { 25 | const [query, setQuery] = useState({ 26 | combinator: "and", 27 | rules: [], 28 | }); 29 | 30 | return ( 31 | 32 | 33 | 34 | ); 35 | }; 36 | ``` 37 | 38 | ## Notes 39 | 40 | - Some additional styling may be necessary, see [src/components/react-querybuilder-shadcn-ui/styles.scss](https://github.com/jide/react-querybuilder-shadcn-ui/tree/main/src/components/react-querybuilder-shadcn-ui/styles.scss) 41 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-querybuilder-shadcn-ui", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@radix-ui/react-checkbox": "^1.0.4", 14 | "@radix-ui/react-dropdown-menu": "^2.0.6", 15 | "@radix-ui/react-label": "^2.0.2", 16 | "@radix-ui/react-radio-group": "^1.1.3", 17 | "@radix-ui/react-select": "^2.0.0", 18 | "@radix-ui/react-slot": "^1.0.2", 19 | "@radix-ui/react-switch": "^1.0.3", 20 | "@react-querybuilder/dnd": "^7.2.0", 21 | "class-variance-authority": "^0.7.0", 22 | "clsx": "^2.1.1", 23 | "lucide-react": "^0.372.0", 24 | "react": "^18.2.0", 25 | "react-dnd": "^16.0.1", 26 | "react-dnd-html5-backend": "^16.0.1", 27 | "react-dom": "^18.2.0", 28 | "react-querybuilder": "^7.2.0", 29 | "tailwind-merge": "^2.3.0", 30 | "tailwindcss-animate": "^1.0.7" 31 | }, 32 | "devDependencies": { 33 | "@types/node": "^20.12.7", 34 | "@types/react": "^18.2.66", 35 | "@types/react-dom": "^18.2.22", 36 | "@vitejs/plugin-react": "^4.2.1", 37 | "autoprefixer": "^10.4.19", 38 | "eslint": "^8.57.0", 39 | "eslint-plugin-react": "^7.34.1", 40 | "eslint-plugin-react-hooks": "^4.6.0", 41 | "eslint-plugin-react-refresh": "^0.4.6", 42 | "postcss": "^8.4.38", 43 | "sass": "^1.75.0", 44 | "tailwindcss": "^3.4.3", 45 | "vite": "^5.2.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /react-querybuilder-shadcn-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jide/react-querybuilder-shadcn-ui/e141446f701135f6e020bc2a6c4a8915401d60ae/react-querybuilder-shadcn-ui.png -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { QueryBuilder } from "react-querybuilder"; 2 | import { QueryBuilderDnD } from "@react-querybuilder/dnd"; 3 | import * as ReactDnD from "react-dnd"; 4 | import * as ReactDndHtml5Backend from "react-dnd-html5-backend"; 5 | import { ThemeProvider } from "@/components/theme-provider"; 6 | import { QueryBuilderShadcnUi } from "@/components/react-querybuilder-shadcn-ui"; 7 | import { ModeToggle } from "@/components/mode-toggle"; 8 | 9 | const values = [ 10 | { name: "option1", label: "Option 1" }, 11 | { name: "option2", label: "Option 2" }, 12 | { name: "option3", label: "Option 3" }, 13 | { name: "option4", label: "Option 4" }, 14 | ]; 15 | 16 | const fields = [ 17 | { name: "text", label: "text", inputType: "text" }, 18 | { name: "select", label: "select", valueEditorType: "select", values }, 19 | { name: "checkbox", label: "checkbox", valueEditorType: "checkbox" }, 20 | { name: "radio", label: "radio", valueEditorType: "radio", values }, 21 | { name: "textarea", label: "textarea", valueEditorType: "textarea" }, 22 | { 23 | name: "multiselect", 24 | label: "multiselect", 25 | valueEditorType: "multiselect", 26 | values, 27 | }, 28 | { name: "date", label: "date", inputType: "date" }, 29 | { 30 | name: "datetime-local", 31 | label: "datetime-local", 32 | inputType: "datetime-local", 33 | }, 34 | { name: "time", label: "time", inputType: "time" }, 35 | { name: "field", label: "field", valueSources: ["field", "value"] }, 36 | ]; 37 | export const operators = [ 38 | { name: "=", label: "=" }, 39 | { name: "in", label: "in" }, 40 | { name: "between", label: "between" }, 41 | ]; 42 | export const defaultQuery = { 43 | combinator: "and", 44 | rules: [ 45 | { field: "text", operator: "=", value: "" }, 46 | { field: "select", operator: "=", value: "option2" }, 47 | { field: "checkbox", operator: "=", value: true }, 48 | { field: "switch", operator: "=", value: true }, 49 | { field: "radio", operator: "=", value: "option2" }, 50 | { field: "textarea", operator: "=", value: "" }, 51 | { field: "multiselect", operator: "in", value: ["option1", "option2"] }, 52 | { field: "date", operator: "=", value: "" }, 53 | { field: "datetime-local", operator: "=", value: "" }, 54 | { field: "time", operator: "=", value: "" }, 55 | { field: "text", operator: "between", value: "A,Z" }, 56 | { field: "select", operator: "between", value: "option2,option4" }, 57 | { field: "field", operator: "=", value: "text", valueSource: "field" }, 58 | ], 59 | }; 60 | export const NullComponent = () => null; 61 | 62 | export default function App() { 63 | return ( 64 | 65 | 66 | 67 | 68 | 69 | 70 | 76 | 77 | 78 | 79 | 80 | 81 | ); 82 | } 83 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/mode-toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Moon, Sun } from "lucide-react"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import { 5 | DropdownMenu, 6 | DropdownMenuContent, 7 | DropdownMenuItem, 8 | DropdownMenuTrigger, 9 | } from "@/components/ui/dropdown-menu"; 10 | import { useTheme } from "@/components/theme-provider"; 11 | 12 | export function ModeToggle() { 13 | const { setTheme } = useTheme(); 14 | 15 | return ( 16 | 17 | 18 | 19 | 20 | 21 | Toggle theme 22 | 23 | 24 | 25 | setTheme("light")}> 26 | Light 27 | 28 | setTheme("dark")}> 29 | Dark 30 | 31 | setTheme("system")}> 32 | System 33 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiActionElement.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import type { ComponentPropsWithoutRef } from "react"; 3 | import type { ActionWithRulesProps } from "react-querybuilder"; 4 | 5 | export type ShadcnUiActionProps = ActionWithRulesProps & 6 | ComponentPropsWithoutRef; 7 | 8 | export const ShadcnUiActionElement = ({ 9 | className, 10 | handleOnClick, 11 | label, 12 | title, 13 | disabled, 14 | disabledTranslation, 15 | // Props that should not be in extraProps 16 | testID: _testID, 17 | rules: _rules, 18 | level: _level, 19 | path: _path, 20 | context: _context, 21 | validation: _validation, 22 | ruleOrGroup: _ruleOrGroup, 23 | schema: _schema, 24 | ...extraProps 25 | }: ShadcnUiActionProps) => ( 26 | handleOnClick(e)} 31 | disabled={disabled && !disabledTranslation} 32 | {...extraProps} 33 | > 34 | {disabledTranslation && disabled ? disabledTranslation.label : label} 35 | 36 | ); 37 | 38 | ShadcnUiActionElement.displayName = "ShadcnUiActionElement"; 39 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiActionElementIcon.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import type { ComponentPropsWithoutRef } from "react"; 3 | import { cn } from "@/lib/utils"; 4 | import type { ActionWithRulesProps } from "react-querybuilder"; 5 | 6 | export type ShadcnUiActionProps = ActionWithRulesProps & 7 | ComponentPropsWithoutRef; 8 | 9 | export const ShadcnUiActionElementIcon = ({ 10 | className, 11 | handleOnClick, 12 | label, 13 | title, 14 | disabled, 15 | disabledTranslation, 16 | // Props that should not be in extraProps 17 | testID: _testID, 18 | rules: _rules, 19 | level: _level, 20 | path: _path, 21 | context: _context, 22 | validation: _validation, 23 | ruleOrGroup: _ruleOrGroup, 24 | schema: _schema, 25 | ...extraProps 26 | }: ShadcnUiActionProps) => ( 27 | handleOnClick(e)} 33 | disabled={disabled && !disabledTranslation} 34 | {...extraProps} 35 | > 36 | {disabledTranslation && disabled ? disabledTranslation.label : label} 37 | 38 | ); 39 | 40 | ShadcnUiActionElementIcon.displayName = "ShadcnUiActionElementIcon"; 41 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiDragHandle.tsx: -------------------------------------------------------------------------------- 1 | import { GripVertical } from "lucide-react"; 2 | import type { ComponentPropsWithRef } from "react"; 3 | import { forwardRef } from "react"; 4 | import type { DragHandleProps } from "react-querybuilder"; 5 | 6 | export type ShadcnUiDragHandleProps = DragHandleProps & 7 | ComponentPropsWithRef<"span">; 8 | 9 | export const ShadcnUiDragHandle = forwardRef< 10 | HTMLSpanElement, 11 | ShadcnUiDragHandleProps 12 | >(({ className, title }, dragRef) => ( 13 | 14 | 15 | 16 | )); 17 | 18 | ShadcnUiDragHandle.displayName = "ShadcnUiDragHandle"; 19 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiNotToggle.tsx: -------------------------------------------------------------------------------- 1 | import { Switch } from "@/components/ui/switch"; 2 | import type { ComponentPropsWithoutRef } from "react"; 3 | import type { NotToggleProps } from "react-querybuilder"; 4 | 5 | export type ChakraNotToggleProps = NotToggleProps & 6 | ComponentPropsWithoutRef; 7 | 8 | export const ShadcnUiNotToggle = ({ 9 | className, 10 | handleOnChange, 11 | checked, 12 | disabled, 13 | label, 14 | }: ChakraNotToggleProps) => { 15 | return ( 16 | 17 | 23 | {label} 24 | 25 | ); 26 | }; 27 | 28 | ShadcnUiNotToggle.displayName = "ShadcnUiNotToggle"; 29 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiValueEditor.tsx: -------------------------------------------------------------------------------- 1 | import type { ValueEditorProps } from "react-querybuilder"; 2 | import { cn } from "@/lib/utils"; 3 | import { Checkbox } from "@/components/ui/checkbox"; 4 | import { Input } from "@/components/ui/input"; 5 | import { Label } from "@/components/ui/label"; 6 | import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; 7 | import { Switch } from "@/components/ui/switch"; 8 | import { Textarea } from "@/components/ui/textarea"; 9 | import { 10 | getFirstOption, 11 | standardClassnames, 12 | useValueEditor, 13 | } from "react-querybuilder"; 14 | 15 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 16 | export type ShadcnUiValueEditorProps = ValueEditorProps & { 17 | extraProps?: Record; 18 | }; 19 | 20 | export const ShadcnUiValueEditor = (allProps: ShadcnUiValueEditorProps) => { 21 | const { 22 | fieldData, 23 | operator, 24 | value, 25 | handleOnChange, 26 | title, 27 | className, 28 | type, 29 | inputType, 30 | values = [], 31 | listsAsArrays, 32 | parseNumbers, 33 | separator, 34 | valueSource: _vs, 35 | testID, 36 | disabled, 37 | selectorComponent: SelectorComponent = allProps.schema.controls 38 | .valueSelector, 39 | extraProps, 40 | ...props 41 | } = allProps; 42 | 43 | const { valueAsArray, multiValueHandler } = useValueEditor({ 44 | handleOnChange, 45 | inputType, 46 | operator, 47 | value, 48 | type, 49 | listsAsArrays, 50 | parseNumbers, 51 | values, 52 | }); 53 | 54 | if (operator === "null" || operator === "notNull") { 55 | return null; 56 | } 57 | 58 | const placeHolderText = fieldData?.placeholder ?? ""; 59 | const inputTypeCoerced = ["in", "notIn"].includes(operator) 60 | ? "text" 61 | : inputType || "text"; 62 | 63 | if ( 64 | (operator === "between" || operator === "notBetween") && 65 | (type === "select" || type === "text") 66 | ) { 67 | const editors = ["from", "to"].map((key, i) => { 68 | if (type === "text") { 69 | return ( 70 | multiValueHandler(e.target.value, i)} 78 | {...extraProps} 79 | /> 80 | ); 81 | } 82 | return ( 83 | multiValueHandler(v, i)} 88 | disabled={disabled} 89 | value={valueAsArray[i] ?? getFirstOption(values)} 90 | options={values} 91 | listsAsArrays={listsAsArrays} 92 | /> 93 | ); 94 | }); 95 | return ( 96 | 101 | {editors[0]} 102 | {separator} 103 | {editors[1]} 104 | 105 | ); 106 | } 107 | 108 | switch (type) { 109 | case "select": 110 | return ( 111 | 120 | ); 121 | 122 | case "multiselect": 123 | return ( 124 | 134 | ); 135 | 136 | case "textarea": 137 | return ( 138 | handleOnChange(e.target.value)} 146 | {...extraProps} 147 | /> 148 | ); 149 | 150 | case "switch": 151 | return ( 152 | 160 | ); 161 | 162 | case "checkbox": 163 | return ( 164 | 173 | ); 174 | 175 | case "radio": 176 | return ( 177 | 185 | {values.map((v) => ( 186 | 187 | 188 | Default 189 | 190 | ))} 191 | 192 | ); 193 | } 194 | 195 | return ( 196 | handleOnChange(e.target.value)} 204 | {...extraProps} 205 | /> 206 | ); 207 | }; 208 | 209 | ShadcnUiValueEditor.displayName = "ShadcnUiValueEditor"; 210 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/ShadcnUiValueSelector.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentPropsWithoutRef } from "react"; 2 | import { 3 | Select, 4 | SelectContent, 5 | SelectTrigger, 6 | SelectValue, 7 | } from "@/components/ui/select"; 8 | import type { VersatileSelectorProps } from "react-querybuilder"; 9 | import { MultiSelect } from "./multiselect"; 10 | import { toSelectOptions } from "./utils"; 11 | 12 | export type ShadcnUiValueSelectorProps = VersatileSelectorProps & 13 | ComponentPropsWithoutRef; 14 | 15 | export const ShadcnUiValueSelector = ({ 16 | handleOnChange, 17 | options, 18 | value, 19 | title, 20 | disabled, 21 | // Props that should not be in extraProps 22 | testID: _testID, 23 | rule: _rule, 24 | rules: _rules, 25 | level: _level, 26 | path: _path, 27 | context: _context, 28 | validation: _validation, 29 | operator: _operator, 30 | field: _field, 31 | fieldData: _fieldData, 32 | multiple: _multiple, 33 | listsAsArrays: _listsAsArrays, 34 | schema: _schema, 35 | ...extraProps 36 | }: ShadcnUiValueSelectorProps) => { 37 | return _multiple ? ( 38 | 43 | ) : ( 44 | 50 | 51 | 52 | 53 | {toSelectOptions(options)} 54 | 55 | ); 56 | }; 57 | 58 | ShadcnUiValueSelector.displayName = "ShadcnUiValueSelector"; 59 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/index.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | X, 3 | Copy, 4 | Unlock, 5 | Lock, 6 | ChevronDown, 7 | ChevronUp, 8 | Plus, 9 | } from "lucide-react"; 10 | import type { 11 | Classnames, 12 | Controls, 13 | FullField, 14 | Translations, 15 | } from "react-querybuilder"; 16 | import { getCompatContextProvider } from "react-querybuilder"; 17 | import { ShadcnUiActionElement } from "./ShadcnUiActionElement"; 18 | import { ShadcnUiActionElementIcon } from "./ShadcnUiActionElementIcon"; 19 | import { ShadcnUiValueEditor } from "./ShadcnUiValueEditor"; 20 | import { ShadcnUiValueSelector } from "./ShadcnUiValueSelector"; 21 | import { ShadcnUiNotToggle } from "./ShadcnUiNotToggle"; 22 | import { ShadcnUiDragHandle } from "./ShadcnUiDragHandle"; 23 | 24 | import "./styles.scss"; 25 | 26 | export * from "./ShadcnUiActionElement"; 27 | export * from "./ShadcnUiValueSelector"; 28 | 29 | export const shadcnUiControlClassnames = { 30 | ruleGroup: "rounded-lg shadow-sm border bg-background", 31 | } satisfies Partial; 32 | 33 | export const shadcnUiControlElements = { 34 | actionElement: ShadcnUiActionElement, 35 | removeGroupAction: ShadcnUiActionElementIcon, 36 | removeRuleAction: ShadcnUiActionElementIcon, 37 | valueSelector: ShadcnUiValueSelector, 38 | valueEditor: ShadcnUiValueEditor, 39 | notToggle: ShadcnUiNotToggle, 40 | dragHandle: ShadcnUiDragHandle, 41 | } satisfies Partial>; 42 | 43 | export const shadcnUiTranslations = { 44 | addRule: { 45 | label: ( 46 | <> 47 | Rule 48 | > 49 | ), 50 | }, 51 | addGroup: { 52 | label: ( 53 | <> 54 | Group 55 | > 56 | ), 57 | }, 58 | removeGroup: { label: }, 59 | removeRule: { label: }, 60 | cloneRuleGroup: { label: }, 61 | cloneRule: { label: }, 62 | lockGroup: { label: }, 63 | lockRule: { label: }, 64 | lockGroupDisabled: { label: }, 65 | lockRuleDisabled: { label: }, 66 | shiftActionDown: { label: }, 67 | shiftActionUp: { label: }, 68 | } satisfies Partial; 69 | 70 | export const QueryBuilderShadcnUi = getCompatContextProvider({ 71 | key: "shadcn/ui", 72 | controlClassnames: shadcnUiControlClassnames, 73 | controlElements: shadcnUiControlElements, 74 | translations: shadcnUiTranslations, 75 | }); 76 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/multiselect.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { isOptionGroupArray } from "react-querybuilder"; 5 | import type { OptionList } from "react-querybuilder"; 6 | import { cn } from "@/lib/utils"; 7 | import { Button } from "@/components/ui/button"; 8 | import { 9 | DropdownMenu, 10 | DropdownMenuCheckboxItem, 11 | DropdownMenuContent, 12 | DropdownMenuLabel, 13 | DropdownMenuSeparator, 14 | DropdownMenuTrigger, 15 | } from "@/components/ui/dropdown-menu"; 16 | 17 | export type MultiSelectProps = { 18 | options?: OptionList; 19 | value: string[]; 20 | onValueChange: (value: string[]) => void; 21 | }; 22 | 23 | export function MultiSelect({ 24 | options = [], 25 | value, 26 | onValueChange, 27 | }: MultiSelectProps) { 28 | const toDropdownOptions = (list: OptionList) => 29 | isOptionGroupArray(list) 30 | ? list.map((og) => ( 31 | 32 | {og.label} 33 | 34 | {og.options.map((opt) => ( 35 | { 40 | onValueChange( 41 | checked 42 | ? [...value, opt.name ?? ""] 43 | : value.filter((v) => v !== opt.name) 44 | ); 45 | }} 46 | > 47 | {opt.label} 48 | 49 | ))} 50 | 51 | )) 52 | : Array.isArray(list) 53 | ? list.map((opt) => ( 54 | { 59 | onValueChange( 60 | checked 61 | ? [...value, opt.name] 62 | : value.filter((v) => v !== opt.name) 63 | ); 64 | }} 65 | > 66 | {opt.label} 67 | 68 | )) 69 | : null; 70 | 71 | return ( 72 | 73 | 74 | 0 && "px-1")} 77 | > 78 | {[...value].slice(0, 2).map((it) => ( 79 | 80 | {it} 81 | 82 | ))} 83 | {value.length > 2 && ( 84 | 85 | +{value.length - 2} 86 | 87 | )} 88 | {value.length === 0 && "Choose..."} 89 | 90 | 91 | 92 | {toDropdownOptions(options)} 93 | 94 | 95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'react-querybuilder/dist/query-builder-layout.scss'; 2 | 3 | .dark .queryBuilder { 4 | color-scheme: dark; 5 | } 6 | 7 | .queryBuilder { 8 | .ruleGroup-addGroup { 9 | & + button.ruleGroup-cloneGroup, 10 | & + button.ruleGroup-lock, 11 | & + button.ruleGroup-remove { 12 | margin-left: auto !important; 13 | } 14 | } 15 | .rule-operators, 16 | .rule-value { 17 | & + button.rule-cloneRule, 18 | & + button.rule-lock, 19 | & + button.rule-remove { 20 | margin-left: auto !important; 21 | } 22 | } 23 | .ruleGroup .rule .rule-value:has(.rule-value-list-item) { 24 | gap: 0; 25 | } 26 | &.queryBuilder-branches { 27 | .rule, 28 | .ruleGroup .ruleGroup { 29 | &::before, 30 | &::after { 31 | border-color: hsl(var(--input)); 32 | } 33 | } 34 | 35 | .betweenRules { 36 | &::before { 37 | border-color: hsl(var(--input)); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /src/components/react-querybuilder-shadcn-ui/utils.tsx: -------------------------------------------------------------------------------- 1 | import { SelectGroup, SelectItem, SelectLabel } from "@/components/ui/select"; 2 | import type { OptionList } from "react-querybuilder"; 3 | import { isOptionGroupArray } from "react-querybuilder"; 4 | 5 | export const toSelectOptions = (list: OptionList) => 6 | isOptionGroupArray(list) 7 | ? list.map((og) => ( 8 | 9 | {og.label} 10 | {og.options.map((opt) => ( 11 | 16 | {opt.label} 17 | 18 | ))} 19 | 20 | )) 21 | : Array.isArray(list) 22 | ? list.map((opt) => ( 23 | 28 | {opt.label} 29 | 30 | )) 31 | : null; 32 | -------------------------------------------------------------------------------- /src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, useContext, useEffect, useState } from "react"; 2 | 3 | type Theme = "dark" | "light" | "system"; 4 | 5 | type ThemeProviderProps = { 6 | children: React.ReactNode; 7 | defaultTheme?: Theme; 8 | storageKey?: string; 9 | }; 10 | 11 | type ThemeProviderState = { 12 | theme: Theme; 13 | setTheme: (theme: Theme) => void; 14 | }; 15 | 16 | const initialState: ThemeProviderState = { 17 | theme: "system", 18 | setTheme: () => null, 19 | }; 20 | 21 | const ThemeProviderContext = createContext(initialState); 22 | 23 | export function ThemeProvider({ 24 | children, 25 | defaultTheme = "system", 26 | storageKey = "vite-ui-theme", 27 | ...props 28 | }: ThemeProviderProps) { 29 | const [theme, setTheme] = useState( 30 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme 31 | ); 32 | 33 | useEffect(() => { 34 | const root = window.document.documentElement; 35 | 36 | root.classList.remove("light", "dark"); 37 | 38 | if (theme === "system") { 39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") 40 | .matches 41 | ? "dark" 42 | : "light"; 43 | 44 | root.classList.add(systemTheme); 45 | return; 46 | } 47 | 48 | root.classList.add(theme); 49 | }, [theme]); 50 | 51 | const value = { 52 | theme, 53 | setTheme: (theme: Theme) => { 54 | localStorage.setItem(storageKey, theme); 55 | setTheme(theme); 56 | }, 57 | }; 58 | 59 | return ( 60 | 61 | {children} 62 | 63 | ); 64 | } 65 | 66 | export const useTheme = () => { 67 | const context = useContext(ThemeProviderContext); 68 | 69 | if (context === undefined) 70 | throw new Error("useTheme must be used within a ThemeProvider"); 71 | 72 | return context; 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: "bg-primary text-primary-foreground hover:bg-primary/90", 13 | destructive: 14 | "bg-destructive text-destructive-foreground hover:bg-destructive/90", 15 | outline: 16 | "border border-input bg-background hover:bg-accent hover:text-accent-foreground", 17 | secondary: 18 | "bg-secondary text-secondary-foreground hover:bg-secondary/80", 19 | ghost: "hover:bg-accent hover:text-accent-foreground", 20 | link: "text-primary underline-offset-4 hover:underline", 21 | }, 22 | size: { 23 | default: "h-10 px-4 py-2", 24 | sm: "h-9 rounded-md px-3", 25 | lg: "h-11 rounded-md px-8", 26 | icon: "h-10 w-10", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ) 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps { 39 | asChild?: boolean 40 | } 41 | 42 | const Button = React.forwardRef( 43 | ({ className, variant, size, asChild = false, ...props }, ref) => { 44 | const Comp = asChild ? Slot : "button" 45 | return ( 46 | 51 | ) 52 | } 53 | ) 54 | Button.displayName = "Button" 55 | 56 | export { Button, buttonVariants } 57 | -------------------------------------------------------------------------------- /src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as CheckboxPrimitive from "@radix-ui/react-checkbox" 3 | import { Check } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Checkbox = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => ( 11 | 19 | 22 | 23 | 24 | 25 | )) 26 | Checkbox.displayName = CheckboxPrimitive.Root.displayName 27 | 28 | export { Checkbox } 29 | -------------------------------------------------------------------------------- /src/components/ui/dropdown-menu.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" 3 | import { Check, ChevronRight, Circle } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const DropdownMenu = DropdownMenuPrimitive.Root 8 | 9 | const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger 10 | 11 | const DropdownMenuGroup = DropdownMenuPrimitive.Group 12 | 13 | const DropdownMenuPortal = DropdownMenuPrimitive.Portal 14 | 15 | const DropdownMenuSub = DropdownMenuPrimitive.Sub 16 | 17 | const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup 18 | 19 | const DropdownMenuSubTrigger = React.forwardRef< 20 | React.ElementRef, 21 | React.ComponentPropsWithoutRef & { 22 | inset?: boolean 23 | } 24 | >(({ className, inset, children, ...props }, ref) => ( 25 | 34 | {children} 35 | 36 | 37 | )) 38 | DropdownMenuSubTrigger.displayName = 39 | DropdownMenuPrimitive.SubTrigger.displayName 40 | 41 | const DropdownMenuSubContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, ...props }, ref) => ( 45 | 53 | )) 54 | DropdownMenuSubContent.displayName = 55 | DropdownMenuPrimitive.SubContent.displayName 56 | 57 | const DropdownMenuContent = React.forwardRef< 58 | React.ElementRef, 59 | React.ComponentPropsWithoutRef 60 | >(({ className, sideOffset = 4, ...props }, ref) => ( 61 | 62 | 71 | 72 | )) 73 | DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName 74 | 75 | const DropdownMenuItem = React.forwardRef< 76 | React.ElementRef, 77 | React.ComponentPropsWithoutRef & { 78 | inset?: boolean 79 | } 80 | >(({ className, inset, ...props }, ref) => ( 81 | 90 | )) 91 | DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName 92 | 93 | const DropdownMenuCheckboxItem = React.forwardRef< 94 | React.ElementRef, 95 | React.ComponentPropsWithoutRef 96 | >(({ className, children, checked, ...props }, ref) => ( 97 | 106 | 107 | 108 | 109 | 110 | 111 | {children} 112 | 113 | )) 114 | DropdownMenuCheckboxItem.displayName = 115 | DropdownMenuPrimitive.CheckboxItem.displayName 116 | 117 | const DropdownMenuRadioItem = React.forwardRef< 118 | React.ElementRef, 119 | React.ComponentPropsWithoutRef 120 | >(({ className, children, ...props }, ref) => ( 121 | 129 | 130 | 131 | 132 | 133 | 134 | {children} 135 | 136 | )) 137 | DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName 138 | 139 | const DropdownMenuLabel = React.forwardRef< 140 | React.ElementRef, 141 | React.ComponentPropsWithoutRef & { 142 | inset?: boolean 143 | } 144 | >(({ className, inset, ...props }, ref) => ( 145 | 154 | )) 155 | DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName 156 | 157 | const DropdownMenuSeparator = React.forwardRef< 158 | React.ElementRef, 159 | React.ComponentPropsWithoutRef 160 | >(({ className, ...props }, ref) => ( 161 | 166 | )) 167 | DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName 168 | 169 | const DropdownMenuShortcut = ({ 170 | className, 171 | ...props 172 | }: React.HTMLAttributes) => { 173 | return ( 174 | 178 | ) 179 | } 180 | DropdownMenuShortcut.displayName = "DropdownMenuShortcut" 181 | 182 | export { 183 | DropdownMenu, 184 | DropdownMenuTrigger, 185 | DropdownMenuContent, 186 | DropdownMenuItem, 187 | DropdownMenuCheckboxItem, 188 | DropdownMenuRadioItem, 189 | DropdownMenuLabel, 190 | DropdownMenuSeparator, 191 | DropdownMenuShortcut, 192 | DropdownMenuGroup, 193 | DropdownMenuPortal, 194 | DropdownMenuSub, 195 | DropdownMenuSubContent, 196 | DropdownMenuSubTrigger, 197 | DropdownMenuRadioGroup, 198 | } 199 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /src/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as RadioGroupPrimitive from "@radix-ui/react-radio-group" 3 | import { Circle } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const RadioGroup = React.forwardRef< 8 | React.ElementRef, 9 | React.ComponentPropsWithoutRef 10 | >(({ className, ...props }, ref) => { 11 | return ( 12 | 17 | ) 18 | }) 19 | RadioGroup.displayName = RadioGroupPrimitive.Root.displayName 20 | 21 | const RadioGroupItem = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => { 25 | return ( 26 | 34 | 35 | 36 | 37 | 38 | ) 39 | }) 40 | RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName 41 | 42 | export { RadioGroup, RadioGroupItem } 43 | -------------------------------------------------------------------------------- /src/components/ui/select.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SelectPrimitive from "@radix-ui/react-select" 3 | import { Check, ChevronDown, ChevronUp } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Select = SelectPrimitive.Root 8 | 9 | const SelectGroup = SelectPrimitive.Group 10 | 11 | const SelectValue = SelectPrimitive.Value 12 | 13 | const SelectTrigger = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef 16 | >(({ className, children, ...props }, ref) => ( 17 | span]:line-clamp-1", 21 | className 22 | )} 23 | {...props} 24 | > 25 | {children} 26 | 27 | 28 | 29 | 30 | )) 31 | SelectTrigger.displayName = SelectPrimitive.Trigger.displayName 32 | 33 | const SelectScrollUpButton = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | 46 | 47 | )) 48 | SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName 49 | 50 | const SelectScrollDownButton = React.forwardRef< 51 | React.ElementRef, 52 | React.ComponentPropsWithoutRef 53 | >(({ className, ...props }, ref) => ( 54 | 62 | 63 | 64 | )) 65 | SelectScrollDownButton.displayName = 66 | SelectPrimitive.ScrollDownButton.displayName 67 | 68 | const SelectContent = React.forwardRef< 69 | React.ElementRef, 70 | React.ComponentPropsWithoutRef 71 | >(({ className, children, position = "popper", ...props }, ref) => ( 72 | 73 | 84 | 85 | 92 | {children} 93 | 94 | 95 | 96 | 97 | )) 98 | SelectContent.displayName = SelectPrimitive.Content.displayName 99 | 100 | const SelectLabel = React.forwardRef< 101 | React.ElementRef, 102 | React.ComponentPropsWithoutRef 103 | >(({ className, ...props }, ref) => ( 104 | 109 | )) 110 | SelectLabel.displayName = SelectPrimitive.Label.displayName 111 | 112 | const SelectItem = React.forwardRef< 113 | React.ElementRef, 114 | React.ComponentPropsWithoutRef 115 | >(({ className, children, ...props }, ref) => ( 116 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | {children} 131 | 132 | )) 133 | SelectItem.displayName = SelectPrimitive.Item.displayName 134 | 135 | const SelectSeparator = React.forwardRef< 136 | React.ElementRef, 137 | React.ComponentPropsWithoutRef 138 | >(({ className, ...props }, ref) => ( 139 | 144 | )) 145 | SelectSeparator.displayName = SelectPrimitive.Separator.displayName 146 | 147 | export { 148 | Select, 149 | SelectGroup, 150 | SelectValue, 151 | SelectTrigger, 152 | SelectContent, 153 | SelectLabel, 154 | SelectItem, 155 | SelectSeparator, 156 | SelectScrollUpButton, 157 | SelectScrollDownButton, 158 | } 159 | -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SwitchPrimitives from "@radix-ui/react-switch" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Switch = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | 23 | 24 | )) 25 | Switch.displayName = SwitchPrimitives.Root.displayName 26 | 27 | export { Switch } 28 | -------------------------------------------------------------------------------- /src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface TextareaProps 6 | extends React.TextareaHTMLAttributes {} 7 | 8 | const Textarea = React.forwardRef( 9 | ({ className, ...props }, ref) => { 10 | return ( 11 | 19 | ) 20 | } 21 | ) 22 | Textarea.displayName = "Textarea" 23 | 24 | export { Textarea } 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | :root { 7 | --background: 0 0% 100%; 8 | --foreground: 222.2 84% 4.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 222.2 84% 4.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 222.2 84% 4.9%; 15 | 16 | --primary: 222.2 47.4% 11.2%; 17 | --primary-foreground: 210 40% 98%; 18 | 19 | --secondary: 210 40% 96.1%; 20 | --secondary-foreground: 222.2 47.4% 11.2%; 21 | 22 | --muted: 210 40% 96.1%; 23 | --muted-foreground: 215.4 16.3% 46.9%; 24 | 25 | --accent: 210 40% 96.1%; 26 | --accent-foreground: 222.2 47.4% 11.2%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 210 40% 98%; 30 | 31 | --border: 214.3 31.8% 91.4%; 32 | --input: 214.3 31.8% 91.4%; 33 | --ring: 222.2 84% 4.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 222.2 84% 4.9%; 40 | --foreground: 210 40% 98%; 41 | 42 | --card: 222.2 84% 4.9%; 43 | --card-foreground: 210 40% 98%; 44 | 45 | --popover: 222.2 84% 4.9%; 46 | --popover-foreground: 210 40% 98%; 47 | 48 | --primary: 210 40% 98%; 49 | --primary-foreground: 222.2 47.4% 11.2%; 50 | 51 | --secondary: 217.2 32.6% 17.5%; 52 | --secondary-foreground: 210 40% 98%; 53 | 54 | --muted: 217.2 32.6% 17.5%; 55 | --muted-foreground: 215 20.2% 65.1%; 56 | 57 | --accent: 217.2 32.6% 17.5%; 58 | --accent-foreground: 210 40% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 210 40% 98%; 62 | 63 | --border: 217.2 32.6% 17.5%; 64 | --input: 217.2 32.6% 17.5%; 65 | --ring: 212.7 26.8% 83.9%; 66 | } 67 | } 68 | 69 | @layer base { 70 | * { 71 | @apply border-border; 72 | } 73 | body { 74 | @apply bg-background text-foreground; 75 | } 76 | } -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 5 | prefix: "", 6 | theme: { 7 | container: { 8 | center: true, 9 | padding: "2rem", 10 | screens: { 11 | "2xl": "1400px", 12 | }, 13 | }, 14 | extend: { 15 | colors: { 16 | border: "hsl(var(--border))", 17 | input: "hsl(var(--input))", 18 | ring: "hsl(var(--ring))", 19 | background: "hsl(var(--background))", 20 | foreground: "hsl(var(--foreground))", 21 | primary: { 22 | DEFAULT: "hsl(var(--primary))", 23 | foreground: "hsl(var(--primary-foreground))", 24 | }, 25 | secondary: { 26 | DEFAULT: "hsl(var(--secondary))", 27 | foreground: "hsl(var(--secondary-foreground))", 28 | }, 29 | destructive: { 30 | DEFAULT: "hsl(var(--destructive))", 31 | foreground: "hsl(var(--destructive-foreground))", 32 | }, 33 | muted: { 34 | DEFAULT: "hsl(var(--muted))", 35 | foreground: "hsl(var(--muted-foreground))", 36 | }, 37 | accent: { 38 | DEFAULT: "hsl(var(--accent))", 39 | foreground: "hsl(var(--accent-foreground))", 40 | }, 41 | popover: { 42 | DEFAULT: "hsl(var(--popover))", 43 | foreground: "hsl(var(--popover-foreground))", 44 | }, 45 | card: { 46 | DEFAULT: "hsl(var(--card))", 47 | foreground: "hsl(var(--card-foreground))", 48 | }, 49 | }, 50 | borderRadius: { 51 | lg: "var(--radius)", 52 | md: "calc(var(--radius) - 2px)", 53 | sm: "calc(var(--radius) - 4px)", 54 | }, 55 | keyframes: { 56 | "accordion-down": { 57 | from: { height: "0" }, 58 | to: { height: "var(--radix-accordion-content-height)" }, 59 | }, 60 | "accordion-up": { 61 | from: { height: "var(--radix-accordion-content-height)" }, 62 | to: { height: "0" }, 63 | }, 64 | }, 65 | animation: { 66 | "accordion-down": "accordion-down 0.2s ease-out", 67 | "accordion-up": "accordion-up 0.2s ease-out", 68 | }, 69 | }, 70 | }, 71 | plugins: [require("tailwindcss-animate")], 72 | }; 73 | -------------------------------------------------------------------------------- /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 | "baseUrl": ".", 24 | "paths": { 25 | "@/*": [ 26 | "./src/*" 27 | ] 28 | } 29 | }, 30 | "include": ["src"], 31 | "references": [{ "path": "./tsconfig.node.json" }] 32 | } 33 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import react from "@vitejs/plugin-react"; 3 | import { defineConfig } from "vite"; 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }); 13 | --------------------------------------------------------------------------------