@ecohouse/ui 0.1.42 → 0.1.44

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 (46) hide show
  1. package/dist/index.cjs +312 -37
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +40 -4
  4. package/dist/index.d.ts +40 -4
  5. package/dist/index.js +307 -39
  6. package/dist/index.js.map +1 -1
  7. package/package.json +1 -1
  8. package/src/components/Button/Button.stories.tsx +5 -0
  9. package/src/components/Button/Button.tsx +34 -5
  10. package/src/components/Input/Input.tsx +6 -0
  11. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +1 -1
  12. package/src/components/RadioButton/RadioButton.stories.tsx +64 -0
  13. package/src/components/RadioButton/RadioButton.tsx +135 -0
  14. package/src/components/RadioButton/index.ts +2 -0
  15. package/src/components/SegmentedToggle/SegmentedToggle.tsx +21 -1
  16. package/src/components/Select/Select.tsx +12 -3
  17. package/src/components/index.ts +3 -0
  18. package/src/icons/Amenity/amenityPaths.ts +3 -10
  19. package/src/icons/Dram/DramIcon.tsx +14 -0
  20. package/src/icons/Dram/DramIcon.web.tsx +20 -0
  21. package/src/icons/Dram/dramPath.ts +2 -0
  22. package/src/icons/Dram/index.ts +1 -0
  23. package/src/icons/HouseRules/houseRulesPaths.ts +3 -3
  24. package/src/icons/MoonStars/MoonStarsIcon.tsx +27 -0
  25. package/src/icons/MoonStars/MoonStarsIcon.web.tsx +33 -0
  26. package/src/icons/MoonStars/index.ts +6 -0
  27. package/src/icons/MoonStars/moonStarsPaths.ts +7 -0
  28. package/src/icons/NoPets/NoPetsIcon.tsx +23 -0
  29. package/src/icons/NoPets/NoPetsIcon.web.tsx +29 -0
  30. package/src/icons/NoPets/index.ts +1 -0
  31. package/src/icons/NoPets/noPetsPaths.ts +8 -0
  32. package/src/icons/StarOutline/StarOutlineIcon.tsx +24 -0
  33. package/src/icons/StarOutline/StarOutlineIcon.web.tsx +30 -0
  34. package/src/icons/StarOutline/index.ts +1 -0
  35. package/src/icons/StarOutline/starOutlinePath.ts +2 -0
  36. package/src/icons/Sun/SunIcon.tsx +20 -0
  37. package/src/icons/Sun/SunIcon.web.tsx +26 -0
  38. package/src/icons/Sun/index.ts +1 -0
  39. package/src/icons/Sun/sunPath.ts +2 -0
  40. package/src/icons/Washer/WasherIcon.tsx +25 -0
  41. package/src/icons/Washer/WasherIcon.web.tsx +31 -0
  42. package/src/icons/Washer/index.ts +1 -0
  43. package/src/icons/Washer/washerPaths.ts +28 -0
  44. package/src/icons/index.ts +6 -0
  45. package/src/icons/registry.ts +18 -0
  46. package/src/index.ts +9 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecohouse/ui",
3
- "version": "0.1.42",
3
+ "version": "0.1.44",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "description": "Cross-platform presentation-only UI components for EcoHouse (React Native + React Native Web)",
@@ -10,6 +10,7 @@ const meta = {
10
10
  title: "Button",
11
11
  onPress: fn(),
12
12
  disabled: false,
13
+ loading: false,
13
14
  variant: "primary",
14
15
  size: "lg",
15
16
  showIcon: true,
@@ -105,6 +106,10 @@ export const Disabled: Story = {
105
106
  args: { disabled: true },
106
107
  };
107
108
 
109
+ export const Loading: Story = {
110
+ args: { loading: true },
111
+ };
112
+
108
113
  const storyStyles = StyleSheet.create({
109
114
  column: {
110
115
  gap: 12,
@@ -1,5 +1,6 @@
1
1
  import { forwardRef, useMemo, useRef, type ComponentRef, type ReactNode } from "react";
2
2
  import {
3
+ ActivityIndicator,
3
4
  Platform,
4
5
  StyleSheet,
5
6
  Text,
@@ -27,6 +28,8 @@ export interface ButtonProps extends Omit<
27
28
  children?: ReactNode;
28
29
  onPress: (event: GestureResponderEvent) => void;
29
30
  disabled?: boolean;
31
+ /** Replaces the visible content with a centered spinner and prevents interaction. */
32
+ loading?: boolean;
30
33
  variant?: ButtonVariant;
31
34
  size?: ButtonSize;
32
35
  showIcon?: boolean;
@@ -123,6 +126,7 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
123
126
  children,
124
127
  onPress,
125
128
  disabled = false,
129
+ loading = false,
126
130
  variant = "primary",
127
131
  size = "lg",
128
132
  showIcon = false,
@@ -146,6 +150,7 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
146
150
  const hasCustomChildren = typeof children !== "undefined";
147
151
  const customIcon = icon != null && typeof icon !== "boolean" ? icon : undefined;
148
152
  const hasIcon = showIcon || icon === true || customIcon != null;
153
+ const interactionDisabled = disabled || loading;
149
154
  const resolvedContainerClassName = className?.trim() || undefined;
150
155
  const resolvedAccessibilityLabel =
151
156
  accessibilityLabel ?? (typeof label === "string" ? label : undefined);
@@ -173,7 +178,7 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
173
178
  gap: hasIcon ? metrics.gap : 0,
174
179
  },
175
180
  hasIcon ? styles.contentWithIcon : styles.contentWithoutIcon,
176
- disabled && styles.disabled,
181
+ disabled && !loading && styles.disabled,
177
182
  style,
178
183
  ];
179
184
 
@@ -192,16 +197,20 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
192
197
  ref={setContainerRef}
193
198
  style={containerStyle}
194
199
  onPress={onPress}
195
- disabled={disabled}
200
+ disabled={interactionDisabled}
196
201
  activeOpacity={0.7}
197
202
  accessibilityRole="button"
198
203
  accessibilityLabel={resolvedAccessibilityLabel}
199
- accessibilityState={{ disabled }}
204
+ accessibilityState={{ busy: loading, disabled: interactionDisabled }}
200
205
  >
201
206
  {hasCustomChildren ? (
202
- children
207
+ loading ? (
208
+ <View style={styles.loadingContent}>{children}</View>
209
+ ) : (
210
+ children
211
+ )
203
212
  ) : (
204
- <Text ref={textRef} style={labelStyle}>
213
+ <Text ref={textRef} style={[labelStyle, loading && styles.loadingContent]}>
205
214
  {label}
206
215
  </Text>
207
216
  )}
@@ -216,11 +225,18 @@ export const Button = forwardRef<ComponentRef<typeof TouchableOpacity>, ButtonPr
216
225
  borderRadius: metrics.iconContainerSize / 2,
217
226
  backgroundColor: iconBadgeBackgroundColor ?? preset.iconBadgeBackgroundColor,
218
227
  },
228
+ loading && styles.loadingContent,
219
229
  ]}
220
230
  >
221
231
  {iconNode}
222
232
  </View>
223
233
  ) : null}
234
+
235
+ {loading ? (
236
+ <View pointerEvents="none" style={styles.loadingIndicator}>
237
+ <ActivityIndicator color={preset.textColor} size="small" />
238
+ </View>
239
+ ) : null}
224
240
  </TouchableOpacity>
225
241
  );
226
242
  },
@@ -232,6 +248,7 @@ const styles = StyleSheet.create({
232
248
  alignItems: "center",
233
249
  alignSelf: "flex-start",
234
250
  borderWidth: 0,
251
+ position: "relative",
235
252
  },
236
253
  disabled: {
237
254
  opacity: 0.6,
@@ -254,4 +271,16 @@ const styles = StyleSheet.create({
254
271
  justifyContent: "center",
255
272
  flexShrink: 0,
256
273
  },
274
+ loadingContent: {
275
+ opacity: 0,
276
+ },
277
+ loadingIndicator: {
278
+ alignItems: "center",
279
+ bottom: 0,
280
+ justifyContent: "center",
281
+ left: 0,
282
+ position: "absolute",
283
+ right: 0,
284
+ top: 0,
285
+ },
257
286
  });
@@ -168,6 +168,8 @@ export const Input = forwardRef<ComponentRef<typeof TextInput>, InputProps>(func
168
168
  showRightIcon = false,
169
169
  onRightIconPress,
170
170
  rightIconAccessibilityLabel,
171
+ autoComplete = "off",
172
+ importantForAutofill,
171
173
  style,
172
174
  fieldStyle,
173
175
  inputStyle,
@@ -194,6 +196,8 @@ export const Input = forwardRef<ComponentRef<typeof TextInput>, InputProps>(func
194
196
  const visualState = resolveVisualState({ disabled, error, focused, hasValue });
195
197
  const chrome = chromeFor(visualState);
196
198
  const resolvedAccessibilityLabel = accessibilityLabel ?? (showLabel ? undefined : label);
199
+ const resolvedImportantForAutofill =
200
+ importantForAutofill ?? (autoComplete === "off" ? "no" : "auto");
197
201
 
198
202
  const hasLeftIcon = showLeftIcon || leftIcon != null;
199
203
  const hasRightIcon = showRightIcon || rightIcon != null;
@@ -256,6 +260,8 @@ export const Input = forwardRef<ComponentRef<typeof TextInput>, InputProps>(func
256
260
  ref={setInputRef}
257
261
  {...textInputProps}
258
262
  nativeID={inputDomId}
263
+ autoComplete={autoComplete}
264
+ importantForAutofill={resolvedImportantForAutofill}
259
265
  value={isControlled ? value : undefined}
260
266
  defaultValue={isControlled ? undefined : defaultValue}
261
267
  editable={!disabled}
@@ -58,7 +58,7 @@ export const defaultLanguageOptions = [
58
58
  { value: "ru", label: "Рус" },
59
59
  ] as const satisfies readonly LanguageOption[];
60
60
 
61
- const CONTROL_WIDTH = 90;
61
+ const CONTROL_WIDTH = 96;
62
62
  const CONTROL_HEIGHT = 35;
63
63
  const ICON_SIZE = 16;
64
64
  const ICON_BUTTON_SIZE = 32;
@@ -0,0 +1,64 @@
1
+ import { useState } from "react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import { fn } from "storybook/test";
4
+ import { StyleSheet, Text, View } from "react-native";
5
+ import { colors } from "../../theme";
6
+ import { RadioButton } from "./RadioButton";
7
+
8
+ const meta = {
9
+ title: "Components/RadioButton",
10
+ component: RadioButton,
11
+ args: {
12
+ defaultValue: false,
13
+ disabled: false,
14
+ variant: "default",
15
+ onValueChange: fn(),
16
+ },
17
+ parameters: {
18
+ backgrounds: { default: "black" },
19
+ },
20
+ } satisfies Meta<typeof RadioButton>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ export const Default: Story = {};
26
+
27
+ export const Active: Story = {
28
+ args: { defaultValue: true },
29
+ };
30
+
31
+ export const Disabled: Story = {
32
+ args: { disabled: true },
33
+ };
34
+
35
+ export const WithLabel: Story = {
36
+ args: { defaultValue: true, label: "Option" },
37
+ };
38
+
39
+ function ControlledGroup() {
40
+ const [value, setValue] = useState("first");
41
+
42
+ return (
43
+ <View style={styles.group} accessibilityRole="radiogroup">
44
+ {["first", "second", "third"].map((option) => (
45
+ <RadioButton key={option} value={value === option} onValueChange={() => setValue(option)}>
46
+ <Text style={styles.label}>{option}</Text>
47
+ </RadioButton>
48
+ ))}
49
+ </View>
50
+ );
51
+ }
52
+
53
+ export const Group: Story = {
54
+ render: () => <ControlledGroup />,
55
+ };
56
+
57
+ const styles = StyleSheet.create({
58
+ group: {
59
+ gap: 12,
60
+ },
61
+ label: {
62
+ color: colors.white,
63
+ },
64
+ });
@@ -0,0 +1,135 @@
1
+ import { forwardRef, useMemo, useRef, useState, type ComponentRef, type ReactNode } from "react";
2
+ import {
3
+ Pressable,
4
+ StyleSheet,
5
+ Text,
6
+ View,
7
+ type PressableProps,
8
+ type StyleProp,
9
+ type TextStyle,
10
+ type ViewStyle,
11
+ } from "react-native";
12
+ import { colors, fonts } from "../../theme";
13
+ import { mergeRefs } from "../../utils/mergeRefs";
14
+ import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
15
+
16
+ export type RadioButtonVariant = "default";
17
+
18
+ export interface RadioButtonProps extends Omit<
19
+ PressableProps,
20
+ "onPress" | "disabled" | "style" | "children" | "hitSlop"
21
+ > {
22
+ value?: boolean;
23
+ defaultValue?: boolean;
24
+ onValueChange?: (value: true) => void;
25
+ disabled?: boolean;
26
+ variant?: RadioButtonVariant;
27
+ label?: string;
28
+ children?: ReactNode;
29
+ style?: StyleProp<ViewStyle>;
30
+ labelStyle?: StyleProp<TextStyle>;
31
+ className?: string;
32
+ hitSlop?: PressableProps["hitSlop"];
33
+ }
34
+
35
+ const CONTROL_SIZE = 16;
36
+ const DOT_SIZE = 8;
37
+
38
+ export const RadioButton = forwardRef<ComponentRef<typeof Pressable>, RadioButtonProps>(
39
+ function RadioButton(
40
+ {
41
+ value,
42
+ defaultValue = false,
43
+ onValueChange,
44
+ disabled = false,
45
+ variant: _variant = "default",
46
+ label,
47
+ children,
48
+ style,
49
+ labelStyle,
50
+ className,
51
+ hitSlop = 8,
52
+ accessibilityLabel,
53
+ ...rest
54
+ },
55
+ forwardedRef,
56
+ ) {
57
+ const containerRef = useRef<ComponentRef<typeof Pressable>>(null);
58
+ const isControlled = typeof value === "boolean";
59
+ const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue);
60
+ const isActive = isControlled ? value : uncontrolledValue;
61
+ const labelContent = children ?? label;
62
+ const resolvedAccessibilityLabel =
63
+ accessibilityLabel ?? (typeof labelContent === "string" ? labelContent : undefined);
64
+ const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
65
+
66
+ useApplyWebClassName(containerRef, className);
67
+
68
+ const handlePress = () => {
69
+ if (disabled || isActive) return;
70
+ if (!isControlled) setUncontrolledValue(true);
71
+ onValueChange?.(true);
72
+ };
73
+
74
+ return (
75
+ <Pressable
76
+ {...rest}
77
+ ref={setContainerRef}
78
+ onPress={handlePress}
79
+ disabled={disabled}
80
+ hitSlop={hitSlop}
81
+ accessibilityRole="radio"
82
+ accessibilityLabel={resolvedAccessibilityLabel}
83
+ accessibilityState={{ selected: isActive, disabled }}
84
+ style={[styles.root, disabled && styles.disabled, style]}
85
+ >
86
+ <View style={styles.control}>{isActive ? <View style={styles.dot} /> : null}</View>
87
+
88
+ {labelContent != null && labelContent !== false ? (
89
+ typeof labelContent === "string" || typeof labelContent === "number" ? (
90
+ <Text style={[styles.label, labelStyle]}>{labelContent}</Text>
91
+ ) : (
92
+ labelContent
93
+ )
94
+ ) : null}
95
+ </Pressable>
96
+ );
97
+ },
98
+ );
99
+
100
+ const styles = StyleSheet.create({
101
+ root: {
102
+ flexDirection: "row",
103
+ alignItems: "center",
104
+ alignSelf: "flex-start",
105
+ gap: 6,
106
+ },
107
+ disabled: {
108
+ opacity: 0.5,
109
+ },
110
+ control: {
111
+ width: CONTROL_SIZE,
112
+ height: CONTROL_SIZE,
113
+ borderRadius: 20,
114
+ borderWidth: 1,
115
+ borderColor: colors.grey700,
116
+ backgroundColor: colors.dark,
117
+ alignItems: "center",
118
+ justifyContent: "center",
119
+ flexShrink: 0,
120
+ },
121
+ dot: {
122
+ width: DOT_SIZE,
123
+ height: DOT_SIZE,
124
+ borderRadius: 43,
125
+ backgroundColor: colors.primary,
126
+ },
127
+ label: {
128
+ flexShrink: 1,
129
+ textAlign: "center",
130
+ fontFamily: fonts.sans,
131
+ fontSize: 12,
132
+ fontWeight: "400",
133
+ color: colors.white,
134
+ },
135
+ });
@@ -0,0 +1,2 @@
1
+ export { RadioButton } from "./RadioButton";
2
+ export type { RadioButtonProps, RadioButtonVariant } from "./RadioButton";
@@ -90,6 +90,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
90
90
  const resolvedClassName = className?.trim() || undefined;
91
91
  const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
92
92
  const progress = useRef(new Animated.Value(selectedIndex)).current;
93
+ const hasMounted = useRef(false);
93
94
  const [contentWidths, setContentWidths] = useState<number[]>(() =>
94
95
  Array.from({ length: optionCount }, () => 0),
95
96
  );
@@ -128,6 +129,12 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
128
129
  }, [optionCount]);
129
130
 
130
131
  useEffect(() => {
132
+ if (!hasMounted.current) {
133
+ hasMounted.current = true;
134
+ progress.setValue(selectedIndex);
135
+ return;
136
+ }
137
+
131
138
  const animation = Animated.timing(progress, {
132
139
  toValue: selectedIndex,
133
140
  duration: ANIMATION_DURATION,
@@ -202,7 +209,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
202
209
  {options.map(({ value: optionValue, label, icon: Icon, showLabel = true }, index) => {
203
210
  const selected = optionValue === selectedValue;
204
211
  const iconColor = selected ? colors.primary : colors.whiteAlpha20;
205
- const labelColor = selected ? colors.whiteAlpha86 : colors.whiteAlpha40;
212
+ const labelColor = selected ? colors.white : colors.grey150;
206
213
  const optionWidth = optionWidths[index] ?? 0;
207
214
 
208
215
  return (
@@ -219,6 +226,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
219
226
  fullWidth
220
227
  ? styles.optionFullWidth
221
228
  : [styles.optionContentWidth, optionWidth > 0 && { width: optionWidth }],
229
+ selected && selectedOptionWidth === 0 && styles.initialSelectedOption,
222
230
  ]}
223
231
  >
224
232
  <View style={styles.optionContent}>
@@ -228,6 +236,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
228
236
  numberOfLines={fullWidth ? 1 : undefined}
229
237
  style={[
230
238
  styles.label,
239
+ selected ? styles.selectedLabel : styles.inactiveLabel,
231
240
  !fullWidth && styles.contentWidthLabel,
232
241
  { color: labelColor },
233
242
  ]}
@@ -292,6 +301,9 @@ const styles = StyleSheet.create({
292
301
  optionContentWidth: {
293
302
  minWidth: OPTION_MIN_WIDTH,
294
303
  },
304
+ initialSelectedOption: {
305
+ backgroundColor: colors.grey600,
306
+ },
295
307
  activeIndicator: {
296
308
  position: "absolute",
297
309
  top: TRACK_PADDING,
@@ -304,6 +316,14 @@ const styles = StyleSheet.create({
304
316
  ...segmentedToggleTypography,
305
317
  flexShrink: 1,
306
318
  },
319
+ selectedLabel: {
320
+ fontWeight: "600",
321
+ letterSpacing: 0.36,
322
+ },
323
+ inactiveLabel: {
324
+ fontWeight: "400",
325
+ letterSpacing: 0.36,
326
+ },
307
327
  contentWidthLabel: {
308
328
  flexShrink: 0,
309
329
  },
@@ -347,15 +347,24 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
347
347
  return null;
348
348
  }, [menuStyle]);
349
349
 
350
+ const resolvedOptionListMaxHeight = useMemo(() => {
351
+ const flat = StyleSheet.flatten(menuStyle);
352
+ const menuMaxHeight = flat?.maxHeight;
353
+ if (typeof menuMaxHeight !== "number") return MENU_MAX_LIST_HEIGHT;
354
+
355
+ const menuChromeHeight = 24 + (showSearch ? 48 : 0);
356
+ return Math.max(43, menuMaxHeight - menuChromeHeight);
357
+ }, [menuStyle, showSearch]);
358
+
350
359
  const estimatedMenuHeight = useMemo(() => {
351
360
  const searchBlock = showSearch ? 40 + 8 : 0;
352
361
  const optionCount = Math.max(filteredOptions.length, 1);
353
362
  const listHeight = Math.min(
354
363
  optionCount * 43 + Math.max(0, optionCount - 1) * 8,
355
- MENU_MAX_LIST_HEIGHT,
364
+ resolvedOptionListMaxHeight,
356
365
  );
357
366
  return 24 + searchBlock + listHeight;
358
- }, [filteredOptions.length, showSearch]);
367
+ }, [filteredOptions.length, resolvedOptionListMaxHeight, showSearch]);
359
368
 
360
369
  const setOpen = useCallback(
361
370
  (next: boolean) => {
@@ -675,7 +684,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
675
684
  ) : null}
676
685
 
677
686
  <ScrollView
678
- style={styles.optionList}
687
+ style={[styles.optionList, { maxHeight: resolvedOptionListMaxHeight }]}
679
688
  contentContainerStyle={styles.optionListContent}
680
689
  keyboardShouldPersistTaps="handled"
681
690
  showsVerticalScrollIndicator={false}
@@ -118,3 +118,6 @@ export type { CommentCardProps } from "./CommentCard";
118
118
 
119
119
  export { Range } from "./Range";
120
120
  export type { RangeProps, RangeValue } from "./Range";
121
+
122
+ export { RadioButton } from "./RadioButton";
123
+ export type { RadioButtonProps, RadioButtonVariant } from "./RadioButton";
@@ -1,3 +1,5 @@
1
+ import { WASHER_PATHS } from "../Washer/washerPaths";
2
+
1
3
  export const amenityNames = [
2
4
  "kitchen",
3
5
  "wifi",
@@ -46,16 +48,7 @@ export const AMENITY_PATHS: Record<AmenityName, readonly AmenityPathDefinition[]
46
48
  strokeWidth: 2,
47
49
  },
48
50
  ],
49
- washer: [
50
- {
51
- d: "M15.833 1.66699H4.16634C3.7061 1.66699 3.33301 2.04009 3.33301 2.50033V17.5003C3.33301 17.9606 3.7061 18.3337 4.16634 18.3337H15.833C16.2932 18.3337 16.6663 17.9606 16.6663 17.5003V2.50033C16.6663 2.04009 16.2932 1.66699 15.833 1.66699ZM3.33301 5.00033C3.33301 5.46058 3.7061 5.83366 4.16634 5.83366H15.833C16.2933 5.83366 16.6663 5.46058 16.6663 5.00033V2.50033C16.6663 2.04009 16.2933 1.66699 15.833 1.66699H4.16634C3.7061 1.66699 3.33301 2.04009 3.33301 2.50033V5.00033ZM9.99967 15.0003C11.6105 15.0003 12.9163 13.6945 12.9163 12.0837C12.9163 10.4728 11.6105 9.16699 9.99967 9.16699C8.38884 9.16699 7.08301 10.4728 7.08301 12.0837C7.08301 13.6945 8.38884 15.0003 9.99967 15.0003Z",
52
- strokeWidth: 1.5,
53
- },
54
- {
55
- d: "M5.83333 4.58317C6.29357 4.58317 6.66667 4.21007 6.66667 3.74984C6.66667 3.2896 6.29357 2.9165 5.83333 2.9165C5.3731 2.9165 5 3.2896 5 3.74984C5 4.21007 5.3731 4.58317 5.83333 4.58317ZM8.33333 4.58317C8.79357 4.58317 9.16667 4.21007 9.16667 3.74984C9.16667 3.2896 8.79357 2.9165 8.33333 2.9165C7.8731 2.9165 7.5 3.2896 7.5 3.74984C7.5 4.21007 7.8731 4.58317 8.33333 4.58317Z",
56
- fill: true,
57
- },
58
- ],
51
+ washer: WASHER_PATHS,
59
52
  ac: [
60
53
  {
61
54
  d: "M17.5003 3.33301H2.50033C2.04009 3.33301 1.66699 3.7061 1.66699 4.16634V10.833C1.66699 11.2932 2.04009 11.6663 2.50033 11.6663H17.5003C17.9606 11.6663 18.3337 11.2932 18.3337 10.833V4.16634C18.3337 3.7061 17.9606 3.33301 17.5003 3.33301ZM15 8.3335H5V11.6668H15V8.3335ZM13.333 5.83301H14.9997M10 14.1665V16.6665M6.66699 15V15.8333M13.333 15V15.8333",
@@ -0,0 +1,14 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+ import { colors } from "../../theme";
3
+ import type { IconProps } from "../types";
4
+ import { DRAM_PATH } from "./dramPath";
5
+
6
+ export function DramIcon({ size = 12, color = colors.grey0 }: IconProps) {
7
+ return (
8
+ <Svg width={(size * 11) / 12} height={size} viewBox="0 0 11 12" fill="none">
9
+ <Path d={DRAM_PATH} fill={color} />
10
+ </Svg>
11
+ );
12
+ }
13
+
14
+ export { DRAM_PATH } from "./dramPath";
@@ -0,0 +1,20 @@
1
+ import { colors } from "../../theme";
2
+ import type { IconProps } from "../types";
3
+ import { DRAM_PATH } from "./dramPath";
4
+
5
+ export function DramIcon({ size = 12, color = colors.grey0 }: IconProps) {
6
+ return (
7
+ <svg
8
+ width={(size * 11) / 12}
9
+ height={size}
10
+ viewBox="0 0 11 12"
11
+ fill="none"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ aria-hidden
14
+ >
15
+ <path d={DRAM_PATH} fill={color} />
16
+ </svg>
17
+ );
18
+ }
19
+
20
+ export { DRAM_PATH } from "./dramPath";
@@ -0,0 +1,2 @@
1
+ export const DRAM_PATH =
2
+ "M8.72034 11.584H6.78434V4.06398C6.78434 3.23198 6.58168 2.61332 6.17634 2.20798C5.77101 1.80265 5.16834 1.59998 4.36834 1.59998C3.81368 1.59998 3.35501 1.70665 2.99234 1.91998C2.64034 2.12265 2.37901 2.44265 2.20834 2.87998C2.03768 3.31732 1.95234 3.88265 1.95234 4.57598H0.0003438C0.0003438 3.63732 0.160344 2.82665 0.480344 2.14398C0.811011 1.45065 1.30168 0.922651 1.95234 0.559984C2.61368 0.186651 3.43501 -1.62125e-05 4.41634 -1.62125e-05C5.34434 -1.62125e-05 6.12834 0.17065 6.76834 0.511984C7.41901 0.842651 7.90434 1.32798 8.22434 1.96798C8.55501 2.59732 8.72034 3.34932 8.72034 4.22398V11.584ZM4.67234 4.81598H10.8323V6.03198H4.67234V4.81598ZM4.67234 7.08798H10.8323V8.31998H4.67234V7.08798Z";
@@ -0,0 +1 @@
1
+ export { DramIcon, DRAM_PATH } from "./DramIcon";
@@ -4,10 +4,10 @@ export const QUIET_HOURS_PATH =
4
4
 
5
5
  /** No-smoking — stroke paths, 20×20 viewBox. */
6
6
  export const NO_SMOKING_PATHS = [
7
- "M6.66602 10.8334V14.1667",
8
- "M13.334 4.16663V4.58329C13.334 5.02532 13.5096 5.44924 13.8221 5.7618C14.1347 6.07436 14.5586 6.24996 15.0007 6.24996C15.4427 6.24996 15.8666 6.42555 16.1792 6.73811C16.4917 7.05068 16.6673 7.4746 16.6673 7.91663V8.33329",
7
+ "M6.66797 10.834V14.1673",
8
+ "M13.332 4.16602V4.58268C13.332 5.02471 13.5076 5.44863 13.8202 5.76119C14.1327 6.07375 14.5567 6.24935 14.9987 6.24935C15.4407 6.24935 15.8646 6.42494 16.1772 6.7375C16.4898 7.05007 16.6654 7.47399 16.6654 7.91602V8.33268",
9
9
  "M2.5 2.5L17.5 17.5",
10
- "M14.1667 10.8334H16.6667C16.8877 10.8334 17.0996 10.9212 17.2559 11.0775C17.4122 11.2337 17.5 11.4457 17.5 11.6667V13.3334C17.5 13.5667 17.4042 13.7775 17.25 13.9284M14.1667 14.1667H3.33333C3.11232 14.1667 2.90036 14.0789 2.74408 13.9226C2.5878 13.7663 2.5 13.5544 2.5 13.3334V11.6667C2.5 11.4457 2.5878 11.2337 2.74408 11.0775C2.90036 10.9212 3.11232 10.8334 3.33333 10.8334H10.8333",
10
+ "M14.1667 10.834H16.6667C16.8877 10.834 17.0996 10.9218 17.2559 11.0781C17.4122 11.2343 17.5 11.4463 17.5 11.6673V13.334C17.5 13.5673 17.4042 13.7782 17.25 13.929M14.1667 14.1673H3.33333C3.11232 14.1673 2.90036 14.0795 2.74408 13.9232C2.5878 13.767 2.5 13.555 2.5 13.334V11.6673C2.5 11.4463 2.5878 11.2343 2.74408 11.0781C2.90036 10.9218 3.11232 10.834 3.33333 10.834H10.8333",
11
11
  ] as const;
12
12
 
13
13
  /** Paw / pets — stroke paths, 20×20 viewBox. */
@@ -0,0 +1,27 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+ import { colors } from "../../theme";
3
+ import type { IconProps } from "../types";
4
+ import { MOON_STARS_MOON_PATH, MOON_STARS_SPARKLE_PATH } from "./moonStarsPaths";
5
+
6
+ export function MoonStarsIcon({ size = 15, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
7
+ return (
8
+ <Svg width={size} height={size} viewBox="0 0 15 15" fill="none">
9
+ <Path
10
+ d={MOON_STARS_SPARKLE_PATH}
11
+ stroke={color}
12
+ strokeWidth={strokeWidth}
13
+ strokeLinecap="round"
14
+ strokeLinejoin="round"
15
+ />
16
+ <Path
17
+ d={MOON_STARS_MOON_PATH}
18
+ stroke={color}
19
+ strokeWidth={strokeWidth}
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </Svg>
24
+ );
25
+ }
26
+
27
+ export { MOON_STARS_MOON_PATH, MOON_STARS_PATHS, MOON_STARS_SPARKLE_PATH } from "./moonStarsPaths";
@@ -0,0 +1,33 @@
1
+ import { colors } from "../../theme";
2
+ import type { IconProps } from "../types";
3
+ import { MOON_STARS_MOON_PATH, MOON_STARS_SPARKLE_PATH } from "./moonStarsPaths";
4
+
5
+ export function MoonStarsIcon({ size = 15, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
6
+ return (
7
+ <svg
8
+ width={size}
9
+ height={size}
10
+ viewBox="0 0 15 15"
11
+ fill="none"
12
+ xmlns="http://www.w3.org/2000/svg"
13
+ aria-hidden
14
+ >
15
+ <path
16
+ d={MOON_STARS_SPARKLE_PATH}
17
+ stroke={color}
18
+ strokeWidth={strokeWidth}
19
+ strokeLinecap="round"
20
+ strokeLinejoin="round"
21
+ />
22
+ <path
23
+ d={MOON_STARS_MOON_PATH}
24
+ stroke={color}
25
+ strokeWidth={strokeWidth}
26
+ strokeLinecap="round"
27
+ strokeLinejoin="round"
28
+ />
29
+ </svg>
30
+ );
31
+ }
32
+
33
+ export { MOON_STARS_MOON_PATH, MOON_STARS_PATHS, MOON_STARS_SPARKLE_PATH } from "./moonStarsPaths";
@@ -0,0 +1,6 @@
1
+ export {
2
+ MoonStarsIcon,
3
+ MOON_STARS_MOON_PATH,
4
+ MOON_STARS_PATHS,
5
+ MOON_STARS_SPARKLE_PATH,
6
+ } from "./MoonStarsIcon";
@@ -0,0 +1,7 @@
1
+ export const MOON_STARS_SPARKLE_PATH =
2
+ "M11.4167 0.75L11.8285 1.57372C12.0055 1.92771 12.094 2.1047 12.2122 2.25808C12.3171 2.39417 12.4392 2.51618 12.5753 2.62109C12.7286 2.73932 12.9056 2.82781 13.2596 3.00481L14.0833 3.41667L13.2596 3.82853C12.9056 4.00552 12.7286 4.09402 12.5753 4.21224C12.4392 4.31715 12.3171 4.43916 12.2122 4.57526C12.094 4.72863 12.0055 4.90563 11.8285 5.25961L11.4167 6.08333L11.0048 5.25961C10.8278 4.90563 10.7393 4.72863 10.6211 4.57526C10.5162 4.43916 10.3942 4.31715 10.2581 4.21224C10.1047 4.09402 9.92771 4.00552 9.57372 3.82853L8.75 3.41667L9.57372 3.00481C9.92771 2.82781 10.1047 2.73932 10.2581 2.62109C10.3942 2.51618 10.5162 2.39417 10.6211 2.25808C10.7393 2.1047 10.8278 1.92771 11.0048 1.57372L11.4167 0.75Z";
3
+
4
+ export const MOON_STARS_MOON_PATH =
5
+ "M13.4167 8.34286C12.5427 9.876 10.893 10.9097 9.00196 10.9097C6.19739 10.9097 3.92383 8.63612 3.92383 5.83155C3.92383 3.94036 4.95766 2.29063 6.491 1.41667C3.26986 1.72208 0.75 4.43461 0.75 7.73569C0.75 11.2414 3.59195 14.0833 7.09766 14.0833C10.3986 14.0833 13.111 11.5637 13.4167 8.34286Z";
6
+
7
+ export const MOON_STARS_PATHS = [MOON_STARS_SPARKLE_PATH, MOON_STARS_MOON_PATH] as const;