generator-kodly-react-app 1.0.6 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/generators/app/index.js +63 -11
  2. package/generators/app/templates/.env.dev +4 -0
  3. package/generators/app/templates/.env.sandbox +4 -0
  4. package/generators/app/templates/STRUCTURE.md +661 -0
  5. package/generators/app/templates/components.json +22 -0
  6. package/generators/app/templates/index.html +14 -6
  7. package/generators/app/templates/openapi-ts.config.ts +29 -0
  8. package/generators/app/templates/package.json +46 -26
  9. package/generators/app/templates/public/favicon.svg +4 -0
  10. package/generators/app/templates/src/app.tsx +8 -8
  11. package/generators/app/templates/src/components/layout/language-switcher.tsx +40 -0
  12. package/generators/app/templates/src/components/layout/theme-switcher.tsx +37 -0
  13. package/generators/app/templates/src/components/theme/theme-provider.tsx +22 -28
  14. package/generators/app/templates/src/components/ui/button.tsx +34 -32
  15. package/generators/app/templates/src/components/ui/card.tsx +76 -0
  16. package/generators/app/templates/src/components/ui/field.tsx +242 -0
  17. package/generators/app/templates/src/components/ui/form.tsx +129 -0
  18. package/generators/app/templates/src/components/ui/input-group.tsx +170 -0
  19. package/generators/app/templates/src/components/ui/input.tsx +19 -21
  20. package/generators/app/templates/src/components/ui/label.tsx +24 -0
  21. package/generators/app/templates/src/components/ui/select.tsx +184 -0
  22. package/generators/app/templates/src/components/ui/separator.tsx +31 -0
  23. package/generators/app/templates/src/components/ui/textarea.tsx +22 -0
  24. package/generators/app/templates/src/index.css +83 -26
  25. package/generators/app/templates/src/lib/api/client.ts +4 -5
  26. package/generators/app/templates/src/lib/i18n.ts +31 -16
  27. package/generators/app/templates/src/lib/routes.ts +14 -0
  28. package/generators/app/templates/src/lib/utils/error-handler.ts +7 -2
  29. package/generators/app/templates/src/lib/utils/init-form-schema.ts +12 -0
  30. package/generators/app/templates/src/lib/utils.ts +3 -4
  31. package/generators/app/templates/src/locales/en/common.json +5 -0
  32. package/generators/app/templates/src/locales/en/theme.json +5 -0
  33. package/generators/app/templates/src/locales/index.ts +31 -0
  34. package/generators/app/templates/src/locales/ja/common.json +5 -0
  35. package/generators/app/templates/src/locales/ja/theme.json +6 -0
  36. package/generators/app/templates/src/main.tsx +19 -15
  37. package/generators/app/templates/src/modules/app/layouts/app-layout.tsx +37 -0
  38. package/generators/app/templates/src/modules/app/locales/app-en.json +9 -0
  39. package/generators/app/templates/src/modules/app/locales/app-ja.json +9 -0
  40. package/generators/app/templates/src/modules/auth/components/forgot-password-form.tsx +74 -0
  41. package/generators/app/templates/src/modules/auth/components/login-form.tsx +95 -0
  42. package/generators/app/templates/src/modules/auth/components/reset-password-form.tsx +112 -0
  43. package/generators/app/templates/src/modules/auth/components/signup-form.tsx +92 -0
  44. package/generators/app/templates/src/modules/auth/contexts/auth-context.tsx +11 -0
  45. package/generators/app/templates/src/modules/auth/hooks/use-auth-hook.ts +175 -0
  46. package/generators/app/templates/src/modules/auth/layouts/auth-layout.tsx +28 -0
  47. package/generators/app/templates/src/modules/auth/locales/auth-en.json +105 -0
  48. package/generators/app/templates/src/modules/auth/locales/auth-ja.json +105 -0
  49. package/generators/app/templates/src/modules/landing/components/auth-hero.tsx +34 -0
  50. package/generators/app/templates/src/modules/landing/components/welcome-hero.tsx +24 -0
  51. package/generators/app/templates/src/modules/landing/landing-page-layout.tsx +24 -0
  52. package/generators/app/templates/src/modules/landing/landing-page.tsx +17 -0
  53. package/generators/app/templates/src/modules/landing/layouts/landing-page-layout.tsx +24 -0
  54. package/generators/app/templates/src/modules/landing/locales/landing-en.json +12 -0
  55. package/generators/app/templates/src/modules/landing/locales/landing-ja.json +11 -0
  56. package/generators/app/templates/src/openapi-client-config.ts +6 -0
  57. package/generators/app/templates/src/routeTree.gen.ts +268 -3
  58. package/generators/app/templates/src/router.tsx +2 -2
  59. package/generators/app/templates/src/routes/__root.tsx +3 -3
  60. package/generators/app/templates/src/routes/_landing/index.tsx +10 -0
  61. package/generators/app/templates/src/routes/_landing/route.tsx +14 -0
  62. package/generators/app/templates/src/routes/app/index.tsx +4 -21
  63. package/generators/app/templates/src/routes/app/route.tsx +12 -8
  64. package/generators/app/templates/src/routes/auth/forgot-password.tsx +10 -0
  65. package/generators/app/templates/src/routes/auth/login.tsx +6 -7
  66. package/generators/app/templates/src/routes/auth/reset-password.tsx +15 -0
  67. package/generators/app/templates/src/routes/auth/route.tsx +23 -6
  68. package/generators/app/templates/src/routes/auth/signup.tsx +11 -0
  69. package/generators/app/templates/src/sdk/@tanstack/react-query.gen.ts +91 -0
  70. package/generators/app/templates/src/sdk/client/client.gen.ts +167 -0
  71. package/generators/app/templates/src/sdk/client/index.ts +23 -0
  72. package/generators/app/templates/src/sdk/client/types.gen.ts +197 -0
  73. package/generators/app/templates/src/sdk/client/utils.gen.ts +213 -0
  74. package/generators/app/templates/src/sdk/client.gen.ts +18 -0
  75. package/generators/app/templates/src/sdk/core/auth.gen.ts +42 -0
  76. package/generators/app/templates/src/sdk/core/bodySerializer.gen.ts +100 -0
  77. package/generators/app/templates/src/sdk/core/params.gen.ts +176 -0
  78. package/generators/app/templates/src/sdk/core/pathSerializer.gen.ts +181 -0
  79. package/generators/app/templates/src/sdk/core/queryKeySerializer.gen.ts +136 -0
  80. package/generators/app/templates/src/sdk/core/serverSentEvents.gen.ts +266 -0
  81. package/generators/app/templates/src/sdk/core/types.gen.ts +118 -0
  82. package/generators/app/templates/src/sdk/core/utils.gen.ts +143 -0
  83. package/generators/app/templates/src/sdk/index.ts +4 -0
  84. package/generators/app/templates/src/sdk/schemas.gen.ts +195 -0
  85. package/generators/app/templates/src/sdk/sdk.gen.ts +80 -0
  86. package/generators/app/templates/src/sdk/types.gen.ts +158 -0
  87. package/generators/app/templates/src/sdk/zod.gen.ts +148 -0
  88. package/generators/app/templates/src/vite-env.d.ts +2 -1
  89. package/generators/app/templates/tsconfig.json +1 -1
  90. package/generators/app/templates/vite.config.js +35 -21
  91. package/generators/constants.js +9 -9
  92. package/package.json +3 -2
  93. package/generators/app/templates/.env.example +0 -5
  94. package/generators/app/templates/README.md +0 -57
  95. package/generators/app/templates/src/locales/en.json +0 -18
  96. package/generators/app/templates/src/modules/auth/auth-context.tsx +0 -13
  97. package/generators/app/templates/src/modules/auth/login/login-form.tsx +0 -49
  98. package/generators/app/templates/src/modules/auth/login/login-page.tsx +0 -12
  99. package/generators/app/templates/src/modules/auth/use-auth-hook.ts +0 -87
  100. package/generators/app/templates/src/routes/index.tsx +0 -12
  101. package/generators/app/templates/types.d.ts +0 -3
@@ -0,0 +1,29 @@
1
+ import { defineConfig } from '@hey-api/openapi-ts';
2
+ import { loadEnv } from 'vite';
3
+
4
+ // Get the mode from environment variable (set by npm scripts)
5
+ const mode = process.env.MODE || 'development';
6
+
7
+ // Load environment variables from .env files
8
+ const env = loadEnv(mode, process.cwd(), '');
9
+
10
+ export default defineConfig({
11
+ input: `${env.VITE_API_BASE_URL}/v3/api-docs`,
12
+ output: 'src/sdk',
13
+ plugins: [
14
+ {
15
+ name: '@hey-api/client-axios',
16
+ runtimeConfigPath: '@/openapi-client-config.ts',
17
+ },
18
+ '@tanstack/react-query',
19
+ 'zod',
20
+ {
21
+ name: '@hey-api/sdk',
22
+ validator: true,
23
+ },
24
+ {
25
+ name: '@hey-api/schemas',
26
+ type: 'json',
27
+ },
28
+ ],
29
+ });
@@ -1,42 +1,62 @@
1
1
  {
2
- "name": "<%= appNameSlug %>",
2
+ "name": "app",
3
3
  "private": true,
4
4
  "type": "module",
5
5
  "version": "0.0.1",
6
6
  "scripts": {
7
- "dev": "vite",
8
- "start": "vite",
9
- "build": "vite build && tsc",
10
- "serve": "vite preview"
7
+ "generate-routes": "tsr generate",
8
+ "watch-routes": "tsr watch",
9
+ "vite": "vite",
10
+ "vite:dev": "vite --mode dev",
11
+ "vite:sandbox": "vite --mode sandbox",
12
+ "dev": "npm-run-all --parallel watch-routes vite:dev",
13
+ "dev:sandbox": "npm-run-all --parallel watch-routes vite:sandbox",
14
+ "build": "npm run generate-routes && vite build --mode dev && tsc",
15
+ "build:sandbox": "npm run generate-routes && vite build --mode sandbox && tsc",
16
+ "serve": "vite preview",
17
+ "openapi-ts:dev": "MODE=dev openapi-ts",
18
+ "openapi-ts:sandbox": "MODE=sandbox openapi-ts"
11
19
  },
12
20
  "dependencies": {
21
+ "@hey-api/openapi-ts": "^0.90.2",
22
+ "@hookform/resolvers": "^5.2.2",
23
+ "@radix-ui/react-label": "^2.1.8",
24
+ "@radix-ui/react-select": "^2.2.6",
25
+ "@radix-ui/react-separator": "^1.1.8",
13
26
  "@radix-ui/react-slot": "^1.2.4",
14
- "@tanstack/react-query": "<%= TANSTACK_REACT_QUERY_VERSION %>",
15
- "@tanstack/react-router": "<%= TANSTACK_ROUTER_VERSION %>",
16
- "@tanstack/router-plugin": "<%= TANSTACK_ROUTER_PLUGIN_VERSION %>",
17
- "axios": "<%= AXIOS_VERSION %>",
27
+ "@tanstack/react-query": "^5.90.16",
28
+ "@tanstack/react-router": "^1.145.7",
29
+ "@tanstack/router-plugin": "^1.145.7",
30
+ "axios": "^1.13.2",
18
31
  "class-variance-authority": "^0.7.1",
19
32
  "clsx": "^2.1.1",
20
- "dotenv": "<%= DOTENV_VERSION %>",
21
- "i18next": "<%= I18NEXT_VERSION %>",
22
- "jotai": "<%= JOTAI_VERSION %>",
23
- "lucide-react": "^0.554.0",
24
- "react": "<%= REACT_VERSION %>",
25
- "react-dom": "<%= REACT_DOM_VERSION %>",
26
- "react-i18next": "<%= REACT_I18NEXT_VERSION %>",
33
+ "dotenv": "^17.2.3",
34
+ "i18next": "^25.7.3",
35
+ "jotai": "^2.16.1",
36
+ "lucide-react": "^0.562.0",
37
+ "react": "^19.2.3",
38
+ "react-dom": "^19.2.3",
39
+ "react-hook-form": "^7.70.0",
40
+ "react-i18next": "^16.5.1",
27
41
  "sonner": "^2.0.7",
28
- "tailwind-merge": "^3.4.0"
42
+ "tailwind-merge": "^3.4.0",
43
+ "tailwindcss-animate": "^1.0.7",
44
+ "tw-animate-css": "^1.4.0",
45
+ "zod": "^4.3.5",
46
+ "zod-empty": "^2.0.2"
29
47
  },
30
48
  "devDependencies": {
31
- "@tailwindcss/postcss": "<%= TAILWIND_VERSION %>",
32
- "@tailwindcss/vite": "<%= TAILWIND_VERSION %>",
33
- "@types/node": "^24.10.1",
34
- "@types/react": "^19.2.6",
49
+ "@tailwindcss/postcss": "^4.1.18",
50
+ "@tailwindcss/vite": "^4.1.18",
51
+ "@tanstack/router-cli": "^1.145.7",
52
+ "@types/node": "^25.0.3",
53
+ "@types/react": "^19.2.7",
35
54
  "@types/react-dom": "^19.2.3",
36
- "@vitejs/plugin-react": "^5.1.1",
37
- "cross-env": "<%= CROSS_ENV_VERSION %>",
38
- "tailwindcss": "<%= TAILWIND_VERSION %>",
39
- "typescript": "<%= TYPESCRIPT_VERSION %>",
40
- "vite": "<%= VITE_VERSION %>"
55
+ "@vitejs/plugin-react": "^5.1.2",
56
+ "babel-plugin-react-compiler": "^1.0.0",
57
+ "npm-run-all": "^4.1.5",
58
+ "tailwindcss": "^4.1.18",
59
+ "typescript": "^5.9.3",
60
+ "vite": "^7.3.1"
41
61
  }
42
62
  }
@@ -0,0 +1,4 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
2
+ <rect width="32" height="32" rx="10" fill="#000000"/>
3
+ <path d="M 9 7 L 9 25 M 9 16 L 19 7 M 9 16 L 19 25" stroke="#ffffff" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
4
+ </svg>
@@ -3,11 +3,11 @@ import { useAtomValue } from 'jotai';
3
3
  import { Loader2 } from 'lucide-react';
4
4
  import { useEffect } from 'react';
5
5
  import { Toaster } from 'sonner';
6
- import './index.css';
7
- import { AuthProvider } from './modules/auth/auth-context';
8
- import { authTokenAtom, currentUserDetailsAtom, useValidateToken, setLocaleInAxios } from './modules/auth/use-auth-hook';
9
- import { router } from './router';
10
- import { getLanguage } from './lib/i18n';
6
+ import '@/index.css';
7
+ import { AuthProvider } from '@/modules/auth/contexts/auth-context';
8
+ import { authTokenAtom, currentUserDetailsAtom, useValidateToken, setLocaleInClient } from '@/modules/auth/hooks/use-auth-hook';
9
+ import { router } from '@/router';
10
+ import { getLanguage, SupportedLanguages } from '@/lib/i18n';
11
11
 
12
12
  declare module '@tanstack/react-router' {
13
13
  interface Register {
@@ -31,13 +31,13 @@ export default function App() {
31
31
  const token = useAtomValue(authTokenAtom);
32
32
 
33
33
  useEffect(() => {
34
- const currentLanguage = getLanguage() as 'en';
35
- setLocaleInAxios(currentLanguage);
34
+ const currentLanguage = getLanguage() as SupportedLanguages;
35
+ setLocaleInClient(currentLanguage);
36
36
  }, []);
37
37
 
38
38
  useEffect(() => {
39
39
  if (token) {
40
- mutate();
40
+ mutate({});
41
41
  }
42
42
  }, [token, mutate]);
43
43
 
@@ -0,0 +1,40 @@
1
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
2
+ import { getLanguage, changeLanguage } from '@/lib/i18n';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { useEffect, useState } from 'react';
5
+
6
+ export function LanguageSwitcher() {
7
+ const { i18n } = useTranslation();
8
+ const [currentLang, setCurrentLang] = useState<'en' | 'ja'>(() => {
9
+ return (getLanguage() as 'en' | 'ja') || 'en';
10
+ });
11
+
12
+ useEffect(() => {
13
+ const handleLanguageChange = (lng: string) => {
14
+ setCurrentLang(lng as 'en' | 'ja');
15
+ };
16
+
17
+ i18n.on('languageChanged', handleLanguageChange);
18
+ return () => {
19
+ i18n.off('languageChanged', handleLanguageChange);
20
+ };
21
+ }, [i18n]);
22
+
23
+ const handleLanguageChange = (value: string) => {
24
+ changeLanguage(value as 'en' | 'ja');
25
+ };
26
+
27
+ return (
28
+ <Select
29
+ value={currentLang}
30
+ onValueChange={handleLanguageChange}>
31
+ <SelectTrigger className='min-w-[120px]'>
32
+ <SelectValue />
33
+ </SelectTrigger>
34
+ <SelectContent>
35
+ <SelectItem value='en'>English</SelectItem>
36
+ <SelectItem value='ja'>日本語</SelectItem>
37
+ </SelectContent>
38
+ </Select>
39
+ );
40
+ }
@@ -0,0 +1,37 @@
1
+ import { Button } from '@/components/ui/button';
2
+ import { ThemeTypes, useTheme } from '@/components/theme/theme-provider';
3
+ import { Moon, Sun, Monitor } from 'lucide-react';
4
+
5
+ export function ThemeSwitcher() {
6
+ const { theme, setTheme } = useTheme();
7
+
8
+ const cycleTheme = () => {
9
+ const themes: ThemeTypes[] = ['light', 'dark', 'system'];
10
+ const currentIndex = themes.indexOf(theme);
11
+ const nextIndex = (currentIndex + 1) % themes.length;
12
+ setTheme(themes[nextIndex]);
13
+ };
14
+
15
+ const getIcon = () => {
16
+ switch (theme) {
17
+ case 'light':
18
+ return <Sun className='h-4 w-4' />;
19
+ case 'dark':
20
+ return <Moon className='h-4 w-4' />;
21
+ case 'system':
22
+ return <Monitor className='h-4 w-4' />;
23
+ default:
24
+ return <Monitor className='h-4 w-4' />;
25
+ }
26
+ };
27
+
28
+ return (
29
+ <Button
30
+ variant='outline'
31
+ size='icon'
32
+ onClick={cycleTheme}
33
+ aria-label='Toggle theme'>
34
+ {getIcon()}
35
+ </Button>
36
+ );
37
+ }
@@ -1,45 +1,41 @@
1
- import { createContext, useContext, useEffect, useState } from "react";
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
2
 
3
- type Theme = "dark" | "light" | "system";
3
+ export type ThemeTypes = 'dark' | 'light' | 'system';
4
4
 
5
5
  type ThemeProviderProps = {
6
6
  children: React.ReactNode;
7
- defaultTheme?: Theme;
7
+ defaultTheme?: ThemeTypes;
8
8
  storageKey?: string;
9
9
  };
10
10
 
11
11
  type ThemeProviderState = {
12
- theme: Theme;
13
- setTheme: (theme: Theme) => void;
12
+ theme: ThemeTypes;
13
+ setTheme: (theme: ThemeTypes) => void;
14
14
  };
15
15
 
16
16
  const initialState: ThemeProviderState = {
17
- theme: "system",
17
+ theme: 'system' as ThemeTypes,
18
18
  setTheme: () => null,
19
19
  };
20
20
 
21
21
  const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
22
22
 
23
- export function ThemeProvider({
24
- children,
25
- defaultTheme = "system",
26
- storageKey = "vite-ui-theme",
27
- ...props
28
- }: ThemeProviderProps) {
29
- const [theme, setTheme] = useState<Theme>(
30
- () => (localStorage.getItem(storageKey) as Theme) || defaultTheme
31
- );
23
+ export function ThemeProvider({ children, defaultTheme = 'system', storageKey = 'vite-ui-theme', ...props }: ThemeProviderProps) {
24
+ const [theme, setTheme] = useState<ThemeTypes>(() => {
25
+ const stored = localStorage.getItem(storageKey);
26
+ if (stored && ['dark', 'light', 'system'].includes(stored)) {
27
+ return stored as ThemeTypes;
28
+ }
29
+ return 'system';
30
+ });
32
31
 
33
32
  useEffect(() => {
34
33
  const root = window.document.documentElement;
35
34
 
36
- root.classList.remove("light", "dark");
35
+ root.classList.remove('light', 'dark');
37
36
 
38
- if (theme === "system") {
39
- const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
40
- .matches
41
- ? "dark"
42
- : "light";
37
+ if (theme === 'system') {
38
+ const systemTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
43
39
  root.classList.add(systemTheme);
44
40
  return;
45
41
  }
@@ -49,14 +45,16 @@ export function ThemeProvider({
49
45
 
50
46
  const value = {
51
47
  theme,
52
- setTheme: (theme: Theme) => {
48
+ setTheme: (theme: ThemeTypes) => {
53
49
  localStorage.setItem(storageKey, theme);
54
50
  setTheme(theme);
55
51
  },
56
52
  };
57
53
 
58
54
  return (
59
- <ThemeProviderContext.Provider {...props} value={value}>
55
+ <ThemeProviderContext.Provider
56
+ {...props}
57
+ value={value}>
60
58
  {children}
61
59
  </ThemeProviderContext.Provider>
62
60
  );
@@ -64,10 +62,6 @@ export function ThemeProvider({
64
62
 
65
63
  export const useTheme = () => {
66
64
  const context = useContext(ThemeProviderContext);
67
-
68
- if (context === undefined)
69
- throw new Error("useTheme must be used within a ThemeProvider");
70
-
65
+ if (context === undefined) throw new Error('useTheme must be used within a ThemeProvider');
71
66
  return context;
72
67
  };
73
-
@@ -1,23 +1,28 @@
1
- import * as React from "react";
2
- import { Slot } from "@radix-ui/react-slot";
3
- import { cva, type VariantProps } from "class-variance-authority";
4
- import { cn } from "@/lib/utils";
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"
5
6
 
6
7
  const buttonVariants = cva(
7
- "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:cursor-not-allowed disabled:opacity-50 hover:cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-ring",
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
8
9
  {
9
10
  variants: {
10
11
  variant: {
11
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
12
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
13
- outline: "border border-border bg-background hover:bg-accent hover:text-accent-foreground",
14
- secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
12
+ default:
13
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
14
+ destructive:
15
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
16
+ outline:
17
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
18
+ secondary:
19
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
15
20
  ghost: "hover:bg-accent hover:text-accent-foreground",
16
21
  link: "text-primary underline-offset-4 hover:underline",
17
22
  },
18
23
  size: {
19
24
  default: "h-9 px-4 py-2",
20
- sm: "h-8 rounded-md px-3",
25
+ sm: "h-8 rounded-md px-3 text-xs",
21
26
  lg: "h-10 rounded-md px-8",
22
27
  icon: "h-9 w-9",
23
28
  },
@@ -27,29 +32,26 @@ const buttonVariants = cva(
27
32
  size: "default",
28
33
  },
29
34
  }
30
- );
31
-
32
- function Button({
33
- className,
34
- variant,
35
- size,
36
- asChild = false,
37
- type,
38
- ...props
39
- }: React.ComponentProps<"button"> &
40
- VariantProps<typeof buttonVariants> & {
41
- asChild?: boolean;
42
- }) {
43
- const Comp = asChild ? Slot : "button";
35
+ )
44
36
 
45
- return (
46
- <Comp
47
- type={type ?? "button"}
48
- className={cn(buttonVariants({ variant, size, className }))}
49
- {...props}
50
- />
51
- );
37
+ export interface ButtonProps
38
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
39
+ VariantProps<typeof buttonVariants> {
40
+ asChild?: boolean
52
41
  }
53
42
 
54
- export { Button, buttonVariants };
43
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
44
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
45
+ const Comp = asChild ? Slot : "button"
46
+ return (
47
+ <Comp
48
+ className={cn(buttonVariants({ variant, size, className }))}
49
+ ref={ref}
50
+ {...props}
51
+ />
52
+ )
53
+ }
54
+ )
55
+ Button.displayName = "Button"
55
56
 
57
+ export { Button, buttonVariants }
@@ -0,0 +1,76 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-xl border bg-card text-card-foreground shadow",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLDivElement,
34
+ React.HTMLAttributes<HTMLDivElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <div
37
+ ref={ref}
38
+ className={cn("font-semibold leading-none tracking-tight", className)}
39
+ {...props}
40
+ />
41
+ ))
42
+ CardTitle.displayName = "CardTitle"
43
+
44
+ const CardDescription = React.forwardRef<
45
+ HTMLDivElement,
46
+ React.HTMLAttributes<HTMLDivElement>
47
+ >(({ className, ...props }, ref) => (
48
+ <div
49
+ ref={ref}
50
+ className={cn("text-sm text-muted-foreground", className)}
51
+ {...props}
52
+ />
53
+ ))
54
+ CardDescription.displayName = "CardDescription"
55
+
56
+ const CardContent = React.forwardRef<
57
+ HTMLDivElement,
58
+ React.HTMLAttributes<HTMLDivElement>
59
+ >(({ className, ...props }, ref) => (
60
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
61
+ ))
62
+ CardContent.displayName = "CardContent"
63
+
64
+ const CardFooter = React.forwardRef<
65
+ HTMLDivElement,
66
+ React.HTMLAttributes<HTMLDivElement>
67
+ >(({ className, ...props }, ref) => (
68
+ <div
69
+ ref={ref}
70
+ className={cn("flex items-center p-6 pt-0", className)}
71
+ {...props}
72
+ />
73
+ ))
74
+ CardFooter.displayName = "CardFooter"
75
+
76
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }