@sudobility/design-components 1.0.8
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/dist/aurora-background.d.ts +33 -0
- package/dist/aurora-background.d.ts.map +1 -0
- package/dist/badge-designer.d.ts +33 -0
- package/dist/badge-designer.d.ts.map +1 -0
- package/dist/color-picker-advanced.d.ts +8 -0
- package/dist/color-picker-advanced.d.ts.map +1 -0
- package/dist/color-picker.d.ts +42 -0
- package/dist/color-picker.d.ts.map +1 -0
- package/dist/color-swatch.d.ts +41 -0
- package/dist/color-swatch.d.ts.map +1 -0
- package/dist/gradient-banner.d.ts +47 -0
- package/dist/gradient-banner.d.ts.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +638 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +7 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/neumorphic-button.d.ts +33 -0
- package/dist/neumorphic-button.d.ts.map +1 -0
- package/dist/theme-switcher.d.ts +33 -0
- package/dist/theme-switcher.d.ts.map +1 -0
- package/package.json +52 -0
- package/src/aurora-background.tsx +60 -0
- package/src/badge-designer.tsx +60 -0
- package/src/color-picker-advanced.tsx +51 -0
- package/src/color-picker.tsx +188 -0
- package/src/color-swatch.tsx +151 -0
- package/src/gradient-banner.tsx +106 -0
- package/src/index.ts +16 -0
- package/src/neumorphic-button.tsx +60 -0
- package/src/theme-switcher.tsx +60 -0
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { cn } from '@sudobility/components';
|
|
3
|
+
|
|
4
|
+
export interface ColorPickerProps {
|
|
5
|
+
/** Selected color (hex format) */
|
|
6
|
+
value: string;
|
|
7
|
+
/** Change handler */
|
|
8
|
+
onChange: (color: string) => void;
|
|
9
|
+
/** Preset colors */
|
|
10
|
+
presets?: string[];
|
|
11
|
+
/** Show input field */
|
|
12
|
+
showInput?: boolean;
|
|
13
|
+
/** Show opacity slider */
|
|
14
|
+
showOpacity?: boolean;
|
|
15
|
+
/** Additional className */
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* ColorPicker Component
|
|
21
|
+
*
|
|
22
|
+
* Color picker with preset colors and custom input.
|
|
23
|
+
* Supports hex colors and opacity.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <ColorPicker
|
|
28
|
+
* value={color}
|
|
29
|
+
* onChange={setColor}
|
|
30
|
+
* showInput
|
|
31
|
+
* />
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <ColorPicker
|
|
37
|
+
* value={brandColor}
|
|
38
|
+
* onChange={handleColorChange}
|
|
39
|
+
* presets={['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A']}
|
|
40
|
+
* showOpacity
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const ColorPicker: React.FC<ColorPickerProps> = ({
|
|
45
|
+
value,
|
|
46
|
+
onChange,
|
|
47
|
+
presets = [
|
|
48
|
+
'#000000',
|
|
49
|
+
'#FFFFFF',
|
|
50
|
+
'#FF6B6B',
|
|
51
|
+
'#4ECDC4',
|
|
52
|
+
'#45B7D1',
|
|
53
|
+
'#FFA07A',
|
|
54
|
+
'#F7DC6F',
|
|
55
|
+
'#BB8FCE',
|
|
56
|
+
'#85C1E2',
|
|
57
|
+
'#F8B500',
|
|
58
|
+
'#52B788',
|
|
59
|
+
'#E63946',
|
|
60
|
+
'#1D3557',
|
|
61
|
+
'#457B9D',
|
|
62
|
+
'#A8DADC',
|
|
63
|
+
'#F4A261',
|
|
64
|
+
'#264653',
|
|
65
|
+
'#2A9D8F',
|
|
66
|
+
],
|
|
67
|
+
showInput = true,
|
|
68
|
+
showOpacity = false,
|
|
69
|
+
className,
|
|
70
|
+
}) => {
|
|
71
|
+
const [inputValue, setInputValue] = useState(value);
|
|
72
|
+
const [opacity, setOpacity] = useState(100);
|
|
73
|
+
|
|
74
|
+
const handleColorClick = (color: string) => {
|
|
75
|
+
onChange(color);
|
|
76
|
+
setInputValue(color);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
80
|
+
const newValue = e.target.value;
|
|
81
|
+
setInputValue(newValue);
|
|
82
|
+
|
|
83
|
+
// Validate hex color
|
|
84
|
+
if (/^#[0-9A-F]{6}$/i.test(newValue)) {
|
|
85
|
+
onChange(newValue);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleOpacityChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
90
|
+
setOpacity(Number(e.target.value));
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const hexToRgba = (hex: string, alpha: number) => {
|
|
94
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
95
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
96
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
97
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha / 100})`;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const displayColor = showOpacity ? hexToRgba(value, opacity) : value;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div className={cn('bg-white dark:bg-gray-900 rounded-lg p-4', className)}>
|
|
104
|
+
{/* Current color preview */}
|
|
105
|
+
<div className='mb-4'>
|
|
106
|
+
<div className='flex items-center gap-3'>
|
|
107
|
+
<div
|
|
108
|
+
className='w-16 h-16 rounded-lg border-2 border-gray-200 dark:border-gray-700 shadow-sm'
|
|
109
|
+
style={{ backgroundColor: displayColor }}
|
|
110
|
+
/>
|
|
111
|
+
<div className='flex-1'>
|
|
112
|
+
<p className='text-sm font-medium text-gray-900 dark:text-white'>
|
|
113
|
+
Selected Color
|
|
114
|
+
</p>
|
|
115
|
+
<p className='text-xs text-gray-600 dark:text-gray-400 mt-1 font-mono'>
|
|
116
|
+
{value}
|
|
117
|
+
{showOpacity && ` (${opacity}%)`}
|
|
118
|
+
</p>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
|
|
123
|
+
{/* Preset colors */}
|
|
124
|
+
<div className='mb-4'>
|
|
125
|
+
<p className='text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
|
126
|
+
Preset Colors
|
|
127
|
+
</p>
|
|
128
|
+
<div className='grid grid-cols-6 gap-2'>
|
|
129
|
+
{presets.map(preset => (
|
|
130
|
+
<button
|
|
131
|
+
key={preset}
|
|
132
|
+
onClick={() => handleColorClick(preset)}
|
|
133
|
+
className={cn(
|
|
134
|
+
'w-full aspect-square rounded-md border-2 transition-all hover:scale-110',
|
|
135
|
+
preset === value
|
|
136
|
+
? 'border-blue-500 dark:border-blue-400 ring-2 ring-blue-200 dark:ring-blue-800'
|
|
137
|
+
: 'border-gray-200 dark:border-gray-700'
|
|
138
|
+
)}
|
|
139
|
+
style={{ backgroundColor: preset }}
|
|
140
|
+
aria-label={`Select color ${preset}`}
|
|
141
|
+
/>
|
|
142
|
+
))}
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Custom color input */}
|
|
147
|
+
{showInput && (
|
|
148
|
+
<div className='mb-4'>
|
|
149
|
+
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
|
150
|
+
Custom Color (Hex)
|
|
151
|
+
</label>
|
|
152
|
+
<div className='flex gap-2'>
|
|
153
|
+
<input
|
|
154
|
+
type='text'
|
|
155
|
+
value={inputValue}
|
|
156
|
+
onChange={handleInputChange}
|
|
157
|
+
placeholder='#000000'
|
|
158
|
+
className='flex-1 px-3 py-2 text-sm font-mono bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400'
|
|
159
|
+
/>
|
|
160
|
+
<input
|
|
161
|
+
type='color'
|
|
162
|
+
value={value}
|
|
163
|
+
onChange={e => handleColorClick(e.target.value)}
|
|
164
|
+
className='w-12 h-10 rounded-md cursor-pointer'
|
|
165
|
+
/>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
)}
|
|
169
|
+
|
|
170
|
+
{/* Opacity slider */}
|
|
171
|
+
{showOpacity && (
|
|
172
|
+
<div>
|
|
173
|
+
<label className='block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2'>
|
|
174
|
+
Opacity: {opacity}%
|
|
175
|
+
</label>
|
|
176
|
+
<input
|
|
177
|
+
type='range'
|
|
178
|
+
min='0'
|
|
179
|
+
max='100'
|
|
180
|
+
value={opacity}
|
|
181
|
+
onChange={handleOpacityChange}
|
|
182
|
+
className='w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer'
|
|
183
|
+
/>
|
|
184
|
+
</div>
|
|
185
|
+
)}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { cn } from '@sudobility/components';
|
|
3
|
+
|
|
4
|
+
export interface Color {
|
|
5
|
+
/** Color hex value */
|
|
6
|
+
hex: string;
|
|
7
|
+
/** Color name */
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ColorSwatchProps {
|
|
12
|
+
/** Colors to display */
|
|
13
|
+
colors: Color[] | string[];
|
|
14
|
+
/** Selected color */
|
|
15
|
+
selectedColor?: string;
|
|
16
|
+
/** Color change handler */
|
|
17
|
+
onColorSelect?: (color: string) => void;
|
|
18
|
+
/** Swatch size */
|
|
19
|
+
size?: 'sm' | 'md' | 'lg';
|
|
20
|
+
/** Show color names */
|
|
21
|
+
showNames?: boolean;
|
|
22
|
+
/** Show copy button */
|
|
23
|
+
showCopy?: boolean;
|
|
24
|
+
/** Additional className */
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* ColorSwatch Component
|
|
30
|
+
*
|
|
31
|
+
* Color palette display with selection and copy functionality.
|
|
32
|
+
* Useful for color pickers and design systems.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```tsx
|
|
36
|
+
* <ColorSwatch
|
|
37
|
+
* colors={['#FF0000', '#00FF00', '#0000FF']}
|
|
38
|
+
* selectedColor={activeColor}
|
|
39
|
+
* onColorSelect={setActiveColor}
|
|
40
|
+
* showCopy
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export const ColorSwatch: React.FC<ColorSwatchProps> = ({
|
|
45
|
+
colors,
|
|
46
|
+
selectedColor,
|
|
47
|
+
onColorSelect,
|
|
48
|
+
size = 'md',
|
|
49
|
+
showNames = false,
|
|
50
|
+
showCopy = false,
|
|
51
|
+
className,
|
|
52
|
+
}) => {
|
|
53
|
+
const [copiedColor, setCopiedColor] = useState<string | null>(null);
|
|
54
|
+
|
|
55
|
+
const sizeClasses = {
|
|
56
|
+
sm: 'w-8 h-8',
|
|
57
|
+
md: 'w-12 h-12',
|
|
58
|
+
lg: 'w-16 h-16',
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const normalizeColors = (): Color[] => {
|
|
62
|
+
return colors.map(color =>
|
|
63
|
+
typeof color === 'string' ? { hex: color } : color
|
|
64
|
+
);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const handleCopy = async (hex: string, e: React.MouseEvent) => {
|
|
68
|
+
e.stopPropagation();
|
|
69
|
+
await navigator.clipboard.writeText(hex);
|
|
70
|
+
setCopiedColor(hex);
|
|
71
|
+
setTimeout(() => setCopiedColor(null), 2000);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const normalizedColors = normalizeColors();
|
|
75
|
+
|
|
76
|
+
return (
|
|
77
|
+
<div className={cn('flex flex-wrap gap-3', className)}>
|
|
78
|
+
{normalizedColors.map((color, index) => {
|
|
79
|
+
const isSelected = selectedColor === color.hex;
|
|
80
|
+
const isCopied = copiedColor === color.hex;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div key={index} className='flex flex-col items-center gap-2'>
|
|
84
|
+
<button
|
|
85
|
+
onClick={() => onColorSelect?.(color.hex)}
|
|
86
|
+
className={cn(
|
|
87
|
+
'rounded-lg transition-all relative group',
|
|
88
|
+
sizeClasses[size],
|
|
89
|
+
isSelected &&
|
|
90
|
+
'ring-2 ring-blue-500 ring-offset-2 dark:ring-offset-gray-900',
|
|
91
|
+
!isSelected && 'hover:scale-110'
|
|
92
|
+
)}
|
|
93
|
+
style={{ backgroundColor: color.hex }}
|
|
94
|
+
aria-label={color.name || color.hex}
|
|
95
|
+
>
|
|
96
|
+
{showCopy && (
|
|
97
|
+
<div
|
|
98
|
+
className='absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity bg-black/50 rounded-lg'
|
|
99
|
+
onClick={e => handleCopy(color.hex, e)}
|
|
100
|
+
>
|
|
101
|
+
{isCopied ? (
|
|
102
|
+
<svg
|
|
103
|
+
className='w-4 h-4 text-white'
|
|
104
|
+
fill='none'
|
|
105
|
+
stroke='currentColor'
|
|
106
|
+
viewBox='0 0 24 24'
|
|
107
|
+
>
|
|
108
|
+
<path
|
|
109
|
+
strokeLinecap='round'
|
|
110
|
+
strokeLinejoin='round'
|
|
111
|
+
strokeWidth={2}
|
|
112
|
+
d='M5 13l4 4L19 7'
|
|
113
|
+
/>
|
|
114
|
+
</svg>
|
|
115
|
+
) : (
|
|
116
|
+
<svg
|
|
117
|
+
className='w-4 h-4 text-white'
|
|
118
|
+
fill='none'
|
|
119
|
+
stroke='currentColor'
|
|
120
|
+
viewBox='0 0 24 24'
|
|
121
|
+
>
|
|
122
|
+
<path
|
|
123
|
+
strokeLinecap='round'
|
|
124
|
+
strokeLinejoin='round'
|
|
125
|
+
strokeWidth={2}
|
|
126
|
+
d='M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z'
|
|
127
|
+
/>
|
|
128
|
+
</svg>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
)}
|
|
132
|
+
</button>
|
|
133
|
+
|
|
134
|
+
{showNames && (
|
|
135
|
+
<div className='text-center'>
|
|
136
|
+
{color.name && (
|
|
137
|
+
<p className='text-xs font-medium text-gray-900 dark:text-white'>
|
|
138
|
+
{color.name}
|
|
139
|
+
</p>
|
|
140
|
+
)}
|
|
141
|
+
<p className='text-xs text-gray-600 dark:text-gray-400 font-mono'>
|
|
142
|
+
{color.hex}
|
|
143
|
+
</p>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
})}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { cn } from '@sudobility/components';
|
|
3
|
+
|
|
4
|
+
export interface GradientBannerProps {
|
|
5
|
+
/** Main content of the banner */
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
/** Gradient color variant */
|
|
8
|
+
variant?:
|
|
9
|
+
| 'blue-purple'
|
|
10
|
+
| 'green-blue'
|
|
11
|
+
| 'orange-red'
|
|
12
|
+
| 'gray'
|
|
13
|
+
| 'light'
|
|
14
|
+
| 'custom';
|
|
15
|
+
/** Custom gradient classes (when variant is 'custom') */
|
|
16
|
+
gradientClasses?: string;
|
|
17
|
+
/** Size/padding variant */
|
|
18
|
+
size?: 'sm' | 'md' | 'lg';
|
|
19
|
+
/** Border radius */
|
|
20
|
+
rounded?: 'md' | 'lg' | 'xl' | '2xl';
|
|
21
|
+
/** Show border */
|
|
22
|
+
bordered?: boolean;
|
|
23
|
+
/** Border color classes (when bordered is true) */
|
|
24
|
+
borderClasses?: string;
|
|
25
|
+
/** Additional className for the container */
|
|
26
|
+
className?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* GradientBanner Component
|
|
31
|
+
*
|
|
32
|
+
* A versatile banner component with gradient backgrounds for highlighting
|
|
33
|
+
* important content, CTAs, or feature sections.
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <GradientBanner variant="blue-purple" size="lg">
|
|
38
|
+
* <h3 className={textVariants.heading.h3()}>Special Offer</h3>
|
|
39
|
+
* <p>Get started with Web3 Email today!</p>
|
|
40
|
+
* </GradientBanner>
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* // Light variant with border
|
|
46
|
+
* <GradientBanner
|
|
47
|
+
* variant="light"
|
|
48
|
+
* bordered
|
|
49
|
+
* borderClasses="border-2 border-blue-200"
|
|
50
|
+
* >
|
|
51
|
+
* <Alert variant="info">Important information</Alert>
|
|
52
|
+
* </GradientBanner>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export const GradientBanner: React.FC<GradientBannerProps> = ({
|
|
56
|
+
children,
|
|
57
|
+
variant = 'blue-purple',
|
|
58
|
+
gradientClasses,
|
|
59
|
+
size = 'md',
|
|
60
|
+
rounded = '2xl',
|
|
61
|
+
bordered = false,
|
|
62
|
+
borderClasses = 'border border-white/20',
|
|
63
|
+
className,
|
|
64
|
+
}) => {
|
|
65
|
+
// Gradient color variants
|
|
66
|
+
const gradientVariants = {
|
|
67
|
+
'blue-purple': 'bg-gradient-to-r from-blue-600 to-purple-600 text-white',
|
|
68
|
+
'green-blue': 'bg-gradient-to-r from-green-600 to-blue-600 text-white',
|
|
69
|
+
'orange-red': 'bg-gradient-to-r from-orange-600 to-red-600 text-white',
|
|
70
|
+
gray: 'bg-gradient-to-r from-gray-600 to-gray-800 dark:from-gray-700 dark:to-gray-900 text-white',
|
|
71
|
+
light:
|
|
72
|
+
'bg-gradient-to-r from-blue-50 to-purple-50 dark:from-blue-900/20 dark:to-purple-900/20 text-gray-900 dark:text-gray-100',
|
|
73
|
+
custom:
|
|
74
|
+
gradientClasses ||
|
|
75
|
+
'bg-gradient-to-r from-blue-600 to-purple-600 text-white',
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Size/padding configurations
|
|
79
|
+
const sizeClasses = {
|
|
80
|
+
sm: 'p-4',
|
|
81
|
+
md: 'p-6',
|
|
82
|
+
lg: 'p-8',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Border radius configurations
|
|
86
|
+
const roundedClasses = {
|
|
87
|
+
md: 'rounded-md',
|
|
88
|
+
lg: 'rounded-lg',
|
|
89
|
+
xl: 'rounded-xl',
|
|
90
|
+
'2xl': 'rounded-2xl',
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div
|
|
95
|
+
className={cn(
|
|
96
|
+
gradientVariants[variant],
|
|
97
|
+
sizeClasses[size],
|
|
98
|
+
roundedClasses[rounded],
|
|
99
|
+
bordered && borderClasses,
|
|
100
|
+
className
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design-components
|
|
3
|
+
*
|
|
4
|
+
* Specialized components for the design domain
|
|
5
|
+
*
|
|
6
|
+
* @package @sudobility/design-components
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './aurora-background';
|
|
10
|
+
export * from './badge-designer';
|
|
11
|
+
export * from './color-picker-advanced';
|
|
12
|
+
export * from './color-picker';
|
|
13
|
+
export * from './color-swatch';
|
|
14
|
+
export * from './gradient-banner';
|
|
15
|
+
export * from './neumorphic-button';
|
|
16
|
+
export * from './theme-switcher';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { cn } from '@sudobility/components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UneumorphicUbutton Component
|
|
5
|
+
*
|
|
6
|
+
* A reusable UneumorphicUbutton component with full dark mode support.
|
|
7
|
+
* Optimized for accessibility and AI-assisted development.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <UneumorphicUbutton className="custom-class" />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This component supports:
|
|
17
|
+
* - Light and dark themes automatically
|
|
18
|
+
* - Responsive design
|
|
19
|
+
* - Accessibility features
|
|
20
|
+
* - TypeScript type safety
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://docs.example.com/components/neumorphic-button}
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface UneumorphicUbuttonProps {
|
|
26
|
+
/** Additional CSS classes */
|
|
27
|
+
className?: string;
|
|
28
|
+
/** Component children */
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
/** Disabled state */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Callback when component is interacted with */
|
|
33
|
+
onClick?: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const UneumorphicUbutton = ({
|
|
37
|
+
className,
|
|
38
|
+
children,
|
|
39
|
+
disabled = false,
|
|
40
|
+
onClick,
|
|
41
|
+
}: UneumorphicUbuttonProps) => {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
'p-4 rounded-lg border transition-colors',
|
|
46
|
+
'bg-white dark:bg-gray-900',
|
|
47
|
+
'border-gray-200 dark:border-gray-700',
|
|
48
|
+
'text-gray-900 dark:text-white',
|
|
49
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
50
|
+
'hover:bg-gray-50 dark:hover:bg-gray-800',
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
onClick={disabled ? undefined : onClick}
|
|
54
|
+
role='region'
|
|
55
|
+
aria-label='UneumorphicUbutton'
|
|
56
|
+
>
|
|
57
|
+
{children || 'UneumorphicUbutton Component'}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { cn } from '@sudobility/components';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* UthemeUswitcher Component
|
|
5
|
+
*
|
|
6
|
+
* A reusable UthemeUswitcher component with full dark mode support.
|
|
7
|
+
* Optimized for accessibility and AI-assisted development.
|
|
8
|
+
*
|
|
9
|
+
* @component
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <UthemeUswitcher className="custom-class" />
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* This component supports:
|
|
17
|
+
* - Light and dark themes automatically
|
|
18
|
+
* - Responsive design
|
|
19
|
+
* - Accessibility features
|
|
20
|
+
* - TypeScript type safety
|
|
21
|
+
*
|
|
22
|
+
* @see {@link https://docs.example.com/components/theme-switcher}
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
export interface UthemeUswitcherProps {
|
|
26
|
+
/** Additional CSS classes */
|
|
27
|
+
className?: string;
|
|
28
|
+
/** Component children */
|
|
29
|
+
children?: React.ReactNode;
|
|
30
|
+
/** Disabled state */
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
/** Callback when component is interacted with */
|
|
33
|
+
onClick?: () => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const UthemeUswitcher = ({
|
|
37
|
+
className,
|
|
38
|
+
children,
|
|
39
|
+
disabled = false,
|
|
40
|
+
onClick,
|
|
41
|
+
}: UthemeUswitcherProps) => {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={cn(
|
|
45
|
+
'p-4 rounded-lg border transition-colors',
|
|
46
|
+
'bg-white dark:bg-gray-900',
|
|
47
|
+
'border-gray-200 dark:border-gray-700',
|
|
48
|
+
'text-gray-900 dark:text-white',
|
|
49
|
+
disabled && 'opacity-50 cursor-not-allowed',
|
|
50
|
+
'hover:bg-gray-50 dark:hover:bg-gray-800',
|
|
51
|
+
className
|
|
52
|
+
)}
|
|
53
|
+
onClick={disabled ? undefined : onClick}
|
|
54
|
+
role='region'
|
|
55
|
+
aria-label='UthemeUswitcher'
|
|
56
|
+
>
|
|
57
|
+
{children || 'UthemeUswitcher Component'}
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
};
|