@ecohouse/ui 0.1.2 → 0.1.4

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 (74) hide show
  1. package/dist/index.cjs +1734 -155
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +287 -13
  4. package/dist/index.d.ts +287 -13
  5. package/dist/index.js +1720 -159
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/components/Avatar/Avatar.stories.tsx +133 -0
  9. package/src/components/Avatar/Avatar.tsx +389 -0
  10. package/src/components/Avatar/index.ts +2 -0
  11. package/src/components/Button/Button.tsx +106 -88
  12. package/src/components/Checkbox/Checkbox.stories.tsx +112 -0
  13. package/src/components/Checkbox/Checkbox.tsx +150 -0
  14. package/src/components/Checkbox/index.ts +2 -0
  15. package/src/components/Input/Input.stories.tsx +11 -40
  16. package/src/components/Input/Input.tsx +58 -54
  17. package/src/components/Input/index.ts +1 -1
  18. package/src/components/Select/Select.stories.tsx +196 -0
  19. package/src/components/Select/Select.tsx +851 -0
  20. package/src/components/Select/index.ts +2 -0
  21. package/src/components/Textarea/Textarea.stories.tsx +71 -0
  22. package/src/components/Textarea/Textarea.tsx +241 -0
  23. package/src/components/Textarea/index.ts +2 -0
  24. package/src/components/Toggle/Toggle.stories.tsx +100 -0
  25. package/src/components/Toggle/Toggle.tsx +176 -0
  26. package/src/components/Toggle/index.ts +2 -0
  27. package/src/components/index.ts +16 -1
  28. package/src/icons/Calendar/CalendarIcon.tsx +21 -0
  29. package/src/icons/Calendar/CalendarIcon.web.tsx +27 -0
  30. package/src/icons/Calendar/calendarPath.ts +3 -0
  31. package/src/icons/Calendar/index.ts +1 -0
  32. package/src/icons/Check/CheckIcon.tsx +26 -0
  33. package/src/icons/Check/CheckIcon.web.tsx +32 -0
  34. package/src/icons/Check/checkPath.ts +2 -0
  35. package/src/icons/Check/index.ts +1 -0
  36. package/src/icons/ChevronDown/ChevronDownIcon.tsx +30 -0
  37. package/src/icons/ChevronDown/ChevronDownIcon.web.tsx +36 -0
  38. package/src/icons/ChevronDown/chevronDownPath.ts +2 -0
  39. package/src/icons/ChevronDown/index.ts +1 -0
  40. package/src/icons/Heart/HeartIcon.tsx +23 -0
  41. package/src/icons/Heart/HeartIcon.web.tsx +29 -0
  42. package/src/icons/Heart/heartPath.ts +3 -0
  43. package/src/icons/Heart/index.ts +1 -0
  44. package/src/icons/HelpCircle/HelpCircleIcon.tsx +25 -0
  45. package/src/icons/HelpCircle/HelpCircleIcon.web.tsx +31 -0
  46. package/src/icons/HelpCircle/helpCirclePath.ts +3 -0
  47. package/src/icons/HelpCircle/index.ts +1 -0
  48. package/src/icons/LayoutGrid/LayoutGridIcon.tsx +25 -0
  49. package/src/icons/LayoutGrid/LayoutGridIcon.web.tsx +31 -0
  50. package/src/icons/LayoutGrid/index.ts +1 -0
  51. package/src/icons/LayoutGrid/layoutGridPath.ts +3 -0
  52. package/src/icons/LogOut/LogOutIcon.tsx +21 -0
  53. package/src/icons/LogOut/LogOutIcon.web.tsx +27 -0
  54. package/src/icons/LogOut/index.ts +1 -0
  55. package/src/icons/LogOut/logOutPath.ts +3 -0
  56. package/src/icons/Search/SearchIcon.tsx +37 -0
  57. package/src/icons/Search/SearchIcon.web.tsx +43 -0
  58. package/src/icons/Search/index.ts +1 -0
  59. package/src/icons/Search/searchPath.ts +5 -0
  60. package/src/icons/Settings/SettingsIcon.tsx +21 -0
  61. package/src/icons/Settings/SettingsIcon.web.tsx +27 -0
  62. package/src/icons/Settings/index.ts +1 -0
  63. package/src/icons/Settings/settingsPath.ts +3 -0
  64. package/src/icons/Video/VideoIcon.tsx +24 -0
  65. package/src/icons/Video/VideoIcon.web.tsx +30 -0
  66. package/src/icons/Video/index.ts +1 -0
  67. package/src/icons/Video/videoPath.ts +5 -0
  68. package/src/icons/index.ts +10 -0
  69. package/src/icons/registry.ts +24 -3
  70. package/src/index.ts +31 -3
  71. package/src/theme/colors.ts +1 -0
  72. package/src/theme/index.ts +8 -1
  73. package/src/theme/typography.ts +77 -2
  74. package/src/utils/mergeRefs.ts +20 -0
@@ -0,0 +1,2 @@
1
+ export { Select } from "./Select";
2
+ export type { SelectProps, SelectOption, SelectSingleProps, SelectMultipleProps } from "./Select";
@@ -0,0 +1,71 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { fn } from "storybook/test";
3
+ import { View, StyleSheet } from "react-native";
4
+ import { Textarea } from "./Textarea";
5
+
6
+ const meta = {
7
+ title: "Components/Textarea",
8
+ component: Textarea,
9
+ args: {
10
+ label: "Label",
11
+ showLabel: true,
12
+ hint: "Hint",
13
+ showHint: true,
14
+ placeholder: "placeholder",
15
+ disabled: false,
16
+ error: false,
17
+ onChangeText: fn(),
18
+ },
19
+ argTypes: {
20
+ disabled: { control: "boolean" },
21
+ error: { control: "boolean" },
22
+ },
23
+ parameters: {
24
+ backgrounds: { default: "black" },
25
+ },
26
+ } satisfies Meta<typeof Textarea>;
27
+
28
+ export default meta;
29
+ type Story = StoryObj<typeof meta>;
30
+
31
+ export const Default: Story = {};
32
+
33
+ export const Filled: Story = {
34
+ args: { value: "Filled value" },
35
+ };
36
+
37
+ export const Error: Story = {
38
+ args: { error: true, hint: "Hint" },
39
+ };
40
+
41
+ export const Disabled: Story = {
42
+ args: { disabled: true, value: "Disabled value" },
43
+ };
44
+
45
+ export const WithoutLabelAndHint: Story = {
46
+ args: { showLabel: false, showHint: false },
47
+ };
48
+
49
+ export const AllStates: Story = {
50
+ render: () => (
51
+ <View style={storyStyles.column}>
52
+ <Textarea label="Label" hint="Hint" placeholder="placeholder" />
53
+ <Textarea label="Label" hint="Hint" placeholder="placeholder" value="Filled value" />
54
+ <Textarea label="Label" hint="Hint" placeholder="placeholder" error />
55
+ <Textarea
56
+ label="Label"
57
+ hint="Hint"
58
+ placeholder="placeholder"
59
+ disabled
60
+ value="Disabled value"
61
+ />
62
+ </View>
63
+ ),
64
+ };
65
+
66
+ const storyStyles = StyleSheet.create({
67
+ column: {
68
+ gap: 24,
69
+ alignItems: "flex-start",
70
+ },
71
+ });
@@ -0,0 +1,241 @@
1
+ import { forwardRef, useMemo, useRef, useState, type ComponentRef } from "react";
2
+ import {
3
+ Platform,
4
+ StyleSheet,
5
+ Text,
6
+ TextInput,
7
+ View,
8
+ type StyleProp,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ type TextInputProps,
12
+ } from "react-native";
13
+ import { colors, textareaTypography } from "../../theme";
14
+ import { mergeRefs } from "../../utils/mergeRefs";
15
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
16
+
17
+ export interface TextareaProps extends Omit<TextInputProps, "style" | "editable" | "multiline"> {
18
+ label?: string;
19
+ showLabel?: boolean;
20
+ hint?: string;
21
+ showHint?: boolean;
22
+ /** External — validation / form error. */
23
+ error?: boolean;
24
+ /** External — non-interactive. */
25
+ disabled?: boolean;
26
+ style?: StyleProp<ViewStyle>;
27
+ fieldStyle?: StyleProp<ViewStyle>;
28
+ inputStyle?: StyleProp<TextStyle>;
29
+ className?: string;
30
+ inputClassName?: string;
31
+ }
32
+
33
+ type VisualState = "default" | "filled" | "active" | "error" | "disabled";
34
+
35
+ type FieldChrome = {
36
+ borderColor: string;
37
+ backgroundColor: string;
38
+ textColor: string;
39
+ placeholderColor: string;
40
+ labelColor: string;
41
+ hintColor: string;
42
+ };
43
+
44
+ const DEFAULT_WIDTH = 308;
45
+ const FIELD_HEIGHT = 100;
46
+ const FIELD_RADIUS = 16;
47
+ const FIELD_PADDING_VERTICAL = 12;
48
+ const FIELD_PADDING_HORIZONTAL = 16;
49
+
50
+ /** default / filled / active come from value + focus; error / disabled from props. */
51
+ function resolveVisualState({
52
+ disabled,
53
+ error,
54
+ focused,
55
+ hasValue,
56
+ }: {
57
+ disabled: boolean;
58
+ error: boolean;
59
+ focused: boolean;
60
+ hasValue: boolean;
61
+ }): VisualState {
62
+ if (disabled) return "disabled";
63
+ if (error) return "error";
64
+ if (focused) return "active";
65
+ if (hasValue) return "filled";
66
+ return "default";
67
+ }
68
+
69
+ function chromeFor(state: VisualState): FieldChrome {
70
+ const base: FieldChrome = {
71
+ borderColor: colors.grey700,
72
+ backgroundColor: colors.grey700,
73
+ textColor: colors.white,
74
+ placeholderColor: colors.grey100,
75
+ labelColor: colors.white,
76
+ hintColor: colors.grey100,
77
+ };
78
+
79
+ switch (state) {
80
+ case "active":
81
+ return {
82
+ ...base,
83
+ borderColor: colors.primaryMuted,
84
+ };
85
+ case "error":
86
+ return {
87
+ ...base,
88
+ borderColor: colors.redMuted,
89
+ textColor: colors.red,
90
+ placeholderColor: colors.red,
91
+ labelColor: colors.red,
92
+ hintColor: colors.red,
93
+ };
94
+ case "filled":
95
+ return base;
96
+ case "disabled":
97
+ return {
98
+ ...base,
99
+ textColor: colors.grey0,
100
+ };
101
+ default:
102
+ return base;
103
+ }
104
+ }
105
+
106
+ export const Textarea = forwardRef<ComponentRef<typeof TextInput>, TextareaProps>(function Textarea(
107
+ {
108
+ label = "Label",
109
+ showLabel = true,
110
+ hint = "Hint",
111
+ showHint = true,
112
+ placeholder = "placeholder",
113
+ disabled = false,
114
+ error = false,
115
+ value,
116
+ defaultValue,
117
+ onChangeText,
118
+ onFocus,
119
+ onBlur,
120
+ style,
121
+ fieldStyle,
122
+ inputStyle,
123
+ className,
124
+ inputClassName,
125
+ accessibilityLabel,
126
+ ...textInputProps
127
+ },
128
+ forwardedRef,
129
+ ) {
130
+ const rootRef = useRef<ComponentRef<typeof View>>(null);
131
+ const inputRef = useRef<ComponentRef<typeof TextInput>>(null);
132
+ const setInputRef = useMemo(() => mergeRefs(inputRef, forwardedRef), [forwardedRef]);
133
+ const [focused, setFocused] = useState(false);
134
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
135
+
136
+ const isControlled = typeof value === "string";
137
+ const currentValue = isControlled ? value : uncontrolledValue;
138
+ const hasValue = currentValue.length > 0;
139
+
140
+ const visualState = resolveVisualState({ disabled, error, focused, hasValue });
141
+ const chrome = chromeFor(visualState);
142
+ const resolvedAccessibilityLabel = accessibilityLabel ?? (showLabel ? undefined : label);
143
+
144
+ useApplyWebClassName(rootRef, className);
145
+ useApplyWebClassName(inputRef, inputClassName);
146
+
147
+ return (
148
+ <View ref={rootRef} style={[styles.root, disabled && styles.disabled, style]}>
149
+ {showLabel && label ? (
150
+ <Text style={[styles.label, { color: chrome.labelColor }]}>{label}</Text>
151
+ ) : null}
152
+
153
+ <View
154
+ style={[
155
+ styles.field,
156
+ {
157
+ borderColor: chrome.borderColor,
158
+ backgroundColor: chrome.backgroundColor,
159
+ },
160
+ fieldStyle,
161
+ ]}
162
+ >
163
+ <TextInput
164
+ ref={setInputRef}
165
+ {...textInputProps}
166
+ multiline
167
+ textAlignVertical="top"
168
+ value={isControlled ? value : undefined}
169
+ defaultValue={isControlled ? undefined : defaultValue}
170
+ editable={!disabled}
171
+ placeholder={placeholder}
172
+ placeholderTextColor={chrome.placeholderColor}
173
+ onChangeText={(text) => {
174
+ if (!isControlled) setUncontrolledValue(text);
175
+ onChangeText?.(text);
176
+ }}
177
+ onFocus={(event) => {
178
+ setFocused(true);
179
+ onFocus?.(event);
180
+ }}
181
+ onBlur={(event) => {
182
+ setFocused(false);
183
+ onBlur?.(event);
184
+ }}
185
+ style={[
186
+ styles.input,
187
+ textareaTypography.field,
188
+ { color: chrome.textColor },
189
+ Platform.OS === "web" ? styles.inputWeb : null,
190
+ Platform.OS === "android" ? styles.inputAndroid : null,
191
+ inputStyle,
192
+ ]}
193
+ accessibilityLabel={resolvedAccessibilityLabel}
194
+ accessibilityState={{ disabled }}
195
+ />
196
+ </View>
197
+
198
+ {showHint && hint ? (
199
+ <Text style={[styles.hint, { color: chrome.hintColor }]}>{hint}</Text>
200
+ ) : null}
201
+ </View>
202
+ );
203
+ });
204
+
205
+ const styles = StyleSheet.create({
206
+ root: {
207
+ width: DEFAULT_WIDTH,
208
+ gap: 8,
209
+ alignSelf: "flex-start",
210
+ },
211
+ disabled: {
212
+ opacity: 0.6,
213
+ },
214
+ label: {
215
+ ...textareaTypography.label,
216
+ },
217
+ field: {
218
+ height: FIELD_HEIGHT,
219
+ borderRadius: FIELD_RADIUS,
220
+ borderWidth: 1,
221
+ paddingTop: FIELD_PADDING_VERTICAL,
222
+ paddingBottom: FIELD_PADDING_VERTICAL,
223
+ paddingLeft: FIELD_PADDING_HORIZONTAL,
224
+ paddingRight: FIELD_PADDING_HORIZONTAL,
225
+ },
226
+ input: {
227
+ flex: 1,
228
+ paddingVertical: 0,
229
+ margin: 0,
230
+ },
231
+ inputWeb: {
232
+ outlineStyle: "none",
233
+ resize: "none",
234
+ } as TextStyle,
235
+ inputAndroid: {
236
+ includeFontPadding: false,
237
+ },
238
+ hint: {
239
+ ...textareaTypography.hint,
240
+ },
241
+ });
@@ -0,0 +1,2 @@
1
+ export { Textarea } from "./Textarea";
2
+ export type { TextareaProps } from "./Textarea";
@@ -0,0 +1,100 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { fn } from "storybook/test";
4
+ import { View, StyleSheet, Text } from "react-native";
5
+ import { Toggle } from "./Toggle";
6
+ import { colors } from "../../theme";
7
+
8
+ const meta = {
9
+ title: "Components/Toggle",
10
+ component: Toggle,
11
+ args: {
12
+ defaultValue: false,
13
+ disabled: false,
14
+ onValueChange: fn(),
15
+ },
16
+ argTypes: {
17
+ value: { control: "boolean" },
18
+ defaultValue: { control: "boolean" },
19
+ disabled: { control: "boolean" },
20
+ onValueChange: { action: "valueChange" },
21
+ },
22
+ parameters: {
23
+ backgrounds: { default: "black" },
24
+ },
25
+ } satisfies Meta<typeof Toggle>;
26
+
27
+ export default meta;
28
+ type Story = StoryObj<typeof meta>;
29
+
30
+ export const Default: Story = {};
31
+
32
+ export const Active: Story = {
33
+ args: { defaultValue: true },
34
+ };
35
+
36
+ export const Disabled: Story = {
37
+ args: { disabled: true },
38
+ };
39
+
40
+ export const DisabledActive: Story = {
41
+ args: { defaultValue: true, disabled: true },
42
+ };
43
+
44
+ export const WithLabel: Story = {
45
+ args: { label: "Enable notifications" },
46
+ };
47
+
48
+ function ControlledToggle() {
49
+ const [value, setValue] = useState(false);
50
+
51
+ return (
52
+ <View style={storyStyles.row}>
53
+ <Toggle value={value} onValueChange={setValue} />
54
+ <Text style={storyStyles.label}>{value ? "On" : "Off"}</Text>
55
+ </View>
56
+ );
57
+ }
58
+
59
+ export const Controlled: Story = {
60
+ render: () => <ControlledToggle />,
61
+ };
62
+
63
+ export const AllStates: Story = {
64
+ render: () => (
65
+ <View style={storyStyles.column}>
66
+ <View style={storyStyles.row}>
67
+ <Toggle defaultValue={false} onValueChange={fn()} />
68
+ <Text style={storyStyles.label}>Inactive</Text>
69
+ </View>
70
+ <View style={storyStyles.row}>
71
+ <Toggle defaultValue={true} onValueChange={fn()} />
72
+ <Text style={storyStyles.label}>Active</Text>
73
+ </View>
74
+ <View style={storyStyles.row}>
75
+ <Toggle defaultValue={false} disabled onValueChange={fn()} />
76
+ <Text style={storyStyles.label}>Disabled</Text>
77
+ </View>
78
+ <View style={storyStyles.row}>
79
+ <Toggle defaultValue={true} disabled onValueChange={fn()} />
80
+ <Text style={storyStyles.label}>Disabled active</Text>
81
+ </View>
82
+ </View>
83
+ ),
84
+ };
85
+
86
+ const storyStyles = StyleSheet.create({
87
+ column: {
88
+ gap: 16,
89
+ alignItems: "flex-start",
90
+ },
91
+ row: {
92
+ flexDirection: "row",
93
+ alignItems: "center",
94
+ gap: 12,
95
+ },
96
+ label: {
97
+ color: colors.white,
98
+ fontSize: 14,
99
+ },
100
+ });
@@ -0,0 +1,176 @@
1
+ import {
2
+ forwardRef,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ type ComponentRef,
8
+ type ReactNode,
9
+ } from "react";
10
+ import {
11
+ Animated,
12
+ Easing,
13
+ Pressable,
14
+ StyleSheet,
15
+ Text,
16
+ type PressableProps,
17
+ type StyleProp,
18
+ type TextStyle,
19
+ type ViewStyle,
20
+ } from "react-native";
21
+ import { colors, inputTypography } from "../../theme";
22
+ import { mergeRefs } from "../../utils/mergeRefs";
23
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
24
+
25
+ export interface ToggleProps extends Omit<
26
+ PressableProps,
27
+ "onPress" | "disabled" | "style" | "children" | "hitSlop"
28
+ > {
29
+ value?: boolean;
30
+ defaultValue?: boolean;
31
+ onValueChange?: (value: boolean) => void;
32
+ disabled?: boolean;
33
+ /** Visible label rendered next to the track. */
34
+ label?: string;
35
+ children?: ReactNode;
36
+ /** Accessible name for screen readers; falls back to `label`/`children` when omitted. */
37
+ accessibilityLabel?: string;
38
+ style?: StyleProp<ViewStyle>;
39
+ labelStyle?: StyleProp<TextStyle>;
40
+ className?: string;
41
+ hitSlop?: PressableProps["hitSlop"];
42
+ }
43
+
44
+ const TRACK_WIDTH = 44;
45
+ const TRACK_HEIGHT = 24;
46
+ const TRACK_RADIUS = 43;
47
+ const TRACK_PADDING = 4;
48
+ const THUMB_SIZE = TRACK_HEIGHT - TRACK_PADDING * 2;
49
+ const THUMB_TRAVEL = TRACK_WIDTH - TRACK_PADDING * 2 - THUMB_SIZE;
50
+ const ANIMATION_DURATION = 300;
51
+
52
+ export const Toggle = forwardRef<ComponentRef<typeof Pressable>, ToggleProps>(function Toggle(
53
+ {
54
+ value,
55
+ defaultValue = false,
56
+ onValueChange,
57
+ disabled = false,
58
+ label,
59
+ children,
60
+ accessibilityLabel,
61
+ style,
62
+ labelStyle,
63
+ className,
64
+ hitSlop = 10,
65
+ ...rest
66
+ },
67
+ forwardedRef,
68
+ ) {
69
+ const containerRef = useRef<ComponentRef<typeof Pressable>>(null);
70
+ const isControlled = typeof value === "boolean";
71
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
72
+ const isActive = isControlled ? value : uncontrolledValue;
73
+ const labelContent = children ?? label;
74
+ const resolvedAccessibilityLabel =
75
+ accessibilityLabel ?? (typeof labelContent === "string" ? labelContent : undefined);
76
+ const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
77
+
78
+ const progress = useRef(new Animated.Value(isActive ? 1 : 0)).current;
79
+
80
+ useApplyWebClassName(containerRef, className);
81
+
82
+ useEffect(() => {
83
+ Animated.timing(progress, {
84
+ toValue: isActive ? 1 : 0,
85
+ duration: ANIMATION_DURATION,
86
+ easing: Easing.out(Easing.ease),
87
+ useNativeDriver: false,
88
+ }).start();
89
+ }, [isActive, progress]);
90
+
91
+ const handlePress = () => {
92
+ if (disabled) return;
93
+ const next = !isActive;
94
+ if (!isControlled) setUncontrolledValue(next);
95
+ onValueChange?.(next);
96
+ };
97
+
98
+ const trackBackgroundColor = progress.interpolate({
99
+ inputRange: [0, 1],
100
+ outputRange: [colors.grey700, colors.primary],
101
+ });
102
+
103
+ const thumbTranslateX = progress.interpolate({
104
+ inputRange: [0, 1],
105
+ outputRange: [0, THUMB_TRAVEL],
106
+ });
107
+
108
+ return (
109
+ <Pressable
110
+ {...rest}
111
+ ref={setContainerRef}
112
+ onPress={handlePress}
113
+ disabled={disabled}
114
+ hitSlop={hitSlop}
115
+ accessibilityRole="switch"
116
+ accessibilityLabel={resolvedAccessibilityLabel}
117
+ accessibilityState={{ checked: isActive, disabled }}
118
+ style={[styles.pressable, disabled && styles.disabled, style]}
119
+ >
120
+ <Animated.View
121
+ style={[
122
+ styles.track,
123
+ {
124
+ backgroundColor: trackBackgroundColor,
125
+ },
126
+ ]}
127
+ >
128
+ <Animated.View
129
+ style={[
130
+ styles.thumb,
131
+ {
132
+ transform: [{ translateX: thumbTranslateX }],
133
+ },
134
+ ]}
135
+ />
136
+ </Animated.View>
137
+
138
+ {labelContent != null && labelContent !== false ? (
139
+ typeof labelContent === "string" || typeof labelContent === "number" ? (
140
+ <Text style={[styles.label, labelStyle]}>{labelContent}</Text>
141
+ ) : (
142
+ labelContent
143
+ )
144
+ ) : null}
145
+ </Pressable>
146
+ );
147
+ });
148
+
149
+ const styles = StyleSheet.create({
150
+ pressable: {
151
+ flexDirection: "row",
152
+ alignItems: "center",
153
+ alignSelf: "flex-start",
154
+ gap: 10,
155
+ },
156
+ disabled: {
157
+ opacity: 0.6,
158
+ },
159
+ track: {
160
+ width: TRACK_WIDTH,
161
+ height: TRACK_HEIGHT,
162
+ borderRadius: TRACK_RADIUS,
163
+ padding: TRACK_PADDING,
164
+ justifyContent: "center",
165
+ },
166
+ thumb: {
167
+ width: THUMB_SIZE,
168
+ height: THUMB_SIZE,
169
+ borderRadius: THUMB_SIZE / 2,
170
+ backgroundColor: colors.white,
171
+ },
172
+ label: {
173
+ ...inputTypography.field,
174
+ color: colors.white,
175
+ },
176
+ });
@@ -0,0 +1,2 @@
1
+ export { Toggle } from "./Toggle";
2
+ export type { ToggleProps } from "./Toggle";
@@ -1,5 +1,20 @@
1
+ export { Avatar } from "./Avatar";
2
+ export type { AvatarProps, AvatarMenuItem } from "./Avatar";
3
+
1
4
  export { Button } from "./Button";
2
5
  export type { ButtonProps, ButtonSize, ButtonVariant } from "./Button";
3
6
 
7
+ export { Checkbox } from "./Checkbox";
8
+ export type { CheckboxProps, CheckboxVariant } from "./Checkbox";
9
+
4
10
  export { Input } from "./Input";
5
- export type { InputProps, InputSize, InputState } from "./Input";
11
+ export type { InputProps, InputSize } from "./Input";
12
+
13
+ export { Select } from "./Select";
14
+ export type { SelectOption, SelectProps, SelectSingleProps, SelectMultipleProps } from "./Select";
15
+
16
+ export { Textarea } from "./Textarea";
17
+ export type { TextareaProps } from "./Textarea";
18
+
19
+ export { Toggle } from "./Toggle";
20
+ export type { ToggleProps } from "./Toggle";
@@ -0,0 +1,21 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+ import { CALENDAR_PATH } from "./calendarPath";
3
+ import { colors } from "../../theme";
4
+ import type { IconProps } from "../types";
5
+
6
+ export { CALENDAR_PATH } from "./calendarPath";
7
+
8
+ /** Native — react-native-svg (peer dependency). */
9
+ export function CalendarIcon({ size = 20, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
10
+ return (
11
+ <Svg width={size} height={size} viewBox="0 0 16 16" fill="none">
12
+ <Path
13
+ d={CALENDAR_PATH}
14
+ stroke={color}
15
+ strokeWidth={strokeWidth}
16
+ strokeLinecap="round"
17
+ strokeLinejoin="round"
18
+ />
19
+ </Svg>
20
+ );
21
+ }
@@ -0,0 +1,27 @@
1
+ import { CALENDAR_PATH } from "./calendarPath";
2
+ import { colors } from "../../theme";
3
+ import type { IconProps } from "../types";
4
+
5
+ /** Web / Storybook — plain SVG (no react-native-svg). */
6
+ export function CalendarIcon({ size = 20, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
7
+ return (
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 16 16"
12
+ fill="none"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ aria-hidden
15
+ >
16
+ <path
17
+ d={CALENDAR_PATH}
18
+ stroke={color}
19
+ strokeWidth={strokeWidth}
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ export { CALENDAR_PATH } from "./calendarPath";
@@ -0,0 +1,3 @@
1
+ /** Calendar icon path — 16×16 viewBox. */
2
+ export const CALENDAR_PATH =
3
+ "M14 6.66668H2M14 8.33334V5.86668C14 4.74657 14 4.18652 13.782 3.7587C13.5903 3.38237 13.2843 3.07641 12.908 2.88466C12.4802 2.66668 11.9201 2.66668 10.8 2.66668H5.2C4.0799 2.66668 3.51984 2.66668 3.09202 2.88466C2.71569 3.07641 2.40973 3.38237 2.21799 3.7587C2 4.18652 2 4.74657 2 5.86668V11.4667C2 12.5868 2 13.1468 2.21799 13.5747C2.40973 13.951 2.71569 14.2569 3.09202 14.4487C3.51984 14.6667 4.0799 14.6667 5.2 14.6667H8M10.6667 1.33334V4.00001M5.33333 1.33334V4.00001M9.66667 12.6667L11 14L14 11";
@@ -0,0 +1 @@
1
+ export { CalendarIcon, CALENDAR_PATH } from "./CalendarIcon";