@umituz/web-design-system 1.8.7 โ 1.9.0
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.
- package/README.md +89 -2
- package/package.json +21 -1
- package/src/presentation/atoms/Separator.tsx +34 -0
- package/src/presentation/atoms/Switch.tsx +55 -0
- package/src/presentation/atoms/Toggle.tsx +48 -0
- package/src/presentation/atoms/index.ts +9 -0
- package/src/presentation/hooks/index.ts +3 -0
- package/src/presentation/hooks/useLanguage.ts +109 -0
- package/src/presentation/organisms/DropdownMenu.tsx +186 -0
- package/src/presentation/organisms/ToggleGroup.tsx +62 -0
- package/src/presentation/organisms/index.ts +22 -0
- package/src/presentation/templates/ResponsiveContainer.tsx +178 -0
- package/src/presentation/templates/index.ts +3 -0
package/README.md
CHANGED
|
@@ -68,7 +68,11 @@ import {
|
|
|
68
68
|
import {
|
|
69
69
|
Form,
|
|
70
70
|
List,
|
|
71
|
-
Section
|
|
71
|
+
Section,
|
|
72
|
+
PageLayout,
|
|
73
|
+
PageHeader,
|
|
74
|
+
ResponsiveContainer,
|
|
75
|
+
ProjectSkeleton
|
|
72
76
|
} from '@umituz/web-design-system/templates';
|
|
73
77
|
```
|
|
74
78
|
|
|
@@ -78,7 +82,15 @@ import {
|
|
|
78
82
|
useTheme,
|
|
79
83
|
useMediaQuery,
|
|
80
84
|
useBreakpoint,
|
|
81
|
-
useLocalStorage
|
|
85
|
+
useLocalStorage,
|
|
86
|
+
useLanguage,
|
|
87
|
+
useClickOutside,
|
|
88
|
+
useKeyboard,
|
|
89
|
+
useEscape,
|
|
90
|
+
useDebounce,
|
|
91
|
+
useClipboard,
|
|
92
|
+
useToggle,
|
|
93
|
+
useScrollLock
|
|
82
94
|
} from '@umituz/web-design-system/hooks';
|
|
83
95
|
```
|
|
84
96
|
|
|
@@ -196,6 +208,81 @@ function ThemeToggle() {
|
|
|
196
208
|
}
|
|
197
209
|
```
|
|
198
210
|
|
|
211
|
+
### Language Hook
|
|
212
|
+
|
|
213
|
+
```tsx
|
|
214
|
+
import { useLanguage } from '@umituz/web-design-system/hooks';
|
|
215
|
+
|
|
216
|
+
function LanguageSelector() {
|
|
217
|
+
const { currentLanguage, changeLanguage, t, supportedLanguages } = useLanguage({
|
|
218
|
+
defaultLanguage: 'en-US',
|
|
219
|
+
supportedLanguages: {
|
|
220
|
+
'en-US': { name: 'English', flag: '๐บ๐ธ' },
|
|
221
|
+
'tr-TR': { name: 'Tรผrkรงe', flag: '๐น๐ท' },
|
|
222
|
+
'de-DE': { name: 'Deutsch', flag: '๐ฉ๐ช' },
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<select value={currentLanguage} onChange={(e) => changeLanguage(e.target.value)}>
|
|
228
|
+
{Object.entries(supportedLanguages).map(([code, { name, flag }]) => (
|
|
229
|
+
<option key={code} value={code}>{flag} {name}</option>
|
|
230
|
+
))}
|
|
231
|
+
</select>
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Breakpoint Hook
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
import { useBreakpoint, useMediaQuery } from '@umituz/web-design-system/hooks';
|
|
240
|
+
|
|
241
|
+
function ResponsiveComponent() {
|
|
242
|
+
const breakpoint = useBreakpoint();
|
|
243
|
+
const isDesktop = useMediaQuery('lg');
|
|
244
|
+
const isTablet = useMediaQuery('md');
|
|
245
|
+
|
|
246
|
+
return (
|
|
247
|
+
<div>
|
|
248
|
+
Current breakpoint: {breakpoint || 'mobile'}
|
|
249
|
+
{isDesktop && <DesktopNavigation />}
|
|
250
|
+
{isTablet && !isDesktop && <TabletNavigation />}
|
|
251
|
+
</div>
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## ๐ฑ Responsive Templates
|
|
257
|
+
|
|
258
|
+
### ResponsiveContainer
|
|
259
|
+
|
|
260
|
+
```tsx
|
|
261
|
+
import { ResponsiveContainer } from '@umituz/web-design-system/templates';
|
|
262
|
+
|
|
263
|
+
// Auto-responsive container with different max widths per device
|
|
264
|
+
<ResponsiveContainer
|
|
265
|
+
mobileMaxWidth="full"
|
|
266
|
+
tabletMaxWidth="lg"
|
|
267
|
+
desktopMaxWidth="xl"
|
|
268
|
+
gradient
|
|
269
|
+
minHeight="screen"
|
|
270
|
+
>
|
|
271
|
+
<h1>Auto-responsive content</h1>
|
|
272
|
+
<p>This container automatically adjusts based on screen size</p>
|
|
273
|
+
</ResponsiveContainer>
|
|
274
|
+
|
|
275
|
+
// Custom responsive container
|
|
276
|
+
<ResponsiveContainer
|
|
277
|
+
mobilePadding={false}
|
|
278
|
+
tabletPadding={true}
|
|
279
|
+
desktopPadding={true}
|
|
280
|
+
centered={false}
|
|
281
|
+
>
|
|
282
|
+
<div>Custom responsive layout</div>
|
|
283
|
+
</ResponsiveContainer>
|
|
284
|
+
```
|
|
285
|
+
|
|
199
286
|
## ๐ฏ Design Tokens
|
|
200
287
|
|
|
201
288
|
### Colors
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/web-design-system",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Web Design System - Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -57,16 +57,26 @@
|
|
|
57
57
|
"@radix-ui/react-alert-dialog": ">=1.0.0",
|
|
58
58
|
"@radix-ui/react-collapsible": ">=1.0.0",
|
|
59
59
|
"@radix-ui/react-dialog": ">=1.0.0",
|
|
60
|
+
"@radix-ui/react-dropdown-menu": ">=1.0.0",
|
|
60
61
|
"@radix-ui/react-hover-card": ">=1.0.0",
|
|
61
62
|
"@radix-ui/react-label": ">=2.0.0",
|
|
62
63
|
"@radix-ui/react-popover": ">=1.0.0",
|
|
63
64
|
"@radix-ui/react-scroll-area": ">=1.0.0",
|
|
64
65
|
"@radix-ui/react-select": ">=2.0.0",
|
|
66
|
+
"@radix-ui/react-separator": ">=1.0.0",
|
|
67
|
+
"@radix-ui/react-slot": ">=1.0.0",
|
|
68
|
+
"@radix-ui/react-switch": ">=1.0.0",
|
|
69
|
+
"@radix-ui/react-tabs": ">=1.0.0",
|
|
70
|
+
"@radix-ui/react-toggle": ">=1.0.0",
|
|
71
|
+
"@radix-ui/react-toggle-group": ">=1.0.0",
|
|
72
|
+
"@radix-ui/react-tooltip": ">=1.0.0",
|
|
73
|
+
"class-variance-authority": ">=0.7.0",
|
|
65
74
|
"clsx": ">=2.0.0",
|
|
66
75
|
"lucide-react": ">=0.400.0",
|
|
67
76
|
"react": ">=18.0.0",
|
|
68
77
|
"react-day-picker": ">=9.0.0",
|
|
69
78
|
"react-dom": ">=18.0.0",
|
|
79
|
+
"react-i18next": ">=13.0.0",
|
|
70
80
|
"tailwind-merge": ">=2.0.0"
|
|
71
81
|
},
|
|
72
82
|
"devDependencies": {
|
|
@@ -74,18 +84,28 @@
|
|
|
74
84
|
"@radix-ui/react-alert-dialog": "^1.1.8",
|
|
75
85
|
"@radix-ui/react-collapsible": "^1.1.12",
|
|
76
86
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
87
|
+
"@radix-ui/react-dropdown-menu": "^2.1.15",
|
|
77
88
|
"@radix-ui/react-hover-card": "^1.1.8",
|
|
78
89
|
"@radix-ui/react-label": "^2.1.8",
|
|
79
90
|
"@radix-ui/react-popover": "^1.1.15",
|
|
80
91
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
|
81
92
|
"@radix-ui/react-select": "^2.2.6",
|
|
93
|
+
"@radix-ui/react-separator": "^1.1.7",
|
|
94
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
95
|
+
"@radix-ui/react-switch": "^1.2.5",
|
|
96
|
+
"@radix-ui/react-tabs": "^1.1.12",
|
|
97
|
+
"@radix-ui/react-toggle": "^1.1.9",
|
|
98
|
+
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
99
|
+
"@radix-ui/react-tooltip": "^1.2.7",
|
|
82
100
|
"@types/react": "^18.0.0",
|
|
83
101
|
"@types/react-dom": "^18.0.0",
|
|
102
|
+
"class-variance-authority": "^0.7.1",
|
|
84
103
|
"clsx": "^2.1.1",
|
|
85
104
|
"lucide-react": "^0.577.0",
|
|
86
105
|
"react": "^18.0.0",
|
|
87
106
|
"react-day-picker": "^9.14.0",
|
|
88
107
|
"react-dom": "^18.0.0",
|
|
108
|
+
"react-i18next": "^13.0.0",
|
|
89
109
|
"tailwind-merge": "^3.5.0",
|
|
90
110
|
"typescript": "~5.9.2"
|
|
91
111
|
},
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Separator Component (Atom) - Shadcn/ui format
|
|
3
|
+
* @description Visual separator/divider component
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
|
8
|
+
|
|
9
|
+
import { cn } from '../../infrastructure/utils';
|
|
10
|
+
|
|
11
|
+
const Separator = React.forwardRef<
|
|
12
|
+
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
|
13
|
+
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
|
14
|
+
>(
|
|
15
|
+
(
|
|
16
|
+
{ className, orientation = 'horizontal', decorative = true, ...props },
|
|
17
|
+
ref
|
|
18
|
+
) => (
|
|
19
|
+
<SeparatorPrimitive.Root
|
|
20
|
+
ref={ref}
|
|
21
|
+
decorative={decorative}
|
|
22
|
+
orientation={orientation}
|
|
23
|
+
className={cn(
|
|
24
|
+
'shrink-0 bg-border',
|
|
25
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
|
33
|
+
|
|
34
|
+
export { Separator };
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switch Component (Atom) - Shadcn/ui format
|
|
3
|
+
* @description Toggle switch component using class-variance-authority
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
|
8
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../infrastructure/utils';
|
|
11
|
+
|
|
12
|
+
const switchVariants = cva(
|
|
13
|
+
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
default: 'h-6 w-11',
|
|
18
|
+
sm: 'h-5 w-9',
|
|
19
|
+
lg: 'h-7 w-13',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
defaultVariants: {
|
|
23
|
+
size: 'default',
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const switchThumbVariants = cva(
|
|
29
|
+
'pointer-events-none block rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
|
|
30
|
+
{
|
|
31
|
+
variants: {
|
|
32
|
+
size: {
|
|
33
|
+
default: 'h-5 w-5 data-[state=checked]:translate-x-5',
|
|
34
|
+
sm: 'h-4 w-4 data-[state=checked]:translate-x-4',
|
|
35
|
+
lg: 'h-6 w-6 data-[state=checked]:translate-x-6',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: {
|
|
39
|
+
size: 'default',
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
export interface SwitchProps extends React.ComponentProps<typeof SwitchPrimitives.Root>, VariantProps<typeof switchVariants> {}
|
|
45
|
+
|
|
46
|
+
const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.Root>, SwitchProps>(
|
|
47
|
+
({ className, size, ...props }, ref) => (
|
|
48
|
+
<SwitchPrimitives.Root className={cn(switchVariants({ size, className }))} {...props} ref={ref}>
|
|
49
|
+
<SwitchPrimitives.Thumb className={cn(switchThumbVariants({ size }))} />
|
|
50
|
+
</SwitchPrimitives.Root>
|
|
51
|
+
)
|
|
52
|
+
);
|
|
53
|
+
Switch.displayName = SwitchPrimitives.Root.displayName;
|
|
54
|
+
|
|
55
|
+
export { Switch };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle Component (Atom) - Shadcn/ui format
|
|
3
|
+
* @description Toggle button component with variants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import * as TogglePrimitive from '@radix-ui/react-toggle';
|
|
8
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../infrastructure/utils';
|
|
11
|
+
|
|
12
|
+
const toggleVariants = cva(
|
|
13
|
+
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors hover:bg-muted hover:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground',
|
|
14
|
+
{
|
|
15
|
+
variants: {
|
|
16
|
+
variant: {
|
|
17
|
+
default: 'bg-transparent',
|
|
18
|
+
outline:
|
|
19
|
+
'border border-input bg-transparent hover:bg-accent hover:text-accent-foreground',
|
|
20
|
+
},
|
|
21
|
+
size: {
|
|
22
|
+
default: 'h-10 px-3',
|
|
23
|
+
sm: 'h-9 px-2.5',
|
|
24
|
+
lg: 'h-11 px-5',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
variant: 'default',
|
|
29
|
+
size: 'default',
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const Toggle = React.forwardRef<
|
|
35
|
+
React.ElementRef<typeof TogglePrimitive.Root>,
|
|
36
|
+
React.ComponentPropsWithoutRef<typeof TogglePrimitive.Root> &
|
|
37
|
+
VariantProps<typeof toggleVariants>
|
|
38
|
+
>(({ className, variant, size, ...props }, ref) => (
|
|
39
|
+
<TogglePrimitive.Root
|
|
40
|
+
ref={ref}
|
|
41
|
+
className={cn(toggleVariants({ variant, size, className }))}
|
|
42
|
+
{...props}
|
|
43
|
+
/>
|
|
44
|
+
));
|
|
45
|
+
|
|
46
|
+
Toggle.displayName = TogglePrimitive.Root.displayName;
|
|
47
|
+
|
|
48
|
+
export { Toggle, toggleVariants };
|
|
@@ -50,3 +50,12 @@ export { Label } from './Label';
|
|
|
50
50
|
|
|
51
51
|
export { AspectRatio } from './AspectRatio';
|
|
52
52
|
export type { AspectRatioProps } from './AspectRatio';
|
|
53
|
+
|
|
54
|
+
export { Switch } from './Switch';
|
|
55
|
+
export type { SwitchProps } from './Switch';
|
|
56
|
+
|
|
57
|
+
export { Separator } from './Separator';
|
|
58
|
+
export type { SeparatorProps } from './Separator';
|
|
59
|
+
|
|
60
|
+
export { Toggle, toggleVariants } from './Toggle';
|
|
61
|
+
export type { ToggleProps } from './Toggle';
|
|
@@ -25,3 +25,6 @@ export type { UseClipboardReturn } from './useClipboard';
|
|
|
25
25
|
export { useToggle } from './useToggle';
|
|
26
26
|
|
|
27
27
|
export { useScrollLock } from './useScrollLock';
|
|
28
|
+
|
|
29
|
+
export { useLanguage } from './useLanguage';
|
|
30
|
+
export type { Language, SupportedLanguage, UseLanguageReturn } from './useLanguage';
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useLanguage Hook
|
|
3
|
+
* @description Language management with i18n integration
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useTranslation } from 'react-i18next';
|
|
7
|
+
|
|
8
|
+
export type Language = string;
|
|
9
|
+
|
|
10
|
+
export interface SupportedLanguage {
|
|
11
|
+
name: string;
|
|
12
|
+
flag: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UseLanguageReturn {
|
|
16
|
+
currentLanguage: Language;
|
|
17
|
+
changeLanguage: (language: Language) => void;
|
|
18
|
+
t: (key: string, params?: Record<string, unknown>) => string;
|
|
19
|
+
supportedLanguages: Record<Language, SupportedLanguage>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Validates if a language code exists in supported languages
|
|
24
|
+
*/
|
|
25
|
+
function isValidLanguage(
|
|
26
|
+
code: string,
|
|
27
|
+
supportedLanguages: Record<Language, SupportedLanguage>
|
|
28
|
+
): boolean {
|
|
29
|
+
return Object.keys(supportedLanguages).includes(code);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Normalizes language code to match supported languages
|
|
34
|
+
* Tries exact match first, then tries to match by language code prefix
|
|
35
|
+
*/
|
|
36
|
+
function normalizeLanguage(
|
|
37
|
+
code: string,
|
|
38
|
+
supportedLanguages: Record<Language, SupportedLanguage>,
|
|
39
|
+
defaultLanguage: Language
|
|
40
|
+
): Language {
|
|
41
|
+
// If code is valid, return it
|
|
42
|
+
if (isValidLanguage(code, supportedLanguages)) {
|
|
43
|
+
return code;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Try to extract language code (e.g., 'en-US' -> 'en')
|
|
47
|
+
const [language] = code.split('-');
|
|
48
|
+
const matchedKey = Object.keys(supportedLanguages).find((key) =>
|
|
49
|
+
key.startsWith(language)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (matchedKey as Language) || defaultLanguage;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Language management hook with i18n integration
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```tsx
|
|
60
|
+
* const { currentLanguage, changeLanguage, t, supportedLanguages } = useLanguage({
|
|
61
|
+
* defaultLanguage: 'en-US',
|
|
62
|
+
* supportedLanguages: {
|
|
63
|
+
* 'en-US': { name: 'English', flag: '๐บ๐ธ' },
|
|
64
|
+
* 'tr-TR': { name: 'Tรผrkรงe', flag: '๐น๐ท' },
|
|
65
|
+
* }
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // Change language
|
|
69
|
+
* changeLanguage('tr-TR');
|
|
70
|
+
*
|
|
71
|
+
* // Translate
|
|
72
|
+
* const title = t('common.title');
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
export function useLanguage(options?: {
|
|
76
|
+
defaultLanguage?: Language;
|
|
77
|
+
supportedLanguages?: Record<Language, SupportedLanguage>;
|
|
78
|
+
}): UseLanguageReturn {
|
|
79
|
+
const {
|
|
80
|
+
defaultLanguage = 'en-US',
|
|
81
|
+
supportedLanguages = {} as Record<Language, SupportedLanguage>,
|
|
82
|
+
} = options || {};
|
|
83
|
+
|
|
84
|
+
const { t, i18n } = useTranslation();
|
|
85
|
+
|
|
86
|
+
// Safely get and validate current language
|
|
87
|
+
const currentLanguage: Language = normalizeLanguage(
|
|
88
|
+
i18n.language,
|
|
89
|
+
supportedLanguages,
|
|
90
|
+
defaultLanguage
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const changeLanguage = (language: Language) => {
|
|
94
|
+
// Validate language before changing
|
|
95
|
+
if (isValidLanguage(language, supportedLanguages)) {
|
|
96
|
+
i18n.changeLanguage(language);
|
|
97
|
+
} else {
|
|
98
|
+
// Fall back to default language
|
|
99
|
+
i18n.changeLanguage(defaultLanguage);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return {
|
|
104
|
+
currentLanguage,
|
|
105
|
+
changeLanguage,
|
|
106
|
+
t,
|
|
107
|
+
supportedLanguages,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dropdown Menu Component (Organism) - Shadcn/ui format
|
|
3
|
+
* @description Dropdown menu component using Radix UI
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
|
8
|
+
import { Check, ChevronRight, Circle } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../infrastructure/utils';
|
|
11
|
+
|
|
12
|
+
const DropdownMenu = DropdownMenuPrimitive.Root;
|
|
13
|
+
|
|
14
|
+
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
|
15
|
+
|
|
16
|
+
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
|
17
|
+
|
|
18
|
+
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
|
19
|
+
|
|
20
|
+
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
|
21
|
+
|
|
22
|
+
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
|
23
|
+
|
|
24
|
+
const DropdownMenuSubTrigger = React.forwardRef<
|
|
25
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
|
26
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
|
27
|
+
inset?: boolean;
|
|
28
|
+
}
|
|
29
|
+
>(({ className, inset, children, ...props }, ref) => (
|
|
30
|
+
<DropdownMenuPrimitive.SubTrigger
|
|
31
|
+
ref={ref}
|
|
32
|
+
className={cn(
|
|
33
|
+
'flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
34
|
+
inset && 'pl-8',
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
{...props}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
<ChevronRight className="ml-auto" />
|
|
41
|
+
</DropdownMenuPrimitive.SubTrigger>
|
|
42
|
+
));
|
|
43
|
+
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName;
|
|
44
|
+
|
|
45
|
+
const DropdownMenuSubContent = React.forwardRef<
|
|
46
|
+
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
|
47
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
|
48
|
+
>(({ className, ...props }, ref) => (
|
|
49
|
+
<DropdownMenuPrimitive.SubContent
|
|
50
|
+
ref={ref}
|
|
51
|
+
className={cn(
|
|
52
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
53
|
+
className
|
|
54
|
+
)}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
));
|
|
58
|
+
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName;
|
|
59
|
+
|
|
60
|
+
const DropdownMenuContent = React.forwardRef<
|
|
61
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
|
62
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
|
63
|
+
>(({ className, collisionPadding = 8, sideOffset = 4, ...props }, ref) => (
|
|
64
|
+
<DropdownMenuPrimitive.Portal>
|
|
65
|
+
<DropdownMenuPrimitive.Content
|
|
66
|
+
ref={ref}
|
|
67
|
+
collisionPadding={collisionPadding}
|
|
68
|
+
sideOffset={sideOffset}
|
|
69
|
+
className={cn(
|
|
70
|
+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
|
71
|
+
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
|
72
|
+
className
|
|
73
|
+
)}
|
|
74
|
+
{...props}
|
|
75
|
+
/>
|
|
76
|
+
</DropdownMenuPrimitive.Portal>
|
|
77
|
+
));
|
|
78
|
+
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
|
79
|
+
|
|
80
|
+
const DropdownMenuItem = React.forwardRef<
|
|
81
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
|
82
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
|
83
|
+
inset?: boolean;
|
|
84
|
+
}
|
|
85
|
+
>(({ className, inset, ...props }, ref) => (
|
|
86
|
+
<DropdownMenuPrimitive.Item
|
|
87
|
+
ref={ref}
|
|
88
|
+
className={cn(
|
|
89
|
+
'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
|
90
|
+
inset && 'pl-8',
|
|
91
|
+
className
|
|
92
|
+
)}
|
|
93
|
+
{...props}
|
|
94
|
+
/>
|
|
95
|
+
));
|
|
96
|
+
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
|
97
|
+
|
|
98
|
+
const DropdownMenuCheckboxItem = React.forwardRef<
|
|
99
|
+
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
|
100
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
|
101
|
+
>(({ className, children, checked, ...props }, ref) => (
|
|
102
|
+
<DropdownMenuPrimitive.CheckboxItem
|
|
103
|
+
ref={ref}
|
|
104
|
+
className={cn(
|
|
105
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
106
|
+
className
|
|
107
|
+
)}
|
|
108
|
+
checked={checked}
|
|
109
|
+
{...props}
|
|
110
|
+
>
|
|
111
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
112
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
113
|
+
<Check className="h-4 w-4" />
|
|
114
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
115
|
+
</span>
|
|
116
|
+
{children}
|
|
117
|
+
</DropdownMenuPrimitive.CheckboxItem>
|
|
118
|
+
));
|
|
119
|
+
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName;
|
|
120
|
+
|
|
121
|
+
const DropdownMenuRadioItem = React.forwardRef<
|
|
122
|
+
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
|
123
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
|
124
|
+
>(({ className, children, ...props }, ref) => (
|
|
125
|
+
<DropdownMenuPrimitive.RadioItem
|
|
126
|
+
ref={ref}
|
|
127
|
+
className={cn(
|
|
128
|
+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
|
129
|
+
className
|
|
130
|
+
)}
|
|
131
|
+
{...props}
|
|
132
|
+
>
|
|
133
|
+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
|
134
|
+
<DropdownMenuPrimitive.ItemIndicator>
|
|
135
|
+
<Circle className="h-2 w-2 fill-current" />
|
|
136
|
+
</DropdownMenuPrimitive.ItemIndicator>
|
|
137
|
+
</span>
|
|
138
|
+
{children}
|
|
139
|
+
</DropdownMenuPrimitive.RadioItem>
|
|
140
|
+
));
|
|
141
|
+
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
|
142
|
+
|
|
143
|
+
const DropdownMenuLabel = React.forwardRef<
|
|
144
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
|
145
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
|
146
|
+
inset?: boolean;
|
|
147
|
+
}
|
|
148
|
+
>(({ className, inset, ...props }, ref) => (
|
|
149
|
+
<DropdownMenuPrimitive.Label
|
|
150
|
+
ref={ref}
|
|
151
|
+
className={cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', className)}
|
|
152
|
+
{...props}
|
|
153
|
+
/>
|
|
154
|
+
));
|
|
155
|
+
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
|
156
|
+
|
|
157
|
+
const DropdownMenuSeparator = React.forwardRef<
|
|
158
|
+
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
|
159
|
+
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
|
160
|
+
>(({ className, ...props }, ref) => (
|
|
161
|
+
<DropdownMenuPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
|
|
162
|
+
));
|
|
163
|
+
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
|
164
|
+
|
|
165
|
+
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
166
|
+
return <span className={cn('ml-auto text-xs tracking-widest opacity-60', className)} {...props} />;
|
|
167
|
+
};
|
|
168
|
+
DropdownMenuShortcut.displayName = 'DropdownMenuShortcut';
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
DropdownMenu,
|
|
172
|
+
DropdownMenuTrigger,
|
|
173
|
+
DropdownMenuContent,
|
|
174
|
+
DropdownMenuItem,
|
|
175
|
+
DropdownMenuCheckboxItem,
|
|
176
|
+
DropdownMenuRadioItem,
|
|
177
|
+
DropdownMenuLabel,
|
|
178
|
+
DropdownMenuSeparator,
|
|
179
|
+
DropdownMenuShortcut,
|
|
180
|
+
DropdownMenuGroup,
|
|
181
|
+
DropdownMenuPortal,
|
|
182
|
+
DropdownMenuSub,
|
|
183
|
+
DropdownMenuSubContent,
|
|
184
|
+
DropdownMenuSubTrigger,
|
|
185
|
+
DropdownMenuRadioGroup,
|
|
186
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Toggle Group Component (Organism) - Shadcn/ui format
|
|
3
|
+
* @description Group of toggle buttons with single or multiple selection
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as React from 'react';
|
|
7
|
+
import * as ToggleGroupPrimitive from '@radix-ui/react-toggle-group';
|
|
8
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
9
|
+
|
|
10
|
+
import { cn } from '../../infrastructure/utils';
|
|
11
|
+
import { Toggle, toggleVariants } from '../atoms/Toggle';
|
|
12
|
+
|
|
13
|
+
const ToggleGroupContext = React.createContext<VariantProps<typeof toggleVariants>>({
|
|
14
|
+
size: 'default',
|
|
15
|
+
variant: 'default',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const ToggleGroup = React.forwardRef<
|
|
19
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Root>,
|
|
20
|
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Root> &
|
|
21
|
+
VariantProps<typeof toggleVariants>
|
|
22
|
+
>(({ className, variant, size, children, ...props }, ref) => (
|
|
23
|
+
<ToggleGroupPrimitive.Root
|
|
24
|
+
ref={ref}
|
|
25
|
+
className={cn('flex items-center justify-center gap-1', className)}
|
|
26
|
+
{...props}
|
|
27
|
+
>
|
|
28
|
+
<ToggleGroupContext.Provider value={{ variant, size }}>
|
|
29
|
+
{children}
|
|
30
|
+
</ToggleGroupContext.Provider>
|
|
31
|
+
</ToggleGroupPrimitive.Root>
|
|
32
|
+
));
|
|
33
|
+
|
|
34
|
+
ToggleGroup.displayName = ToggleGroupPrimitive.Root.displayName;
|
|
35
|
+
|
|
36
|
+
const ToggleGroupItem = React.forwardRef<
|
|
37
|
+
React.ElementRef<typeof ToggleGroupPrimitive.Item>,
|
|
38
|
+
React.ComponentPropsWithoutRef<typeof ToggleGroupPrimitive.Item> &
|
|
39
|
+
VariantProps<typeof toggleVariants>
|
|
40
|
+
>(({ className, children, variant, size, ...props }, ref) => {
|
|
41
|
+
const context = React.useContext(ToggleGroupContext);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<ToggleGroupPrimitive.Item
|
|
45
|
+
ref={ref}
|
|
46
|
+
className={cn(
|
|
47
|
+
toggleVariants({
|
|
48
|
+
variant: variant || context.variant,
|
|
49
|
+
size: size || context.size,
|
|
50
|
+
}),
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
{...props}
|
|
54
|
+
>
|
|
55
|
+
{children}
|
|
56
|
+
</ToggleGroupPrimitive.Item>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
ToggleGroupItem.displayName = ToggleGroupPrimitive.Item.displayName;
|
|
61
|
+
|
|
62
|
+
export { ToggleGroup, ToggleGroupItem };
|
|
@@ -111,3 +111,25 @@ export {
|
|
|
111
111
|
AlertDialogAction,
|
|
112
112
|
AlertDialogCancel,
|
|
113
113
|
} from './AlertDialog';
|
|
114
|
+
|
|
115
|
+
export {
|
|
116
|
+
DropdownMenu,
|
|
117
|
+
DropdownMenuTrigger,
|
|
118
|
+
DropdownMenuContent,
|
|
119
|
+
DropdownMenuItem,
|
|
120
|
+
DropdownMenuCheckboxItem,
|
|
121
|
+
DropdownMenuRadioItem,
|
|
122
|
+
DropdownMenuLabel,
|
|
123
|
+
DropdownMenuSeparator,
|
|
124
|
+
DropdownMenuShortcut,
|
|
125
|
+
DropdownMenuGroup,
|
|
126
|
+
DropdownMenuPortal,
|
|
127
|
+
DropdownMenuSub,
|
|
128
|
+
DropdownMenuSubContent,
|
|
129
|
+
DropdownMenuSubTrigger,
|
|
130
|
+
DropdownMenuRadioGroup,
|
|
131
|
+
} from './DropdownMenu';
|
|
132
|
+
export type { DropdownMenuProps, DropdownMenuContentProps } from './DropdownMenu';
|
|
133
|
+
|
|
134
|
+
export { ToggleGroup, ToggleGroupItem } from './ToggleGroup';
|
|
135
|
+
export type { ToggleGroupProps, ToggleGroupItemProps } from './ToggleGroup';
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResponsiveContainer Template Component
|
|
3
|
+
* @description Responsive wrapper with mobile/desktop optimizations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { forwardRef, type HTMLAttributes } from 'react';
|
|
7
|
+
import { cn } from '../../infrastructure/utils';
|
|
8
|
+
import { useBreakpoint } from '../hooks/useMediaQuery';
|
|
9
|
+
import type { BaseProps, ChildrenProps } from '../../domain/types';
|
|
10
|
+
|
|
11
|
+
export type ResponsiveSize = 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
12
|
+
|
|
13
|
+
export interface ResponsiveContainerProps
|
|
14
|
+
extends HTMLAttributes<HTMLDivElement>,
|
|
15
|
+
BaseProps,
|
|
16
|
+
ChildrenProps {
|
|
17
|
+
/**
|
|
18
|
+
* Maximum width on mobile devices
|
|
19
|
+
* @default 'full'
|
|
20
|
+
*/
|
|
21
|
+
mobileMaxWidth?: ResponsiveSize;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maximum width on tablet devices
|
|
25
|
+
* @default 'lg'
|
|
26
|
+
*/
|
|
27
|
+
tabletMaxWidth?: ResponsiveSize;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Maximum width on desktop devices
|
|
31
|
+
* @default 'xl'
|
|
32
|
+
*/
|
|
33
|
+
desktopMaxWidth?: ResponsiveSize;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Padding on mobile devices
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
mobilePadding?: boolean;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Padding on tablet devices
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
tabletPadding?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Padding on desktop devices
|
|
49
|
+
* @default true
|
|
50
|
+
*/
|
|
51
|
+
desktopPadding?: boolean;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Center content horizontally
|
|
55
|
+
* @default true
|
|
56
|
+
*/
|
|
57
|
+
centered?: boolean;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Background gradient
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
gradient?: boolean;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Minimum height
|
|
67
|
+
* @default 'auto'
|
|
68
|
+
*/
|
|
69
|
+
minHeight?: 'auto' | 'screen' | 'full';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const maxWidthClasses: Record<ResponsiveSize, string> = {
|
|
73
|
+
sm: 'max-w-sm',
|
|
74
|
+
md: 'max-w-md',
|
|
75
|
+
lg: 'max-w-lg',
|
|
76
|
+
xl: 'max-w-xl',
|
|
77
|
+
full: 'max-w-full',
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const sizeBreakpointMap: Record<string, ResponsiveSize> = {
|
|
81
|
+
sm: 'mobileMaxWidth',
|
|
82
|
+
md: 'tabletMaxWidth',
|
|
83
|
+
lg: 'desktopMaxWidth',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export const ResponsiveContainer = forwardRef<
|
|
87
|
+
HTMLDivElement,
|
|
88
|
+
ResponsiveContainerProps
|
|
89
|
+
>(
|
|
90
|
+
(
|
|
91
|
+
{
|
|
92
|
+
children,
|
|
93
|
+
mobileMaxWidth = 'full',
|
|
94
|
+
tabletMaxWidth = 'lg',
|
|
95
|
+
desktopMaxWidth = 'xl',
|
|
96
|
+
mobilePadding = true,
|
|
97
|
+
tabletPadding = true,
|
|
98
|
+
desktopPadding = true,
|
|
99
|
+
centered = true,
|
|
100
|
+
gradient = false,
|
|
101
|
+
minHeight = 'auto',
|
|
102
|
+
className,
|
|
103
|
+
...props
|
|
104
|
+
},
|
|
105
|
+
ref
|
|
106
|
+
) => {
|
|
107
|
+
const breakpoint = useBreakpoint();
|
|
108
|
+
|
|
109
|
+
// Determine current max width based on breakpoint
|
|
110
|
+
const getCurrentMaxWidth = (): string => {
|
|
111
|
+
if (!breakpoint) return maxWidthClasses[mobileMaxWidth];
|
|
112
|
+
|
|
113
|
+
if (breakpoint === 'sm') {
|
|
114
|
+
return maxWidthClasses[mobileMaxWidth];
|
|
115
|
+
}
|
|
116
|
+
if (breakpoint === 'md') {
|
|
117
|
+
return maxWidthClasses[tabletMaxWidth];
|
|
118
|
+
}
|
|
119
|
+
return maxWidthClasses[desktopMaxWidth];
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Determine current padding based on breakpoint
|
|
123
|
+
const getCurrentPadding = (): string => {
|
|
124
|
+
if (!breakpoint) return mobilePadding ? 'px-4' : '';
|
|
125
|
+
|
|
126
|
+
if (breakpoint === 'sm') {
|
|
127
|
+
return mobilePadding ? 'px-4' : '';
|
|
128
|
+
}
|
|
129
|
+
if (breakpoint === 'md') {
|
|
130
|
+
return tabletPadding ? 'px-6' : '';
|
|
131
|
+
}
|
|
132
|
+
return desktopPadding ? 'px-8' : '';
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Determine current py padding based on breakpoint
|
|
136
|
+
const getCurrentPyPadding = (): string => {
|
|
137
|
+
if (!breakpoint) return 'py-8';
|
|
138
|
+
|
|
139
|
+
if (breakpoint === 'sm') {
|
|
140
|
+
return 'py-8';
|
|
141
|
+
}
|
|
142
|
+
if (breakpoint === 'md') {
|
|
143
|
+
return 'py-12';
|
|
144
|
+
}
|
|
145
|
+
return 'py-16';
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const minHeightClasses: Record<
|
|
149
|
+
typeof minHeight,
|
|
150
|
+
string
|
|
151
|
+
> = {
|
|
152
|
+
auto: '',
|
|
153
|
+
screen: 'min-h-screen',
|
|
154
|
+
full: 'min-h-full',
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
ref={ref}
|
|
160
|
+
className={cn(
|
|
161
|
+
'transition-theme',
|
|
162
|
+
gradient && 'bg-surface-gradient',
|
|
163
|
+
minHeightClasses[minHeight],
|
|
164
|
+
getCurrentPyPadding(),
|
|
165
|
+
getCurrentPadding(),
|
|
166
|
+
centered && 'mx-auto',
|
|
167
|
+
getCurrentMaxWidth(),
|
|
168
|
+
className
|
|
169
|
+
)}
|
|
170
|
+
{...props}
|
|
171
|
+
>
|
|
172
|
+
{children}
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
ResponsiveContainer.displayName = 'ResponsiveContainer';
|
|
@@ -21,3 +21,6 @@ export type { PageHeaderProps, TextAlign, HeaderSize } from './PageHeader';
|
|
|
21
21
|
|
|
22
22
|
export { ProjectSkeleton } from './ProjectSkeleton';
|
|
23
23
|
export type { ProjectSkeletonProps } from './ProjectSkeleton';
|
|
24
|
+
|
|
25
|
+
export { ResponsiveContainer } from './ResponsiveContainer';
|
|
26
|
+
export type { ResponsiveContainerProps, ResponsiveSize } from './ResponsiveContainer';
|