@hellboy/ds 0.1.2

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 (137) hide show
  1. package/README.md +111 -0
  2. package/dist/index.css +3699 -0
  3. package/dist/index.css.map +1 -0
  4. package/dist/index.d.mts +1087 -0
  5. package/dist/index.d.ts +1087 -0
  6. package/dist/index.js +3391 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.mjs +3287 -0
  9. package/dist/index.mjs.map +1 -0
  10. package/dist/theme.css +55 -0
  11. package/hellboy-ds-0.1.2.tgz +0 -0
  12. package/package.json +42 -0
  13. package/src/components/badge/Badge.tsx +29 -0
  14. package/src/components/badge/index.ts +1 -0
  15. package/src/components/banner/Banner.tsx +48 -0
  16. package/src/components/banner/banner.css +44 -0
  17. package/src/components/banner/index.ts +1 -0
  18. package/src/components/button/button.tsx +127 -0
  19. package/src/components/button/index.ts +1 -0
  20. package/src/components/card/card.tsx +57 -0
  21. package/src/components/card/index.ts +1 -0
  22. package/src/components/checkbox/Checkbox.tsx +98 -0
  23. package/src/components/checkbox/index.ts +1 -0
  24. package/src/components/code-block/code-block.tsx +44 -0
  25. package/src/components/code-block/index.ts +1 -0
  26. package/src/components/color-control/color-control.tsx +322 -0
  27. package/src/components/color-control/index.ts +1 -0
  28. package/src/components/drag-handle/DragHandle.tsx +78 -0
  29. package/src/components/drag-handle/index.ts +1 -0
  30. package/src/components/drawer/drawer.tsx +82 -0
  31. package/src/components/drawer/index.ts +1 -0
  32. package/src/components/floating-bar/floating-bar.tsx +52 -0
  33. package/src/components/floating-bar/index.ts +2 -0
  34. package/src/components/footer/footer.tsx +28 -0
  35. package/src/components/footer/index.ts +1 -0
  36. package/src/components/grid/Grid.tsx +53 -0
  37. package/src/components/grid/index.ts +1 -0
  38. package/src/components/header/header.tsx +57 -0
  39. package/src/components/header/index.ts +1 -0
  40. package/src/components/icons/icons.tsx +44 -0
  41. package/src/components/icons/index.ts +1 -0
  42. package/src/components/index.ts +29 -0
  43. package/src/components/input/DatePicker.tsx +133 -0
  44. package/src/components/input/Input.tsx +220 -0
  45. package/src/components/input/InputDate.tsx +10 -0
  46. package/src/components/input/InputDateTime.tsx +10 -0
  47. package/src/components/input/InputEmail.tsx +10 -0
  48. package/src/components/input/InputField.tsx +137 -0
  49. package/src/components/input/InputNumber.tsx +10 -0
  50. package/src/components/input/InputPassword.tsx +10 -0
  51. package/src/components/input/InputSearch.tsx +10 -0
  52. package/src/components/input/InputTel.tsx +10 -0
  53. package/src/components/input/InputText.tsx +10 -0
  54. package/src/components/input/InputTime.tsx +10 -0
  55. package/src/components/input/InputUrl.tsx +10 -0
  56. package/src/components/input/TimePicker.tsx +151 -0
  57. package/src/components/input/index.ts +11 -0
  58. package/src/components/layout/Layout.tsx +244 -0
  59. package/src/components/layout/index.ts +1 -0
  60. package/src/components/list/List.tsx +159 -0
  61. package/src/components/list/index.ts +1 -0
  62. package/src/components/navbar/MenuCategory.tsx +20 -0
  63. package/src/components/navbar/MenuGroup.tsx +288 -0
  64. package/src/components/navbar/MenuItem.tsx +65 -0
  65. package/src/components/navbar/Navbar.tsx +23 -0
  66. package/src/components/navbar/index.ts +4 -0
  67. package/src/components/page/index.ts +1 -0
  68. package/src/components/page/page.tsx +46 -0
  69. package/src/components/page-index/PageIndex.tsx +275 -0
  70. package/src/components/page-index/index.ts +1 -0
  71. package/src/components/popover/index.ts +1 -0
  72. package/src/components/popover/popover.tsx +199 -0
  73. package/src/components/radio/Radio.tsx +176 -0
  74. package/src/components/radio/index.ts +1 -0
  75. package/src/components/section/index.ts +1 -0
  76. package/src/components/section/section.tsx +66 -0
  77. package/src/components/select/Select.tsx +212 -0
  78. package/src/components/select/index.ts +1 -0
  79. package/src/components/slider/Slider.tsx +267 -0
  80. package/src/components/slider/index.ts +1 -0
  81. package/src/components/switch/index.ts +1 -0
  82. package/src/components/switch/switch.tsx +99 -0
  83. package/src/components/table/Table.tsx +147 -0
  84. package/src/components/table/index.ts +1 -0
  85. package/src/components/theme-control/index.ts +1 -0
  86. package/src/components/theme-control/theme-control.tsx +78 -0
  87. package/src/components/tooltip/index.ts +1 -0
  88. package/src/components/tooltip/tooltip.tsx +207 -0
  89. package/src/contexts/NavbarTooltipContext.tsx +48 -0
  90. package/src/contexts/index.ts +1 -0
  91. package/src/foundations/motion.md +136 -0
  92. package/src/index.ts +40 -0
  93. package/src/style/_shared/field.css +69 -0
  94. package/src/style/components/badge/badge.css +74 -0
  95. package/src/style/components/button/button.css +244 -0
  96. package/src/style/components/card/card.css +69 -0
  97. package/src/style/components/checkbox.css +142 -0
  98. package/src/style/components/code-block/code-block.css +34 -0
  99. package/src/style/components/color-control/color-control.css +126 -0
  100. package/src/style/components/drag-handle/drag-handle.css +68 -0
  101. package/src/style/components/drawer/drawer.css +210 -0
  102. package/src/style/components/floating-bar/floating-bar.css +39 -0
  103. package/src/style/components/footer/footer.css +108 -0
  104. package/src/style/components/grid/grid.css +33 -0
  105. package/src/style/components/header/header.css +44 -0
  106. package/src/style/components/icons/icons.css +44 -0
  107. package/src/style/components/input/input.css +393 -0
  108. package/src/style/components/layout/layout.css +205 -0
  109. package/src/style/components/list/list.css +140 -0
  110. package/src/style/components/navbar/navbar.css +342 -0
  111. package/src/style/components/page/page.css +46 -0
  112. package/src/style/components/page-index/page-index.css +158 -0
  113. package/src/style/components/popover/popover.css +44 -0
  114. package/src/style/components/radio.css +178 -0
  115. package/src/style/components/section/section.css +67 -0
  116. package/src/style/components/select/select.css +143 -0
  117. package/src/style/components/slider/slider.css +159 -0
  118. package/src/style/components/switch/switch.css +267 -0
  119. package/src/style/components/table/table.css +108 -0
  120. package/src/style/components/theme-control/theme-control.css +35 -0
  121. package/src/style/components/tooltip/tooltip.css +52 -0
  122. package/src/style/foundations/global.css +316 -0
  123. package/src/style/foundations/motion.css +164 -0
  124. package/src/style/foundations/spacing.css +51 -0
  125. package/src/style/foundations/typography.css +39 -0
  126. package/src/style/foundations/z-index.css +81 -0
  127. package/src/style/modes/dark.css +146 -0
  128. package/src/style/modes/light.css +147 -0
  129. package/src/style/semantic.css +52 -0
  130. package/src/style/styles.css +51 -0
  131. package/src/style/themes/theme.json +37 -0
  132. package/src/utils/README.md +305 -0
  133. package/src/utils/USER_PREFERENCES.md +558 -0
  134. package/src/utils/theme.ts +127 -0
  135. package/src/utils/user-preferences.ts +577 -0
  136. package/tsconfig.json +25 -0
  137. package/tsup.config.ts +52 -0
@@ -0,0 +1,66 @@
1
+ import React, { ReactNode, useMemo } from 'react';
2
+
3
+ export type SectionSize = 'sm' | 'md' | 'lg';
4
+
5
+ export interface SectionProps {
6
+ /**
7
+ * Section title
8
+ */
9
+ title?: ReactNode;
10
+
11
+ /**
12
+ * Section content
13
+ */
14
+ children: ReactNode;
15
+
16
+ /**
17
+ * Section size/padding
18
+ * @default 'md'
19
+ */
20
+ size?: SectionSize;
21
+
22
+ /**
23
+ * Custom class name
24
+ */
25
+ className?: string;
26
+ }
27
+
28
+ /**
29
+ * Section component for content areas
30
+ * Provides consistent spacing and styling for content sections
31
+ */
32
+ export const Section: React.FC<SectionProps> = ({ title, children, size = 'md', className }) => {
33
+ const sectionClasses = [
34
+ 'section',
35
+ size !== 'md' && `section--${size}`,
36
+ className,
37
+ ]
38
+ .filter(Boolean)
39
+ .join(' ');
40
+
41
+ // Generate ID from title for page index navigation
42
+ const sectionId = useMemo(() => {
43
+ if (typeof title === 'string' && title.trim()) {
44
+ return title
45
+ .toLowerCase()
46
+ .replace(/[^\w\s-]/g, '') // Remove special characters
47
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
48
+ .replace(/-+/g, '-') // Replace multiple hyphens with single
49
+ .trim();
50
+ }
51
+ return undefined;
52
+ }, [title]);
53
+
54
+ const titleText = typeof title === 'string' ? title : undefined;
55
+
56
+ return (
57
+ <section
58
+ className={sectionClasses}
59
+ id={sectionId}
60
+ data-section-title={titleText}
61
+ >
62
+ {title && <h2 className="section__title">{title}</h2>}
63
+ <div className="section__content">{children}</div>
64
+ </section>
65
+ );
66
+ };
@@ -0,0 +1,212 @@
1
+ import * as React from 'react';
2
+ import { Icon } from '../icons/icons';
3
+ import { Popover } from '../popover/popover';
4
+ import { List, ListItem } from '../list/List';
5
+ import '../../style/components/select/select.css';
6
+
7
+ export interface SelectOption {
8
+ value: string;
9
+ label: string;
10
+ icon?: string;
11
+ disabled?: boolean;
12
+ }
13
+
14
+ export interface SelectProps {
15
+ /**
16
+ * Select options
17
+ */
18
+ options: SelectOption[];
19
+
20
+ /**
21
+ * Current value
22
+ */
23
+ value?: string;
24
+
25
+ /**
26
+ * Change handler
27
+ */
28
+ onChange?: (value: string) => void;
29
+
30
+ /**
31
+ * Label text
32
+ */
33
+ label?: string;
34
+
35
+ /**
36
+ * Placeholder when no value selected
37
+ */
38
+ placeholder?: string;
39
+
40
+ /**
41
+ * Helper text shown below the select
42
+ */
43
+ helperText?: string;
44
+
45
+ /**
46
+ * Error message - when present, select is in error state
47
+ */
48
+ error?: string;
49
+
50
+ /**
51
+ * Whether the select is disabled
52
+ */
53
+ disabled?: boolean;
54
+
55
+ /**
56
+ * Full width select
57
+ * @default false
58
+ */
59
+ fullWidth?: boolean;
60
+
61
+ /**
62
+ * Size variant
63
+ * @default 'md'
64
+ */
65
+ size?: 'sm' | 'md' | 'lg';
66
+
67
+ /**
68
+ * Custom class name
69
+ */
70
+ className?: string;
71
+
72
+ /**
73
+ * ID for accessibility
74
+ */
75
+ id?: string;
76
+ }
77
+
78
+ export const Select: React.FC<SelectProps> = ({
79
+ options,
80
+ value,
81
+ onChange,
82
+ label,
83
+ placeholder = 'Select an option',
84
+ helperText,
85
+ error,
86
+ disabled = false,
87
+ fullWidth = false,
88
+ size = 'md',
89
+ className = '',
90
+ id,
91
+ }) => {
92
+ const [isOpen, setIsOpen] = React.useState(false);
93
+ const selectId = id || `select-${React.useId()}`;
94
+
95
+ const selectedOption = options.find((opt) => opt.value === value);
96
+
97
+ const handleSelect = (optionValue: string) => {
98
+ onChange?.(optionValue);
99
+ setIsOpen(false);
100
+ };
101
+
102
+ const handleKeyDown = (e: React.KeyboardEvent) => {
103
+ if (disabled) return;
104
+
105
+ if (e.key === 'Enter' || e.key === ' ') {
106
+ e.preventDefault();
107
+ setIsOpen(!isOpen);
108
+ } else if (e.key === 'Escape') {
109
+ setIsOpen(false);
110
+ } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
111
+ e.preventDefault();
112
+ if (!isOpen) {
113
+ setIsOpen(true);
114
+ }
115
+ }
116
+ };
117
+
118
+ const containerClasses = [
119
+ 'select-container',
120
+ fullWidth && 'select-container--full-width',
121
+ className,
122
+ ]
123
+ .filter(Boolean)
124
+ .join(' ');
125
+
126
+ const triggerClasses = [
127
+ 'select__trigger',
128
+ `select__trigger--${size}`,
129
+ error && 'select__trigger--error',
130
+ disabled && 'select__trigger--disabled',
131
+ isOpen && 'select__trigger--open',
132
+ ]
133
+ .filter(Boolean)
134
+ .join(' ');
135
+
136
+ return (
137
+ <div className={containerClasses}>
138
+ {label && (
139
+ <label htmlFor={selectId} className="select__label">
140
+ {label}
141
+ </label>
142
+ )}
143
+ <Popover
144
+ trigger={
145
+ <button
146
+ id={selectId}
147
+ type="button"
148
+ className={triggerClasses}
149
+ onClick={() => !disabled && setIsOpen(!isOpen)}
150
+ onKeyDown={handleKeyDown}
151
+ disabled={disabled}
152
+ aria-haspopup="listbox"
153
+ aria-expanded={isOpen}
154
+ aria-labelledby={label ? `${selectId}-label` : undefined}
155
+ aria-describedby={
156
+ error ? `${selectId}-error` : helperText ? `${selectId}-helper` : undefined
157
+ }
158
+ aria-invalid={error ? 'true' : 'false'}
159
+ >
160
+ <span className="select__trigger-content">
161
+ {selectedOption?.icon && (
162
+ <Icon
163
+ name={selectedOption.icon}
164
+ size={size === 'sm' ? 16 : size === 'lg' ? 24 : 20}
165
+ />
166
+ )}
167
+ <span className="select__trigger-text">
168
+ {selectedOption ? selectedOption.label : placeholder}
169
+ </span>
170
+ </span>
171
+ <Icon
172
+ name="chevron-down"
173
+ size={size === 'sm' ? 16 : size === 'lg' ? 24 : 20}
174
+ className="select__trigger-icon"
175
+ />
176
+ </button>
177
+ }
178
+ isOpen={isOpen}
179
+ onToggle={() => !disabled && setIsOpen(!isOpen)}
180
+ placement="bottom"
181
+ >
182
+ <div className="select__dropdown" role="listbox">
183
+ <List>
184
+ {options.map((option) => (
185
+ <ListItem
186
+ key={option.value}
187
+ icon={option.icon}
188
+ selected={option.value === value}
189
+ disabled={option.disabled}
190
+ onClick={() => !option.disabled && handleSelect(option.value)}
191
+ role="option"
192
+ aria-selected={option.value === value}
193
+ >
194
+ {option.label}
195
+ </ListItem>
196
+ ))}
197
+ </List>
198
+ </div>
199
+ </Popover>
200
+ {error && (
201
+ <p id={`${selectId}-error`} className="select__message select__message--error">
202
+ {error}
203
+ </p>
204
+ )}
205
+ {helperText && !error && (
206
+ <p id={`${selectId}-helper`} className="select__message">
207
+ {helperText}
208
+ </p>
209
+ )}
210
+ </div>
211
+ );
212
+ };
@@ -0,0 +1 @@
1
+ export { Select, type SelectProps, type SelectOption } from './Select';
@@ -0,0 +1,267 @@
1
+ import React, { useState, useCallback, useRef } from 'react';
2
+ import { Icon } from '../icons/icons';
3
+ import '../../style/components/slider/slider.css';
4
+
5
+ export interface SliderProps {
6
+ /**
7
+ * Minimum value
8
+ * @default 0
9
+ */
10
+ min?: number;
11
+ /**
12
+ * Maximum value
13
+ * @default 100
14
+ */
15
+ max?: number;
16
+ /**
17
+ * Current value
18
+ */
19
+ value: number;
20
+ /**
21
+ * Step increment
22
+ * @default 1
23
+ */
24
+ step?: number;
25
+ /**
26
+ * Callback when value changes
27
+ */
28
+ onChange: (value: number) => void;
29
+ /**
30
+ * Callback when drag starts
31
+ */
32
+ onDragStart?: () => void;
33
+ /**
34
+ * Callback when drag ends (value committed)
35
+ */
36
+ onChangeEnd?: (value: number) => void;
37
+ /**
38
+ * Label for the slider
39
+ */
40
+ label?: string;
41
+ /**
42
+ * Show value display
43
+ * @default true
44
+ */
45
+ showValue?: boolean;
46
+ /**
47
+ * Custom class name
48
+ */
49
+ className?: string;
50
+ /**
51
+ * Disabled state
52
+ * @default false
53
+ */
54
+ disabled?: boolean;
55
+ /**
56
+ * Slider type for different backgrounds
57
+ * @default 'default'
58
+ */
59
+ type?: 'default' | 'hue' | 'saturation' | 'lightness';
60
+ /**
61
+ * Base color for saturation/lightness gradients (hsl values)
62
+ */
63
+ baseHue?: number;
64
+ /**
65
+ * Base saturation for lightness gradient (0-100)
66
+ */
67
+ baseSaturation?: number;
68
+ }
69
+
70
+ export const Slider: React.FC<SliderProps> = ({
71
+ min = 0,
72
+ max = 100,
73
+ value,
74
+ step = 1,
75
+ onChange,
76
+ onDragStart,
77
+ onChangeEnd,
78
+ label,
79
+ showValue = true,
80
+ className = '',
81
+ disabled = false,
82
+ type = 'default',
83
+ baseHue = 0,
84
+ baseSaturation = 70,
85
+ }) => {
86
+ const [isDragging, setIsDragging] = useState(false);
87
+ const inputRef = useRef<HTMLInputElement>(null);
88
+ const containerRef = useRef<HTMLDivElement>(null);
89
+ const valueRef = useRef(value);
90
+
91
+ // Keep value ref in sync
92
+ React.useEffect(() => {
93
+ valueRef.current = value;
94
+ }, [value]);
95
+
96
+ const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
97
+ const newValue = parseFloat(event.target.value);
98
+ onChange(newValue);
99
+ }, [onChange]);
100
+
101
+ const getValueFromPosition = useCallback((clientX: number) => {
102
+ if (!containerRef.current) return value;
103
+ const rect = containerRef.current.getBoundingClientRect();
104
+ const percentage = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
105
+ const rawValue = min + percentage * (max - min);
106
+ const steppedValue = Math.round(rawValue / step) * step;
107
+ return Math.max(min, Math.min(max, steppedValue));
108
+ }, [min, max, step, value]);
109
+
110
+ const handleMouseDown = useCallback((event: React.MouseEvent) => {
111
+ if (disabled) return;
112
+ event.preventDefault();
113
+ setIsDragging(true);
114
+ onDragStart?.();
115
+ const newValue = getValueFromPosition(event.clientX);
116
+ onChange(newValue);
117
+ }, [disabled, onDragStart, getValueFromPosition, onChange]);
118
+
119
+ const handleMouseMove = useCallback((event: MouseEvent) => {
120
+ if (!isDragging) return;
121
+ event.preventDefault();
122
+ const newValue = getValueFromPosition(event.clientX);
123
+ onChange(newValue);
124
+ }, [isDragging, getValueFromPosition, onChange]);
125
+
126
+ const handleMouseUp = useCallback(() => {
127
+ if (!isDragging) return;
128
+ setIsDragging(false);
129
+ onChangeEnd?.(valueRef.current);
130
+ }, [isDragging, onChangeEnd]);
131
+
132
+ // Add global mouse listeners for dragging
133
+ React.useEffect(() => {
134
+ if (!isDragging) return;
135
+
136
+ document.addEventListener('mousemove', handleMouseMove);
137
+ document.addEventListener('mouseup', handleMouseUp);
138
+
139
+ return () => {
140
+ document.removeEventListener('mousemove', handleMouseMove);
141
+ document.removeEventListener('mouseup', handleMouseUp);
142
+ };
143
+ }, [isDragging, handleMouseMove, handleMouseUp]);
144
+
145
+ // Handle touch events
146
+ const handleTouchStart = useCallback((event: React.TouchEvent) => {
147
+ if (disabled) return;
148
+ event.preventDefault();
149
+ setIsDragging(true);
150
+ onDragStart?.();
151
+ const touch = event.touches[0];
152
+ const newValue = getValueFromPosition(touch.clientX);
153
+ onChange(newValue);
154
+ }, [disabled, onDragStart, getValueFromPosition, onChange]);
155
+
156
+ const handleTouchMove = useCallback((event: TouchEvent) => {
157
+ if (!isDragging) return;
158
+ event.preventDefault();
159
+ const touch = event.touches[0];
160
+ const newValue = getValueFromPosition(touch.clientX);
161
+ onChange(newValue);
162
+ }, [isDragging, getValueFromPosition, onChange]);
163
+
164
+ const handleTouchEnd = useCallback(() => {
165
+ if (!isDragging) return;
166
+ setIsDragging(false);
167
+ onChangeEnd?.(valueRef.current);
168
+ }, [isDragging, onChangeEnd]);
169
+
170
+ React.useEffect(() => {
171
+ if (!isDragging) return;
172
+
173
+ document.addEventListener('touchmove', handleTouchMove);
174
+ document.addEventListener('touchend', handleTouchEnd);
175
+
176
+ return () => {
177
+ document.removeEventListener('touchmove', handleTouchMove);
178
+ document.removeEventListener('touchend', handleTouchEnd);
179
+ };
180
+ }, [isDragging, handleTouchMove, handleTouchEnd]);
181
+
182
+ const sliderClasses = [
183
+ 'slider',
184
+ `slider--${type}`,
185
+ isDragging && 'slider--dragging',
186
+ disabled && 'slider--disabled',
187
+ className
188
+ ].filter(Boolean).join(' ');
189
+
190
+ // Generate background based on type
191
+ const getBackground = () => {
192
+ switch (type) {
193
+ case 'hue':
194
+ return 'linear-gradient(to right, hsl(0, 70%, 50%), hsl(60, 70%, 50%), hsl(120, 70%, 50%), hsl(180, 70%, 50%), hsl(240, 70%, 50%), hsl(300, 70%, 50%), hsl(360, 70%, 50%))';
195
+ case 'saturation':
196
+ return `linear-gradient(to right, hsl(${baseHue}, 0%, 50%), hsl(${baseHue}, 100%, 50%))`;
197
+ case 'lightness':
198
+ return `linear-gradient(to right, hsl(${baseHue}, ${baseSaturation}%, 0%), hsl(${baseHue}, ${baseSaturation}%, 50%), hsl(${baseHue}, ${baseSaturation}%, 100%))`;
199
+ default:
200
+ return 'var(--color-action-primary)';
201
+ }
202
+ };
203
+
204
+ const inputStyle = { background: getBackground() };
205
+
206
+ // Generate step marks
207
+ const stepMarks = step > 1 ? Array.from({ length: Math.floor((max - min) / step) + 1 }, (_, i) => min + i * step) : [];
208
+
209
+ // Calculate thumb position as percentage
210
+ const thumbPosition = ((value - min) / (max - min)) * 100;
211
+
212
+ return (
213
+ <div className={sliderClasses}>
214
+ {(label || showValue) && (
215
+ <div className="slider__header">
216
+ {label && <label className="slider__label">{label}</label>}
217
+ {showValue && (
218
+ <span className="slider__value">{value}</span>
219
+ )}
220
+ </div>
221
+ )}
222
+
223
+ <div className="slider__container" ref={containerRef} onMouseDown={handleMouseDown} onTouchStart={handleTouchStart}>
224
+ <input
225
+ ref={inputRef}
226
+ type="range"
227
+ min={min}
228
+ max={max}
229
+ step={step}
230
+ value={value}
231
+ onChange={handleChange}
232
+ className="slider__input"
233
+ style={inputStyle}
234
+ disabled={disabled}
235
+ readOnly
236
+ />
237
+
238
+ {/* Custom thumb with icon */}
239
+ <div
240
+ className="slider__thumb"
241
+ style={{
242
+ left: `${thumbPosition}%`,
243
+ transform: 'translateX(-50%)',
244
+ }}
245
+ >
246
+ <div className="slider__thumb-icon">
247
+ <Icon name="arrow-down" size={16} />
248
+ </div>
249
+ </div>
250
+
251
+ {stepMarks.length > 0 && (
252
+ <div className="slider__marks">
253
+ {stepMarks.map((mark) => (
254
+ <div
255
+ key={mark}
256
+ className="slider__mark"
257
+ style={{
258
+ left: `${((mark - min) / (max - min)) * 100}%`,
259
+ }}
260
+ />
261
+ ))}
262
+ </div>
263
+ )}
264
+ </div>
265
+ </div>
266
+ );
267
+ };
@@ -0,0 +1 @@
1
+ export * from "./Slider";
@@ -0,0 +1 @@
1
+ export { Switch, type SwitchProps, type SwitchSize } from './switch';
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { Icon } from '../icons';
3
+ import '../../style/components/switch/switch.css';
4
+
5
+ export type SwitchSize = 'sm' | 'md' | 'lg';
6
+
7
+ export interface SwitchProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'size'> {
8
+ /**
9
+ * Size of the switch
10
+ * @default 'md'
11
+ */
12
+ size?: SwitchSize;
13
+
14
+ /**
15
+ * Label text for the switch
16
+ */
17
+ label?: React.ReactNode;
18
+
19
+ /**
20
+ * Icon to display when switch is ON
21
+ */
22
+ onIcon?: string;
23
+
24
+ /**
25
+ * Icon to display when switch is OFF
26
+ */
27
+ offIcon?: string;
28
+
29
+ /**
30
+ * Error message to display below the switch
31
+ */
32
+ error?: string;
33
+
34
+ /**
35
+ * Helper text to display below the switch
36
+ */
37
+ helperText?: string;
38
+ }
39
+
40
+ export const Switch = React.forwardRef<HTMLInputElement, SwitchProps>(
41
+ (
42
+ {
43
+ size = 'md',
44
+ label,
45
+ onIcon,
46
+ offIcon,
47
+ error,
48
+ helperText,
49
+ disabled = false,
50
+ className,
51
+ checked,
52
+ ...props
53
+ },
54
+ ref
55
+ ) => {
56
+ const switchClasses = [
57
+ 'switch',
58
+ `switch--${size}`,
59
+ checked && 'switch--checked',
60
+ disabled && 'switch--disabled',
61
+ error && 'switch--error',
62
+ className,
63
+ ]
64
+ .filter(Boolean)
65
+ .join(' ');
66
+
67
+ return (
68
+ <div className="switch__wrapper">
69
+ <label className="switch__label">
70
+ <input
71
+ type="checkbox"
72
+ ref={ref}
73
+ checked={checked}
74
+ disabled={disabled}
75
+ className="switch__input"
76
+ {...props}
77
+ />
78
+ <span className={switchClasses}>
79
+ <span className="switch__track">
80
+ <span className="switch__thumb">
81
+ {onIcon && <Icon name={onIcon} size={16} className="switch__icon switch__icon--on" />}
82
+ {offIcon && <Icon name={offIcon} size={16} className="switch__icon switch__icon--off" />}
83
+ </span>
84
+ </span>
85
+ </span>
86
+ {label && <span className="switch__text">{label}</span>}
87
+ </label>
88
+ {(error || helperText) && (
89
+ <div className="switch__message">
90
+ {error && <span className="switch__error-text">{error}</span>}
91
+ {helperText && !error && <span className="switch__helper-text">{helperText}</span>}
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
97
+ );
98
+
99
+ Switch.displayName = 'Switch';