├── .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 |
--------------------------------------------------------------------------------