├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── .stackblitzrc ├── .storybook ├── main.ts └── preview.ts ├── README.md ├── components.json ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── vite.svg ├── src ├── App.css ├── App.tsx ├── assets │ └── react.svg ├── components │ └── ui │ │ ├── accordion │ │ ├── accordion.stories.tsx │ │ └── accordion.tsx │ │ ├── avatar │ │ ├── avatar.stories.tsx │ │ └── avatar.tsx │ │ ├── button │ │ ├── button.stories.tsx │ │ └── button.tsx │ │ └── menubar │ │ ├── menubar.stories.tsx │ │ └── menubar.tsx ├── index.css ├── lib │ └── utils.ts ├── main.tsx └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: ['eslint:recommended', 'plugin:react/recommended', 'plugin:react/jsx-runtime', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'], 5 | ignorePatterns: ['dist', '.eslintrc.cjs'], 6 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 7 | settings: { react: { version: '18.2' } }, 8 | plugins: ['react-refresh'], 9 | rules: { 10 | 'react/jsx-no-target-blank': 'off', 11 | 'react/prop-types': 'off', 12 | 'react-refresh/only-export-components': [ 13 | 'warn', 14 | { allowConstantExport: true }, 15 | ], 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": true, 4 | "singleQuote": true, 5 | "trailingComma": "all" 6 | } -------------------------------------------------------------------------------- /.stackblitzrc: -------------------------------------------------------------------------------- 1 | { 2 | "startCommand": "npm run storybook dev -p 6006" 3 | } -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/react-vite'; 2 | 3 | const config: StorybookConfig = { 4 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 5 | addons: [ 6 | '@storybook/addon-onboarding', 7 | '@storybook/addon-links', 8 | '@storybook/addon-essentials', 9 | '@chromatic-com/storybook', 10 | '@storybook/addon-interactions', 11 | '@stackblitz/storybook-addon-stackblitz', 12 | ], 13 | framework: { 14 | name: '@storybook/react-vite', 15 | options: {}, 16 | }, 17 | docs: { 18 | autodocs: 'tag', 19 | }, 20 | }; 21 | export default config; 22 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | import type { Preview } from '@storybook/react'; 2 | import '../src/index.css'; 3 | 4 | const preview: Preview = { 5 | parameters: { 6 | controls: { 7 | matchers: { 8 | color: /(background|color)$/i, 9 | date: /Date$/i, 10 | }, 11 | }, 12 | repositoryUrl: 'https://github.com/stackblitz/storybook-addon-demo', 13 | }, 14 | }; 15 | 16 | export default preview; 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StackBlitz Storybook addon demo 2 | 3 | A simple Storybook component. The stack: 4 | - [Vite](https://vitejs.dev/) 5 | - [Storybook](https://storybook.js.org/) 6 | - [MUI](https://mui.com/) (react) 7 | -------------------------------------------------------------------------------- /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": "zinc", 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 + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-react-starter", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "start": "npm run storybook", 8 | "dev": "vite", 9 | "build": "vite build", 10 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 11 | "preview": "vite preview", 12 | "storybook": "storybook dev -p 6006", 13 | "build-storybook": "storybook build" 14 | }, 15 | "dependencies": { 16 | "@radix-ui/react-accordion": "^1.1.2", 17 | "@radix-ui/react-avatar": "^1.0.4", 18 | "@radix-ui/react-icons": "^1.3.0", 19 | "@radix-ui/react-menubar": "^1.0.4", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@stackblitz/storybook-addon-stackblitz": "^0.0.5", 22 | "class-variance-authority": "^0.7.0", 23 | "clsx": "^2.1.0", 24 | "lucide-react": "^0.360.0", 25 | "react": "^18.2.0", 26 | "react-dom": "^18.2.0", 27 | "tailwind-merge": "^2.2.2", 28 | "tailwindcss-animate": "^1.0.7" 29 | }, 30 | "devDependencies": { 31 | "@chromatic-com/storybook": "^1.2.20", 32 | "@storybook/addon-essentials": "^8.0.2", 33 | "@storybook/addon-interactions": "^8.0.2", 34 | "@storybook/addon-links": "^8.0.2", 35 | "@storybook/addon-onboarding": "^8.0.2", 36 | "@storybook/blocks": "^8.0.2", 37 | "@storybook/react": "^8.0.2", 38 | "@storybook/react-vite": "^8.0.2", 39 | "@storybook/test": "^8.0.2", 40 | "@types/node": "^20.11.30", 41 | "@types/react": "^18.2.66", 42 | "@types/react-dom": "^18.2.22", 43 | "@typescript-eslint/eslint-plugin": "^7.2.0", 44 | "@typescript-eslint/parser": "^7.2.0", 45 | "@vitejs/plugin-react": "^4.2.1", 46 | "autoprefixer": "^10.4.19", 47 | "eslint": "^8.57.0", 48 | "eslint-plugin-react": "^7.34.0", 49 | "eslint-plugin-react-hooks": "^4.6.0", 50 | "eslint-plugin-react-refresh": "^0.4.6", 51 | "eslint-plugin-storybook": "^0.8.0", 52 | "postcss": "^8.4.38", 53 | "prop-types": "^15.8.1", 54 | "storybook": "^8.0.2", 55 | "tailwindcss": "^3.4.1", 56 | "typescript": "^5.2.2", 57 | "vite": "^5.2.0" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | max-width: 1280px; 3 | margin: 0 auto; 4 | padding: 2rem; 5 | text-align: center; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #646cffaa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #61dafbaa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | } 39 | 40 | .read-the-docs { 41 | color: #888; 42 | } 43 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import { Button } from '@/components/ui/button/button'; 3 | import { 4 | Avatar, 5 | AvatarFallback, 6 | AvatarImage, 7 | } from '@/components/ui/avatar/avatar'; 8 | import { 9 | Accordion, 10 | AccordionContent, 11 | AccordionItem, 12 | AccordionTrigger, 13 | } from '@/components/ui/accordion/accordion'; 14 | import { 15 | Menubar, 16 | MenubarMenu, 17 | MenubarTrigger, 18 | } from '@/components/ui/menubar/menubar'; 19 | 20 | function App() { 21 | return ( 22 | <> 23 | 24 | 25 | File 26 | 27 | 28 | Edit 29 | 30 | 31 | View 32 | 33 | 34 | Profile 35 | 36 | 37 | 38 | 39 | 43 | CN 44 | 45 | 46 | 47 | 48 | Is it accessible? 49 | 50 | Yes. It adheres to the WAI-ARIA design pattern. 51 | 52 | 53 | 54 | Is it styled? 55 | 56 | Yes. It comes with default styles that matches the other 57 | components' aesthetic. 58 | 59 | 60 | 61 | Is it animated? 62 | 63 | Yes. It's animated by default, but you can disable it if you prefer. 64 | 65 | 66 | 67 | 68 | ); 69 | } 70 | 71 | export default App; 72 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/accordion/accordion.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import { 3 | Accordion, 4 | AccordionContent, 5 | AccordionItem, 6 | AccordionTrigger, 7 | } from './accordion'; 8 | 9 | const meta: Meta = { 10 | title: 'Demo/Accordion', 11 | component: Accordion, 12 | subcomponents: { 13 | AccordionContent, 14 | AccordionItem, 15 | AccordionTrigger, 16 | }, 17 | parameters: { 18 | filePath: 'src/components/ui/accordion/accordion.stories.tsx', 19 | }, 20 | }; 21 | 22 | export default meta; 23 | type Story = StoryObj; 24 | 25 | export const Default: Story = { 26 | render: () => ( 27 | 28 | 29 | First item 30 | 31 | Only one item opens at once. This is the content of the first 32 | accordion item. 33 | 34 | 35 | 36 | Second item 37 | 38 | Here is the content of the second accordion item. 39 | 40 | 41 | 42 | ), 43 | }; 44 | 45 | export const Multiple: Story = { 46 | render: () => ( 47 | 48 | 49 | First item 50 | 51 | Multiple items can be opened at the same time. This is the content of 52 | the first accordion item. 53 | 54 | 55 | 56 | Second item 57 | 58 | Here is the content of the second accordion item. 59 | 60 | 61 | 62 | ), 63 | }; 64 | -------------------------------------------------------------------------------- /src/components/ui/accordion/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 3 | import { ChevronDown } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Accordion = AccordionPrimitive.Root 8 | 9 | const AccordionItem = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 18 | )) 19 | AccordionItem.displayName = "AccordionItem" 20 | 21 | const AccordionTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, children, ...props }, ref) => ( 25 | 26 | svg]:rotate-180", 30 | className 31 | )} 32 | {...props} 33 | > 34 | {children} 35 | 36 | 37 | 38 | )) 39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 40 | 41 | const AccordionContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, children, ...props }, ref) => ( 45 | 50 |
{children}
51 |
52 | )) 53 | 54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 55 | 56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 57 | -------------------------------------------------------------------------------- /src/components/ui/avatar/avatar.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import { Avatar, AvatarFallback, AvatarImage } from './avatar'; 3 | 4 | const meta: Meta = { 5 | title: 'Demo/Avatar', 6 | component: Avatar, 7 | subcomponents: { AvatarFallback, AvatarImage }, 8 | parameters: { 9 | filePath: 'src/components/ui/avatar/avatar.stories.tsx', 10 | }, 11 | }; 12 | 13 | export default meta; 14 | type Story = StoryObj; 15 | 16 | export const Default: Story = { 17 | args: { 18 | children: , 19 | }, 20 | }; 21 | 22 | export const Fallback: Story = { 23 | args: { 24 | children: ( 25 | <> 26 | 27 | JD 28 | 29 | ), 30 | }, 31 | }; 32 | 33 | export const CustomSize: Story = { 34 | render: (props) => ( 35 | 36 | 37 | 38 | ), 39 | args: { 40 | className: 'w-24 h-24', 41 | }, 42 | }; 43 | -------------------------------------------------------------------------------- /src/components/ui/avatar/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )) 19 | Avatar.displayName = AvatarPrimitive.Root.displayName 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )) 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )) 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 47 | 48 | export { Avatar, AvatarImage, AvatarFallback } 49 | -------------------------------------------------------------------------------- /src/components/ui/button/button.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import { ReloadIcon } from '@radix-ui/react-icons'; 3 | import { Button } from './button'; 4 | 5 | const meta: Meta = { 6 | title: 'Demo/Button', 7 | component: Button, 8 | subcomponents: { ReloadIcon }, 9 | parameters: { 10 | filePath: 'src/components/ui/button/button.stories.tsx', 11 | }, 12 | }; 13 | 14 | export default meta; 15 | type Story = StoryObj; 16 | 17 | export const Default: Story = { 18 | args: { 19 | children: 'Click me', 20 | }, 21 | }; 22 | 23 | export const Outline: Story = { 24 | args: { 25 | variant: 'outline', 26 | children: 'Click me', 27 | }, 28 | }; 29 | 30 | export const WithIcon: Story = { 31 | args: { 32 | disabled: true, 33 | }, 34 | render: (args) => ( 35 | 39 | ), 40 | }; 41 | -------------------------------------------------------------------------------- /src/components/ui/button/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/menubar/menubar.stories.tsx: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/react'; 2 | import { 3 | Menubar, 4 | MenubarCheckboxItem, 5 | MenubarContent, 6 | MenubarItem, 7 | MenubarMenu, 8 | MenubarRadioGroup, 9 | MenubarRadioItem, 10 | MenubarSeparator, 11 | MenubarShortcut, 12 | MenubarSub, 13 | MenubarSubContent, 14 | MenubarSubTrigger, 15 | MenubarTrigger, 16 | } from './menubar'; 17 | 18 | const meta: Meta = { 19 | title: 'Demo/Menubar', 20 | component: Menubar, 21 | subcomponents: { 22 | MenubarCheckboxItem, 23 | MenubarContent, 24 | MenubarItem, 25 | MenubarMenu, 26 | MenubarRadioGroup, 27 | MenubarRadioItem, 28 | MenubarSeparator, 29 | MenubarShortcut, 30 | MenubarSub, 31 | MenubarSubContent, 32 | MenubarSubTrigger, 33 | MenubarTrigger, 34 | }, 35 | parameters: { 36 | filePath: 'src/components/ui/menubar/menubar.stories.tsx', 37 | }, 38 | }; 39 | 40 | export default meta; 41 | type Story = StoryObj; 42 | 43 | export const Default: Story = { 44 | render: () => ( 45 | 46 | 47 | File 48 | 49 | 50 | New Tab ⌘T 51 | 52 | 53 | New Window ⌘N 54 | 55 | New Incognito Window 56 | 57 | 58 | Share 59 | 60 | Email link 61 | Messages 62 | Notes 63 | 64 | 65 | 66 | 67 | Print... ⌘P 68 | 69 | 70 | 71 | 72 | Edit 73 | 74 | 75 | Undo ⌘Z 76 | 77 | 78 | Redo ⇧⌘Z 79 | 80 | 81 | 82 | Find 83 | 84 | Search the web 85 | 86 | Find... 87 | Find Next 88 | Find Previous 89 | 90 | 91 | 92 | Cut 93 | Copy 94 | Paste 95 | 96 | 97 | 98 | View 99 | 100 | Always Show Bookmarks Bar 101 | 102 | Always Show Full URLs 103 | 104 | 105 | 106 | Reload ⌘R 107 | 108 | 109 | Force Reload ⇧⌘R 110 | 111 | 112 | Toggle Fullscreen 113 | 114 | Hide Sidebar 115 | 116 | 117 | 118 | Profiles 119 | 120 | 121 | Andy 122 | Benoit 123 | Luis 124 | 125 | 126 | Edit... 127 | 128 | Add Profile... 129 | 130 | 131 | 132 | ), 133 | }; 134 | -------------------------------------------------------------------------------- /src/components/ui/menubar/menubar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as MenubarPrimitive from "@radix-ui/react-menubar" 3 | import { Check, ChevronRight, Circle } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const MenubarMenu = MenubarPrimitive.Menu 8 | 9 | const MenubarGroup = MenubarPrimitive.Group 10 | 11 | const MenubarPortal = MenubarPrimitive.Portal 12 | 13 | const MenubarSub = MenubarPrimitive.Sub 14 | 15 | const MenubarRadioGroup = MenubarPrimitive.RadioGroup 16 | 17 | const Menubar = React.forwardRef< 18 | React.ElementRef, 19 | React.ComponentPropsWithoutRef 20 | >(({ className, ...props }, ref) => ( 21 | 29 | )) 30 | Menubar.displayName = MenubarPrimitive.Root.displayName 31 | 32 | const MenubarTrigger = React.forwardRef< 33 | React.ElementRef, 34 | React.ComponentPropsWithoutRef 35 | >(({ className, ...props }, ref) => ( 36 | 44 | )) 45 | MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName 46 | 47 | const MenubarSubTrigger = React.forwardRef< 48 | React.ElementRef, 49 | React.ComponentPropsWithoutRef & { 50 | inset?: boolean 51 | } 52 | >(({ className, inset, children, ...props }, ref) => ( 53 | 62 | {children} 63 | 64 | 65 | )) 66 | MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName 67 | 68 | const MenubarSubContent = React.forwardRef< 69 | React.ElementRef, 70 | React.ComponentPropsWithoutRef 71 | >(({ className, ...props }, ref) => ( 72 | 80 | )) 81 | MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName 82 | 83 | const MenubarContent = React.forwardRef< 84 | React.ElementRef, 85 | React.ComponentPropsWithoutRef 86 | >( 87 | ( 88 | { className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, 89 | ref 90 | ) => ( 91 | 92 | 103 | 104 | ) 105 | ) 106 | MenubarContent.displayName = MenubarPrimitive.Content.displayName 107 | 108 | const MenubarItem = React.forwardRef< 109 | React.ElementRef, 110 | React.ComponentPropsWithoutRef & { 111 | inset?: boolean 112 | } 113 | >(({ className, inset, ...props }, ref) => ( 114 | 123 | )) 124 | MenubarItem.displayName = MenubarPrimitive.Item.displayName 125 | 126 | const MenubarCheckboxItem = React.forwardRef< 127 | React.ElementRef, 128 | React.ComponentPropsWithoutRef 129 | >(({ className, children, checked, ...props }, ref) => ( 130 | 139 | 140 | 141 | 142 | 143 | 144 | {children} 145 | 146 | )) 147 | MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName 148 | 149 | const MenubarRadioItem = React.forwardRef< 150 | React.ElementRef, 151 | React.ComponentPropsWithoutRef 152 | >(({ className, children, ...props }, ref) => ( 153 | 161 | 162 | 163 | 164 | 165 | 166 | {children} 167 | 168 | )) 169 | MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName 170 | 171 | const MenubarLabel = React.forwardRef< 172 | React.ElementRef, 173 | React.ComponentPropsWithoutRef & { 174 | inset?: boolean 175 | } 176 | >(({ className, inset, ...props }, ref) => ( 177 | 186 | )) 187 | MenubarLabel.displayName = MenubarPrimitive.Label.displayName 188 | 189 | const MenubarSeparator = React.forwardRef< 190 | React.ElementRef, 191 | React.ComponentPropsWithoutRef 192 | >(({ className, ...props }, ref) => ( 193 | 198 | )) 199 | MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName 200 | 201 | const MenubarShortcut = ({ 202 | className, 203 | ...props 204 | }: React.HTMLAttributes) => { 205 | return ( 206 | 213 | ) 214 | } 215 | MenubarShortcut.displayname = "MenubarShortcut" 216 | 217 | export { 218 | Menubar, 219 | MenubarMenu, 220 | MenubarTrigger, 221 | MenubarContent, 222 | MenubarItem, 223 | MenubarSeparator, 224 | MenubarLabel, 225 | MenubarCheckboxItem, 226 | MenubarRadioGroup, 227 | MenubarRadioItem, 228 | MenubarPortal, 229 | MenubarSubContent, 230 | MenubarSubTrigger, 231 | MenubarGroup, 232 | MenubarSub, 233 | MenubarShortcut, 234 | } 235 | -------------------------------------------------------------------------------- /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: 240 10% 3.9%; 9 | 10 | --card: 0 0% 100%; 11 | --card-foreground: 240 10% 3.9%; 12 | 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 240 10% 3.9%; 15 | 16 | --primary: 240 5.9% 10%; 17 | --primary-foreground: 0 0% 98%; 18 | 19 | --secondary: 240 4.8% 95.9%; 20 | --secondary-foreground: 240 5.9% 10%; 21 | 22 | --muted: 240 4.8% 95.9%; 23 | --muted-foreground: 240 3.8% 46.1%; 24 | 25 | --accent: 240 4.8% 95.9%; 26 | --accent-foreground: 240 5.9% 10%; 27 | 28 | --destructive: 0 84.2% 60.2%; 29 | --destructive-foreground: 0 0% 98%; 30 | 31 | --border: 240 5.9% 90%; 32 | --input: 240 5.9% 90%; 33 | --ring: 240 10% 3.9%; 34 | 35 | --radius: 0.5rem; 36 | } 37 | 38 | .dark { 39 | --background: 240 10% 3.9%; 40 | --foreground: 0 0% 98%; 41 | 42 | --card: 240 10% 3.9%; 43 | --card-foreground: 0 0% 98%; 44 | 45 | --popover: 240 10% 3.9%; 46 | --popover-foreground: 0 0% 98%; 47 | 48 | --primary: 0 0% 98%; 49 | --primary-foreground: 240 5.9% 10%; 50 | 51 | --secondary: 240 3.7% 15.9%; 52 | --secondary-foreground: 0 0% 98%; 53 | 54 | --muted: 240 3.7% 15.9%; 55 | --muted-foreground: 240 5% 64.9%; 56 | 57 | --accent: 240 3.7% 15.9%; 58 | --accent-foreground: 0 0% 98%; 59 | 60 | --destructive: 0 62.8% 30.6%; 61 | --destructive-foreground: 0 0% 98%; 62 | 63 | --border: 240 3.7% 15.9%; 64 | --input: 240 3.7% 15.9%; 65 | --ring: 240 4.9% 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.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')!).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | colors: { 21 | border: "hsl(var(--border))", 22 | input: "hsl(var(--input))", 23 | ring: "hsl(var(--ring))", 24 | background: "hsl(var(--background))", 25 | foreground: "hsl(var(--foreground))", 26 | primary: { 27 | DEFAULT: "hsl(var(--primary))", 28 | foreground: "hsl(var(--primary-foreground))", 29 | }, 30 | secondary: { 31 | DEFAULT: "hsl(var(--secondary))", 32 | foreground: "hsl(var(--secondary-foreground))", 33 | }, 34 | destructive: { 35 | DEFAULT: "hsl(var(--destructive))", 36 | foreground: "hsl(var(--destructive-foreground))", 37 | }, 38 | muted: { 39 | DEFAULT: "hsl(var(--muted))", 40 | foreground: "hsl(var(--muted-foreground))", 41 | }, 42 | accent: { 43 | DEFAULT: "hsl(var(--accent))", 44 | foreground: "hsl(var(--accent-foreground))", 45 | }, 46 | popover: { 47 | DEFAULT: "hsl(var(--popover))", 48 | foreground: "hsl(var(--popover-foreground))", 49 | }, 50 | card: { 51 | DEFAULT: "hsl(var(--card))", 52 | foreground: "hsl(var(--card-foreground))", 53 | }, 54 | }, 55 | borderRadius: { 56 | lg: "var(--radius)", 57 | md: "calc(var(--radius) - 2px)", 58 | sm: "calc(var(--radius) - 4px)", 59 | }, 60 | keyframes: { 61 | "accordion-down": { 62 | from: { height: "0" }, 63 | to: { height: "var(--radix-accordion-content-height)" }, 64 | }, 65 | "accordion-up": { 66 | from: { height: "var(--radix-accordion-content-height)" }, 67 | to: { height: "0" }, 68 | }, 69 | }, 70 | animation: { 71 | "accordion-down": "accordion-down 0.2s ease-out", 72 | "accordion-up": "accordion-up 0.2s ease-out", 73 | }, 74 | }, 75 | }, 76 | plugins: [require("tailwindcss-animate")], 77 | } -------------------------------------------------------------------------------- /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 | "baseUrl": ".", 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | }, 13 | 14 | /* Bundler mode */ 15 | "moduleResolution": "bundler", 16 | "allowImportingTsExtensions": true, 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react-jsx", 21 | 22 | /* Linting */ 23 | "strict": false, 24 | "noUnusedLocals": true, 25 | "noUnusedParameters": true, 26 | "noFallthroughCasesInSwitch": true 27 | }, 28 | "include": ["src"], 29 | "references": [{ "path": "./tsconfig.node.json" }] 30 | } 31 | -------------------------------------------------------------------------------- /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 { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | 5 | // https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | plugins: [react()], 8 | resolve: { 9 | alias: { 10 | '@': path.resolve(__dirname, './src'), 11 | }, 12 | }, 13 | }); 14 | --------------------------------------------------------------------------------