@ecohouse/ui 0.1.34 → 0.1.36

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 (43) hide show
  1. package/README.md +1 -0
  2. package/dist/index.cjs +903 -489
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.d.cts +169 -129
  5. package/dist/index.d.ts +169 -129
  6. package/dist/index.js +901 -491
  7. package/dist/index.js.map +1 -1
  8. package/package.json +1 -1
  9. package/src/components/AccordionItem/AccordionItem.tsx +14 -11
  10. package/src/components/AccountHeader/AccountHeader.tsx +16 -0
  11. package/src/components/DatePicker/DatePicker.tsx +47 -14
  12. package/src/components/Input/Input.tsx +54 -21
  13. package/src/components/LanguageSwitcher/LanguageSwitcher.stories.tsx +11 -0
  14. package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +103 -19
  15. package/src/components/LanguageSwitcher/index.ts +5 -1
  16. package/src/components/SegmentedToggle/SegmentedToggle.stories.tsx +9 -0
  17. package/src/components/SegmentedToggle/SegmentedToggle.tsx +19 -13
  18. package/src/components/Select/Select.tsx +90 -5
  19. package/src/components/Textarea/Textarea.tsx +29 -4
  20. package/src/components/index.ts +5 -1
  21. package/src/icons/Globe/GlobeIcon.tsx +2 -2
  22. package/src/icons/Globe/GlobeIcon.web.tsx +2 -2
  23. package/src/icons/Globe/globePath.ts +2 -2
  24. package/src/icons/Key/keyPath.ts +1 -1
  25. package/src/icons/MoreHorizontal/MoreHorizontalIcon.tsx +26 -0
  26. package/src/icons/MoreHorizontal/MoreHorizontalIcon.web.tsx +25 -0
  27. package/src/icons/MoreHorizontal/index.ts +1 -0
  28. package/src/icons/MoreHorizontal/moreHorizontalPath.ts +2 -0
  29. package/src/icons/TableView/TableViewIcon.tsx +20 -0
  30. package/src/icons/TableView/TableViewIcon.web.tsx +26 -0
  31. package/src/icons/TableView/index.ts +1 -0
  32. package/src/icons/TableView/tableViewPath.ts +2 -0
  33. package/src/icons/index.ts +2 -0
  34. package/src/icons/registry.ts +6 -0
  35. package/src/index.ts +7 -0
  36. package/src/primitives/IconStatusBadge/IconStatusBadge.stories.tsx +21 -0
  37. package/src/primitives/IconStatusBadge/IconStatusBadge.tsx +76 -0
  38. package/src/primitives/IconStatusBadge/index.ts +2 -0
  39. package/src/primitives/InfoCardV2/InfoCardV2.stories.tsx +64 -0
  40. package/src/primitives/InfoCardV2/InfoCardV2.tsx +116 -0
  41. package/src/primitives/InfoCardV2/index.ts +2 -0
  42. package/src/primitives/index.ts +7 -0
  43. package/src/theme/typography.ts +3 -3
@@ -20,6 +20,8 @@ export interface SegmentedToggleOption {
20
20
  value: string;
21
21
  label: string;
22
22
  icon?: IconComponent;
23
+ /** Hides the visual label while retaining it as the accessible name. */
24
+ showLabel?: boolean;
23
25
  }
24
26
 
25
27
  export interface SegmentedToggleProps extends Omit<ViewProps, "style" | "children"> {
@@ -163,7 +165,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
163
165
  accessibilityRole="radiogroup"
164
166
  >
165
167
  {!fullWidth
166
- ? options.map(({ value: optionValue, label, icon: Icon }, index) => (
168
+ ? options.map(({ value: optionValue, label, icon: Icon, showLabel = true }, index) => (
167
169
  <View
168
170
  key={`${optionValue}-measurement`}
169
171
  pointerEvents="none"
@@ -182,7 +184,9 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
182
184
  style={styles.optionMeasurement}
183
185
  >
184
186
  {Icon ? <Icon size={ICON_SIZE} /> : null}
185
- <Text style={[styles.label, styles.contentWidthLabel]}>{label}</Text>
187
+ {showLabel ? (
188
+ <Text style={[styles.label, styles.contentWidthLabel]}>{label}</Text>
189
+ ) : null}
186
190
  </View>
187
191
  ))
188
192
  : null}
@@ -195,7 +199,7 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
195
199
  ]}
196
200
  />
197
201
  ) : null}
198
- {options.map(({ value: optionValue, label, icon: Icon }, index) => {
202
+ {options.map(({ value: optionValue, label, icon: Icon, showLabel = true }, index) => {
199
203
  const selected = optionValue === selectedValue;
200
204
  const iconColor = selected ? colors.primary : colors.whiteAlpha20;
201
205
  const labelColor = selected ? colors.whiteAlpha86 : colors.whiteAlpha40;
@@ -219,16 +223,18 @@ export const SegmentedToggle = forwardRef<ComponentRef<typeof View>, SegmentedTo
219
223
  >
220
224
  <View style={styles.optionContent}>
221
225
  {Icon ? <Icon size={ICON_SIZE} color={iconColor} /> : null}
222
- <Text
223
- numberOfLines={fullWidth ? 1 : undefined}
224
- style={[
225
- styles.label,
226
- !fullWidth && styles.contentWidthLabel,
227
- { color: labelColor },
228
- ]}
229
- >
230
- {label}
231
- </Text>
226
+ {showLabel ? (
227
+ <Text
228
+ numberOfLines={fullWidth ? 1 : undefined}
229
+ style={[
230
+ styles.label,
231
+ !fullWidth && styles.contentWidthLabel,
232
+ { color: labelColor },
233
+ ]}
234
+ >
235
+ {label}
236
+ </Text>
237
+ ) : null}
232
238
  </View>
233
239
  </Pressable>
234
240
  );
@@ -2,6 +2,7 @@ import {
2
2
  forwardRef,
3
3
  useCallback,
4
4
  useEffect,
5
+ useLayoutEffect,
5
6
  useMemo,
6
7
  useRef,
7
8
  useState,
@@ -64,6 +65,8 @@ type SelectBaseProps = Omit<ViewProps, "style" | "children"> & {
64
65
  /** Override color for the selected value text in the trigger. */
65
66
  valueColor?: string;
66
67
  style?: StyleProp<ViewStyle>;
68
+ /** Styles applied to the dropdown menu only (not the trigger). */
69
+ menuStyle?: StyleProp<ViewStyle>;
67
70
  className?: string;
68
71
  };
69
72
 
@@ -268,6 +271,7 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
268
271
  emptyText = "No results",
269
272
  valueColor,
270
273
  style,
274
+ menuStyle,
271
275
  className,
272
276
  accessibilityLabel,
273
277
  // Pulled out only to keep them off `rest` (which is spread onto the root View);
@@ -297,6 +301,10 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
297
301
  const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
298
302
  const [searchQuery, setSearchQuery] = useState("");
299
303
  const [hoveredValue, setHoveredValue] = useState<string | null>(null);
304
+ /** Web: flip menu to the trigger's right edge when it would overflow the viewport. */
305
+ const [menuAlignEnd, setMenuAlignEnd] = useState(false);
306
+ /** Web: open above the trigger when there isn't enough space below. */
307
+ const [menuPlaceAbove, setMenuPlaceAbove] = useState(false);
300
308
 
301
309
  const selectedValues = useMemo(() => {
302
310
  const current = isValueControlled
@@ -330,6 +338,25 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
330
338
 
331
339
  useApplyWebClassName(rootRef, className);
332
340
 
341
+ const resolvedMenuWidth = useMemo(() => {
342
+ const flat = StyleSheet.flatten(menuStyle);
343
+ const width = flat?.width;
344
+ if (typeof width === "number") return width;
345
+ const minWidth = flat?.minWidth;
346
+ if (typeof minWidth === "number") return minWidth;
347
+ return null;
348
+ }, [menuStyle]);
349
+
350
+ const estimatedMenuHeight = useMemo(() => {
351
+ const searchBlock = showSearch ? 40 + 8 : 0;
352
+ const optionCount = Math.max(filteredOptions.length, 1);
353
+ const listHeight = Math.min(
354
+ optionCount * 43 + Math.max(0, optionCount - 1) * 8,
355
+ MENU_MAX_LIST_HEIGHT,
356
+ );
357
+ return 24 + searchBlock + listHeight;
358
+ }, [filteredOptions.length, showSearch]);
359
+
333
360
  const setOpen = useCallback(
334
361
  (next: boolean) => {
335
362
  if (!isOpenControlled) setUncontrolledOpen(next);
@@ -341,6 +368,8 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
341
368
  } else {
342
369
  setSearchQuery("");
343
370
  setHoveredValue(null);
371
+ setMenuAlignEnd(false);
372
+ setMenuPlaceAbove(false);
344
373
  // Let the parent reset its externally fetched options.
345
374
  onSearchChange?.("");
346
375
  }
@@ -348,6 +377,38 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
348
377
  [isOpenControlled, onOpenChange, onSearchChange, selectedValues],
349
378
  );
350
379
 
380
+ // Prefer start (left) / below; flip when the menu would overflow the viewport (web).
381
+ useLayoutEffect(() => {
382
+ if (Platform.OS !== "web" || !isOpen) return;
383
+
384
+ const updateMenuPlacement = () => {
385
+ const trigger = triggerRef.current as unknown as {
386
+ getBoundingClientRect?: () => DOMRect;
387
+ } | null;
388
+ const rect = trigger?.getBoundingClientRect?.();
389
+ if (!rect) return;
390
+
391
+ const viewportPadding = 8;
392
+ const gap = 8;
393
+ const menuWidth = Math.max(rect.width, resolvedMenuWidth ?? 0);
394
+ const overflowsRight = rect.left + menuWidth > window.innerWidth - viewportPadding;
395
+ setMenuAlignEnd(overflowsRight);
396
+
397
+ const spaceBelow = window.innerHeight - viewportPadding - rect.bottom;
398
+ const spaceAbove = rect.top - viewportPadding;
399
+ const needsAbove = estimatedMenuHeight + gap > spaceBelow && spaceAbove > spaceBelow;
400
+ setMenuPlaceAbove(needsAbove);
401
+ };
402
+
403
+ updateMenuPlacement();
404
+ window.addEventListener("resize", updateMenuPlacement);
405
+ window.addEventListener("scroll", updateMenuPlacement, true);
406
+ return () => {
407
+ window.removeEventListener("resize", updateMenuPlacement);
408
+ window.removeEventListener("scroll", updateMenuPlacement, true);
409
+ };
410
+ }, [estimatedMenuHeight, isOpen, resolvedMenuWidth]);
411
+
351
412
  const handleSearchChange = (text: string) => {
352
413
  setSearchQuery(text);
353
414
  onSearchChange?.(text);
@@ -583,7 +644,14 @@ export const Select = forwardRef<ComponentRef<typeof View>, SelectProps>(
583
644
  </Pressable>
584
645
 
585
646
  {isOpen ? (
586
- <View style={styles.menu}>
647
+ <View
648
+ style={[
649
+ styles.menu,
650
+ menuAlignEnd ? styles.menuAlignEnd : styles.menuAlignStart,
651
+ menuPlaceAbove ? styles.menuAbove : styles.menuBelow,
652
+ menuStyle,
653
+ ]}
654
+ >
587
655
  {showSearch ? (
588
656
  <View style={styles.searchField}>
589
657
  <View style={styles.iconSlot}>
@@ -777,10 +845,7 @@ const styles = StyleSheet.create({
777
845
  },
778
846
  menu: {
779
847
  position: "absolute",
780
- top: "100%",
781
- left: 0,
782
- right: 0,
783
- marginTop: 8,
848
+ minWidth: "100%",
784
849
  zIndex: 10,
785
850
  borderRadius: MENU_RADIUS,
786
851
  padding: 12,
@@ -795,6 +860,26 @@ const styles = StyleSheet.create({
795
860
  },
796
861
  }),
797
862
  },
863
+ menuAlignStart: {
864
+ left: 0,
865
+ right: "auto",
866
+ },
867
+ menuAlignEnd: {
868
+ left: "auto",
869
+ right: 0,
870
+ },
871
+ menuBelow: {
872
+ top: "100%",
873
+ bottom: "auto",
874
+ marginTop: 8,
875
+ marginBottom: 0,
876
+ },
877
+ menuAbove: {
878
+ top: "auto",
879
+ bottom: "100%",
880
+ marginTop: 0,
881
+ marginBottom: 8,
882
+ },
798
883
  optionList: {
799
884
  maxHeight: MENU_MAX_LIST_HEIGHT,
800
885
  },
@@ -1,6 +1,7 @@
1
- import { forwardRef, useMemo, useRef, useState, type ComponentRef } from "react";
1
+ import { forwardRef, useId, useMemo, useRef, useState, type ComponentRef } from "react";
2
2
  import {
3
3
  Platform,
4
+ Pressable,
4
5
  StyleSheet,
5
6
  Text,
6
7
  TextInput,
@@ -132,6 +133,8 @@ export const Textarea = forwardRef<ComponentRef<typeof TextInput>, TextareaProps
132
133
  const setInputRef = useMemo(() => mergeRefs(inputRef, forwardedRef), [forwardedRef]);
133
134
  const [focused, setFocused] = useState(false);
134
135
  const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? "");
136
+ const reactId = useId();
137
+ const inputDomId = `eh-textarea-${reactId.replace(/:/g, "")}`;
135
138
 
136
139
  const isControlled = typeof value === "string";
137
140
  const currentValue = isControlled ? value : uncontrolledValue;
@@ -141,28 +144,40 @@ export const Textarea = forwardRef<ComponentRef<typeof TextInput>, TextareaProps
141
144
  const chrome = chromeFor(visualState);
142
145
  const resolvedAccessibilityLabel = accessibilityLabel ?? (showLabel ? undefined : label);
143
146
 
147
+ const focusInput = () => {
148
+ if (disabled) return;
149
+ inputRef.current?.focus();
150
+ };
151
+
144
152
  useApplyWebClassName(rootRef, className);
145
153
  useApplyWebClassName(inputRef, inputClassName);
146
154
 
147
155
  return (
148
156
  <View ref={rootRef} style={[styles.root, disabled && styles.disabled, style]}>
149
157
  {showLabel && label ? (
150
- <Text style={[styles.label, { color: chrome.labelColor }]}>{label}</Text>
158
+ <Pressable disabled={disabled} onPress={focusInput} style={styles.labelPressable}>
159
+ <Text style={[styles.label, { color: chrome.labelColor }]}>{label}</Text>
160
+ </Pressable>
151
161
  ) : null}
152
162
 
153
- <View
163
+ <Pressable
164
+ accessibilityRole="none"
165
+ disabled={disabled}
166
+ onPress={focusInput}
154
167
  style={[
155
168
  styles.field,
156
169
  {
157
170
  borderColor: chrome.borderColor,
158
171
  backgroundColor: chrome.backgroundColor,
159
172
  },
173
+ Platform.OS === "web" ? styles.fieldWeb : null,
160
174
  fieldStyle,
161
175
  ]}
162
176
  >
163
177
  <TextInput
164
178
  ref={setInputRef}
165
179
  {...textInputProps}
180
+ nativeID={inputDomId}
166
181
  multiline
167
182
  textAlignVertical="top"
168
183
  value={isControlled ? value : undefined}
@@ -193,7 +208,7 @@ export const Textarea = forwardRef<ComponentRef<typeof TextInput>, TextareaProps
193
208
  accessibilityLabel={resolvedAccessibilityLabel}
194
209
  accessibilityState={{ disabled }}
195
210
  />
196
- </View>
211
+ </Pressable>
197
212
 
198
213
  {showHint && hint ? (
199
214
  <Text style={[styles.hint, { color: chrome.hintColor }]}>{hint}</Text>
@@ -211,6 +226,9 @@ const styles = StyleSheet.create({
211
226
  disabled: {
212
227
  opacity: 0.6,
213
228
  },
229
+ labelPressable: {
230
+ alignSelf: "flex-start",
231
+ },
214
232
  label: {
215
233
  ...textareaTypography.label,
216
234
  },
@@ -223,14 +241,21 @@ const styles = StyleSheet.create({
223
241
  paddingLeft: FIELD_PADDING_HORIZONTAL,
224
242
  paddingRight: FIELD_PADDING_HORIZONTAL,
225
243
  },
244
+ fieldWeb: {
245
+ cursor: "text" as unknown as ViewStyle["cursor"],
246
+ },
226
247
  input: {
227
248
  flex: 1,
249
+ alignSelf: "stretch",
228
250
  paddingVertical: 0,
229
251
  margin: 0,
230
252
  },
231
253
  inputWeb: {
232
254
  outlineStyle: "none",
233
255
  resize: "none",
256
+ height: "100%",
257
+ minWidth: 0,
258
+ cursor: "text" as unknown as TextStyle["cursor"],
234
259
  } as TextStyle,
235
260
  inputAndroid: {
236
261
  includeFontPadding: false,
@@ -41,7 +41,11 @@ export { Sidebar } from "./Sidebar";
41
41
  export type { SidebarProps, SidebarItem, SidebarUser } from "./Sidebar";
42
42
 
43
43
  export { LanguageSwitcher, defaultLanguageOptions } from "./LanguageSwitcher";
44
- export type { LanguageOption, LanguageSwitcherProps } from "./LanguageSwitcher";
44
+ export type {
45
+ LanguageOption,
46
+ LanguageSwitcherProps,
47
+ LanguageSwitcherVariant,
48
+ } from "./LanguageSwitcher";
45
49
 
46
50
  export { StatCard } from "./StatCard";
47
51
  export type { StatCardProps } from "./StatCard";
@@ -3,9 +3,9 @@ import { colors } from "../../theme";
3
3
  import type { IconProps } from "../types";
4
4
  import { GLOBE_PATH } from "./globePath";
5
5
 
6
- export function GlobeIcon({ size = 20, color = colors.primary, strokeWidth = 2 }: IconProps) {
6
+ export function GlobeIcon({ size = 16, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
7
7
  return (
8
- <Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
8
+ <Svg width={size} height={size} viewBox="0 0 16 16" fill="none">
9
9
  <Path
10
10
  d={GLOBE_PATH}
11
11
  stroke={color}
@@ -2,12 +2,12 @@ import { colors } from "../../theme";
2
2
  import type { IconProps } from "../types";
3
3
  import { GLOBE_PATH } from "./globePath";
4
4
 
5
- export function GlobeIcon({ size = 20, color = colors.primary, strokeWidth = 2 }: IconProps) {
5
+ export function GlobeIcon({ size = 16, color = colors.primary, strokeWidth = 1.5 }: IconProps) {
6
6
  return (
7
7
  <svg
8
8
  width={size}
9
9
  height={size}
10
- viewBox="0 0 20 20"
10
+ viewBox="0 0 16 16"
11
11
  fill="none"
12
12
  xmlns="http://www.w3.org/2000/svg"
13
13
  aria-hidden
@@ -1,3 +1,3 @@
1
- /** Globe paths — 20×20 viewBox. */
1
+ /** Globe paths — 16×16 viewBox. */
2
2
  export const GLOBE_PATH =
3
- "M18.3333 10C18.3333 14.6024 14.6024 18.3333 10 18.3333M18.3333 10C18.3333 5.39763 14.6024 1.66667 10 1.66667M18.3333 10H1.66667M10 18.3333C5.39763 18.3333 1.66667 14.6024 1.66667 10M10 18.3333C12.0833 16.0511 13.2675 13.0836 13.3333 10C13.2675 6.91643 12.0833 3.94892 10 1.66667M10 18.3333C7.91667 16.0511 6.73248 13.0836 6.66667 10C6.73248 6.91643 7.91667 3.94892 10 1.66667M1.66667 10C1.66667 5.39763 5.39763 1.66667 10 1.66667";
3
+ "M1.3335 8.00004H14.6668M1.3335 8.00004C1.3335 11.6819 4.31826 14.6667 8.00016 14.6667M1.3335 8.00004C1.3335 4.31814 4.31826 1.33337 8.00016 1.33337M14.6668 8.00004C14.6668 11.6819 11.6821 14.6667 8.00016 14.6667M14.6668 8.00004C14.6668 4.31814 11.6821 1.33337 8.00016 1.33337M8.00016 1.33337C9.66768 3.15894 10.6153 5.52806 10.6668 8.00004C10.6153 10.472 9.66768 12.8411 8.00016 14.6667M8.00016 1.33337C6.33264 3.15894 5.38499 5.52806 5.3335 8.00004C5.38499 10.472 6.33264 12.8411 8.00016 14.6667";
@@ -1,2 +1,2 @@
1
1
  export const KEY_PATH =
2
- "M5.28673 10.0002L7.0545 11.768M7.6435 7.64301L9.41127 9.41078M10.0003 5.28624L11.768 7.05401M4.22553 11.0604L11.0609 4.22505C11.7209 3.56502 12.0513 3.235 12.4318 3.11136C12.7666 3.00259 13.127 3.00259 13.4618 3.11136C13.8421 3.23494 14.1718 3.56464 14.8311 4.22393L15.775 5.16785C16.4351 5.82788 16.7654 6.15867 16.8891 6.53922C16.9979 6.87396 16.9976 7.23381 16.8889 7.56855C16.7652 7.9491 16.4353 8.27937 15.7753 8.9394L8.93989 15.7748C8.27986 16.4348 7.94959 16.7647 7.56904 16.8884C7.2343 16.9971 6.87445 16.9974 6.53971 16.8886C6.15916 16.765 5.82837 16.4346 5.16834 15.7745L4.22441 14.8306C3.56513 14.1713 3.23542 13.8416 3.11185 13.4613C3.00308 13.1266 3.00308 12.7661 3.11185 12.4313C3.23549 12.0508 3.56551 11.7204 4.22553 11.0604Z";
2
+ "M14.1668 8.33333V6.66667C14.1668 4.36548 12.3013 2.5 10.0002 2.5C7.69898 2.5 5.8335 4.36548 5.8335 6.66667V8.33333M10.0002 12.0833V13.75M7.3335 17.5H12.6668C14.067 17.5 14.767 17.5 15.3018 17.2275C15.7722 16.9878 16.1547 16.6054 16.3943 16.135C16.6668 15.6002 16.6668 14.9001 16.6668 13.5V12.3333C16.6668 10.9332 16.6668 10.2331 16.3943 9.69836C16.1547 9.22795 15.7722 8.8455 15.3018 8.60582C14.767 8.33333 14.067 8.33333 12.6668 8.33333H7.3335C5.93336 8.33333 5.2333 8.33333 4.69852 8.60582C4.22811 8.8455 3.84566 9.22795 3.60598 9.69836C3.3335 10.2331 3.3335 10.9332 3.3335 12.3333V13.5C3.3335 14.9001 3.3335 15.6002 3.60598 16.135C3.84566 16.6054 4.22811 16.9878 4.69852 17.2275C5.2333 17.5 5.93336 17.5 7.3335 17.5Z";
@@ -0,0 +1,26 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+ import { colors } from "../../theme";
3
+ import type { IconProps } from "../types";
4
+ import { MORE_HORIZONTAL_PATH } from "./moreHorizontalPath";
5
+
6
+ export { MORE_HORIZONTAL_PATH } from "./moreHorizontalPath";
7
+
8
+ const ASPECT_RATIO = 4 / 13;
9
+
10
+ export function MoreHorizontalIcon({
11
+ size = 13,
12
+ color = colors.white,
13
+ strokeWidth = 2,
14
+ }: IconProps) {
15
+ return (
16
+ <Svg width={size} height={size * ASPECT_RATIO} viewBox="0 0 13 4" fill="none">
17
+ <Path
18
+ d={MORE_HORIZONTAL_PATH}
19
+ stroke={color}
20
+ strokeWidth={strokeWidth}
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ />
24
+ </Svg>
25
+ );
26
+ }
@@ -0,0 +1,25 @@
1
+ import { colors } from "../../theme";
2
+ import type { IconProps } from "../types";
3
+ import { MORE_HORIZONTAL_PATH } from "./moreHorizontalPath";
4
+
5
+ export { MORE_HORIZONTAL_PATH } from "./moreHorizontalPath";
6
+
7
+ const ASPECT_RATIO = 4 / 13;
8
+
9
+ export function MoreHorizontalIcon({
10
+ size = 13,
11
+ color = colors.white,
12
+ strokeWidth = 2,
13
+ }: IconProps) {
14
+ return (
15
+ <svg width={size} height={size * ASPECT_RATIO} viewBox="0 0 13 4" fill="none" aria-hidden>
16
+ <path
17
+ d={MORE_HORIZONTAL_PATH}
18
+ stroke={color}
19
+ strokeWidth={strokeWidth}
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
@@ -0,0 +1 @@
1
+ export { MoreHorizontalIcon, MORE_HORIZONTAL_PATH } from "./MoreHorizontalIcon";
@@ -0,0 +1,2 @@
1
+ export const MORE_HORIZONTAL_PATH =
2
+ "M6.33333 2.33333C6.70152 2.33333 7 2.03486 7 1.66667C7 1.29848 6.70152 1 6.33333 1C5.96514 1 5.66667 1.29848 5.66667 1.66667C5.66667 2.03486 5.96514 2.33333 6.33333 2.33333ZM11 2.33333C11.3682 2.33333 11.6667 2.03486 11.6667 1.66667C11.6667 1.29848 11.3682 1 11 1C10.6318 1 10.3333 1.29848 10.3333 1.66667C10.3333 2.03486 10.6318 2.33333 11 2.33333ZM1.66667 2.33333C2.03486 2.33333 2.33333 2.03486 2.33333 1.66667C2.33333 1.29848 2.03486 1 1.66667 1C1.29848 1 1 1.29848 1 1.66667C1 2.03486 1.29848 2.33333 1.66667 2.33333Z";
@@ -0,0 +1,20 @@
1
+ import Svg, { Path } from "react-native-svg";
2
+ import { colors } from "../../theme";
3
+ import type { IconProps } from "../types";
4
+ import { TABLE_VIEW_PATH } from "./tableViewPath";
5
+
6
+ export { TABLE_VIEW_PATH } from "./tableViewPath";
7
+
8
+ export function TableViewIcon({ size = 17, color = colors.grey400, strokeWidth = 2 }: IconProps) {
9
+ return (
10
+ <Svg width={size} height={size} viewBox="0 0 17 17" fill="none">
11
+ <Path
12
+ d={TABLE_VIEW_PATH}
13
+ stroke={color}
14
+ strokeWidth={strokeWidth}
15
+ strokeLinecap="round"
16
+ strokeLinejoin="round"
17
+ />
18
+ </Svg>
19
+ );
20
+ }
@@ -0,0 +1,26 @@
1
+ import { colors } from "../../theme";
2
+ import type { IconProps } from "../types";
3
+ import { TABLE_VIEW_PATH } from "./tableViewPath";
4
+
5
+ export { TABLE_VIEW_PATH } from "./tableViewPath";
6
+
7
+ export function TableViewIcon({ size = 17, color = colors.grey400, strokeWidth = 2 }: IconProps) {
8
+ return (
9
+ <svg
10
+ width={size}
11
+ height={size}
12
+ viewBox="0 0 17 17"
13
+ fill="none"
14
+ xmlns="http://www.w3.org/2000/svg"
15
+ aria-hidden
16
+ >
17
+ <path
18
+ d={TABLE_VIEW_PATH}
19
+ stroke={color}
20
+ strokeWidth={strokeWidth}
21
+ strokeLinecap="round"
22
+ strokeLinejoin="round"
23
+ />
24
+ </svg>
25
+ );
26
+ }
@@ -0,0 +1 @@
1
+ export { TableViewIcon, TABLE_VIEW_PATH } from "./TableViewIcon";
@@ -0,0 +1,2 @@
1
+ export const TABLE_VIEW_PATH =
2
+ "M1 6L16 6M6 1L6 16M5 1H12C13.4001 1 14.1002 1 14.635 1.27248C15.1054 1.51217 15.4878 1.89462 15.7275 2.36502C16 2.8998 16 3.59987 16 5V12C16 13.4001 16 14.1002 15.7275 14.635C15.4878 15.1054 15.1054 15.4878 14.635 15.7275C14.1002 16 13.4001 16 12 16H5C3.59987 16 2.8998 16 2.36502 15.7275C1.89462 15.4878 1.51217 15.1054 1.27248 14.635C1 14.1002 1 13.4001 1 12V5C1 3.59987 1 2.8998 1.27248 2.36502C1.51217 1.89462 1.89462 1.51217 2.36502 1.27248C2.8998 1 3.59987 1 5 1Z";
@@ -70,6 +70,7 @@ export { MapCollapseIcon, MapExpandIcon } from "./MapControls";
70
70
  export { MenuIcon } from "./Menu";
71
71
  export type { MenuIconProps } from "./Menu";
72
72
  export { MenuLinesIcon } from "./MenuLines";
73
+ export { MoreHorizontalIcon } from "./MoreHorizontal";
73
74
  export { NavigateIcon } from "./Navigate";
74
75
  export { PhoneIcon } from "./Phone";
75
76
  export { PlusIcon } from "./Plus";
@@ -83,6 +84,7 @@ export { SidebarIcon } from "./Sidebar";
83
84
  export { SortIcon } from "./Sort";
84
85
  export { StarIcon } from "./Star";
85
86
  export { StarFilledIcon } from "./StarFilled";
87
+ export { TableViewIcon } from "./TableView";
86
88
  export { UserIcon } from "./User";
87
89
  export { UsersAltIcon } from "./UsersAlt";
88
90
  export { VideoIcon } from "./Video";
@@ -60,6 +60,7 @@ import { MapCollapseIcon, MapExpandIcon } from "./MapControls";
60
60
  import { MenuIcon } from "./Menu";
61
61
  import { MenuLinesIcon } from "./MenuLines";
62
62
  import { MinusIcon } from "./Minus";
63
+ import { MoreHorizontalIcon } from "./MoreHorizontal";
63
64
  import { NavigateIcon } from "./Navigate";
64
65
  import { PhoneIcon } from "./Phone";
65
66
  import { PlusIcon } from "./Plus";
@@ -72,6 +73,7 @@ import { SidebarIcon } from "./Sidebar";
72
73
  import { SortIcon } from "./Sort";
73
74
  import { StarIcon } from "./Star";
74
75
  import { StarFilledIcon } from "./StarFilled";
76
+ import { TableViewIcon } from "./TableView";
75
77
  import { UserIcon } from "./User";
76
78
  import { UsersAltIcon } from "./UsersAlt";
77
79
  import { VideoIcon } from "./Video";
@@ -133,6 +135,7 @@ export const icons = {
133
135
  menu: MenuIcon,
134
136
  menuLines: MenuLinesIcon,
135
137
  minus: MinusIcon,
138
+ moreHorizontal: MoreHorizontalIcon,
136
139
  navigate: NavigateIcon,
137
140
  noSmoking: NoSmokingIcon,
138
141
  paw: PawIcon,
@@ -152,6 +155,7 @@ export const icons = {
152
155
  sort: SortIcon,
153
156
  star: StarIcon,
154
157
  starFilled: StarFilledIcon,
158
+ tableView: TableViewIcon,
155
159
  tooltipArrow: TooltipArrowIcon,
156
160
  trash: TrashIcon,
157
161
  user: UserIcon,
@@ -181,6 +185,7 @@ export const iconGroups = {
181
185
  "menu",
182
186
  "menuLines",
183
187
  "navigate",
188
+ "tableView",
184
189
  ],
185
190
  actions: [
186
191
  "show",
@@ -211,6 +216,7 @@ export const iconGroups = {
211
216
  "star",
212
217
  "starFilled",
213
218
  "minus",
219
+ "moreHorizontal",
214
220
  "plus",
215
221
  "refresh",
216
222
  "refundStatus",
package/src/index.ts CHANGED
@@ -54,6 +54,7 @@ export type {
54
54
  SidebarUser,
55
55
  LanguageOption,
56
56
  LanguageSwitcherProps,
57
+ LanguageSwitcherVariant,
57
58
  StatCardProps,
58
59
  ButtonProps,
59
60
  ButtonSize,
@@ -93,6 +94,8 @@ export {
93
94
  BRAND_LOGO_DEFAULT_HEIGHT,
94
95
  BRAND_LOGO_DEFAULT_WIDTH,
95
96
  FavoritesButton,
97
+ IconStatusBadge,
98
+ InfoCardV2,
96
99
  MenuItem,
97
100
  } from "./primitives";
98
101
  export type {
@@ -101,6 +104,8 @@ export type {
101
104
  BrandLogoProps,
102
105
  BrandLogoVariant,
103
106
  FavoritesButtonProps,
107
+ IconStatusBadgeProps,
108
+ InfoCardV2Props,
104
109
  MenuItemProps,
105
110
  } from "./primitives";
106
111
 
@@ -136,6 +141,7 @@ export {
136
141
  GuestsIcon,
137
142
  RatingStarIcon,
138
143
  SaveHeartIcon,
144
+ MoreHorizontalIcon,
139
145
  AreaExpandIcon,
140
146
  CheckSuccessIcon,
141
147
  ClockFilledIcon,
@@ -187,6 +193,7 @@ export {
187
193
  SortIcon,
188
194
  StarIcon,
189
195
  StarFilledIcon,
196
+ TableViewIcon,
190
197
  UserIcon,
191
198
  UsersAltIcon,
192
199
  VideoIcon,
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from "@storybook/react-vite";
2
+ import { LockIcon } from "../../icons";
3
+ import { IconStatusBadge } from "./IconStatusBadge";
4
+
5
+ const meta = {
6
+ title: "Primitives/Icon Status Badge",
7
+ component: IconStatusBadge,
8
+ args: {
9
+ icon: LockIcon,
10
+ iconProps: { color: "#60C27A" },
11
+ label: "Available",
12
+ },
13
+ parameters: { backgrounds: { default: "black" } },
14
+ } satisfies Meta<typeof IconStatusBadge>;
15
+
16
+ export default meta;
17
+ type Story = StoryObj<typeof meta>;
18
+
19
+ export const Armenian: Story = { args: { label: "Ազատ" } };
20
+ export const English: Story = {};
21
+ export const Russian: Story = { args: { label: "Свободен" } };