@ecohouse/ui 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -12
- package/dist/index.cjs +363 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +360 -121
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/LanguageSwitcher/LanguageSwitcher.stories.tsx +52 -0
- package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +287 -0
- package/src/components/LanguageSwitcher/index.ts +2 -0
- package/src/components/index.ts +3 -0
- package/src/icons/Globe/GlobeIcon.tsx +20 -0
- package/src/icons/Globe/GlobeIcon.web.tsx +26 -0
- package/src/icons/Globe/globePath.ts +3 -0
- package/src/icons/Globe/index.ts +2 -0
- package/src/icons/index.ts +1 -0
- package/src/icons/registry.ts +3 -1
- package/src/index.ts +6 -0
- package/src/theme/index.ts +1 -0
- package/src/theme/typography.ts +9 -0
package/package.json
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { View } from "react-native";
|
|
4
|
+
import { LanguageSwitcher } from "./LanguageSwitcher";
|
|
5
|
+
|
|
6
|
+
const meta = {
|
|
7
|
+
title: "Components/LanguageSwitcher",
|
|
8
|
+
component: LanguageSwitcher,
|
|
9
|
+
args: {
|
|
10
|
+
defaultValue: "hy",
|
|
11
|
+
},
|
|
12
|
+
parameters: {
|
|
13
|
+
backgrounds: { default: "black" },
|
|
14
|
+
},
|
|
15
|
+
} satisfies Meta<typeof LanguageSwitcher>;
|
|
16
|
+
|
|
17
|
+
export default meta;
|
|
18
|
+
type Story = StoryObj<typeof meta>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {};
|
|
21
|
+
|
|
22
|
+
export const Open: Story = {
|
|
23
|
+
args: { defaultOpen: true },
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const Disabled: Story = {
|
|
27
|
+
args: { disabled: true },
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function ControlledExample() {
|
|
31
|
+
const [value, setValue] = useState("hy");
|
|
32
|
+
return <LanguageSwitcher value={value} onValueChange={setValue} />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const Controlled: Story = {
|
|
36
|
+
render: () => <ControlledExample />,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const CustomLanguages: Story = {
|
|
40
|
+
render: () => (
|
|
41
|
+
<View style={{ alignItems: "flex-start" }}>
|
|
42
|
+
<LanguageSwitcher
|
|
43
|
+
defaultValue="fr"
|
|
44
|
+
options={[
|
|
45
|
+
{ value: "fr", label: "Fra" },
|
|
46
|
+
{ value: "de", label: "Deu" },
|
|
47
|
+
{ value: "it", label: "Ita" },
|
|
48
|
+
]}
|
|
49
|
+
/>
|
|
50
|
+
</View>
|
|
51
|
+
),
|
|
52
|
+
};
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
forwardRef,
|
|
3
|
+
useCallback,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
useRef,
|
|
7
|
+
useState,
|
|
8
|
+
type ComponentRef,
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
Platform,
|
|
12
|
+
Pressable,
|
|
13
|
+
StyleSheet,
|
|
14
|
+
Text,
|
|
15
|
+
View,
|
|
16
|
+
type PressableProps,
|
|
17
|
+
type StyleProp,
|
|
18
|
+
type ViewProps,
|
|
19
|
+
type ViewStyle,
|
|
20
|
+
} from "react-native";
|
|
21
|
+
import { ChevronDownIcon, GlobeIcon } from "../../icons";
|
|
22
|
+
import { colors, fonts, languageSwitcherTypography } from "../../theme";
|
|
23
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
24
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
25
|
+
|
|
26
|
+
export interface LanguageOption {
|
|
27
|
+
/** Stable locale identifier, such as `hy`, `en`, or `ru`. */
|
|
28
|
+
value: string;
|
|
29
|
+
/** Short label shown in the trigger and menu. */
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface LanguageSwitcherProps extends Omit<ViewProps, "style" | "children"> {
|
|
34
|
+
options?: readonly LanguageOption[];
|
|
35
|
+
value?: string;
|
|
36
|
+
defaultValue?: string;
|
|
37
|
+
onValueChange?: (value: string) => void;
|
|
38
|
+
open?: boolean;
|
|
39
|
+
defaultOpen?: boolean;
|
|
40
|
+
onOpenChange?: (open: boolean) => void;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
accessibilityLabel?: string;
|
|
43
|
+
style?: StyleProp<ViewStyle>;
|
|
44
|
+
className?: string;
|
|
45
|
+
hitSlop?: PressableProps["hitSlop"];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const defaultLanguageOptions = [
|
|
49
|
+
{ value: "hy", label: "Հայ" },
|
|
50
|
+
{ value: "en", label: "Eng" },
|
|
51
|
+
{ value: "ru", label: "Рус" },
|
|
52
|
+
] as const satisfies readonly LanguageOption[];
|
|
53
|
+
|
|
54
|
+
const CONTROL_WIDTH = 90;
|
|
55
|
+
const CONTROL_HEIGHT = 35;
|
|
56
|
+
const ICON_SIZE = 16;
|
|
57
|
+
|
|
58
|
+
export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSwitcherProps>(
|
|
59
|
+
function LanguageSwitcher(
|
|
60
|
+
{
|
|
61
|
+
options = defaultLanguageOptions,
|
|
62
|
+
value,
|
|
63
|
+
defaultValue,
|
|
64
|
+
onValueChange,
|
|
65
|
+
open: openProp,
|
|
66
|
+
defaultOpen = false,
|
|
67
|
+
onOpenChange,
|
|
68
|
+
disabled = false,
|
|
69
|
+
accessibilityLabel = "Language",
|
|
70
|
+
style,
|
|
71
|
+
className,
|
|
72
|
+
hitSlop = 8,
|
|
73
|
+
...rest
|
|
74
|
+
},
|
|
75
|
+
forwardedRef,
|
|
76
|
+
) {
|
|
77
|
+
const wrapperRef = useRef<ComponentRef<typeof View>>(null);
|
|
78
|
+
const triggerRef = useRef<ComponentRef<typeof Pressable>>(null);
|
|
79
|
+
const setWrapperRef = useMemo(() => mergeRefs(wrapperRef, forwardedRef), [forwardedRef]);
|
|
80
|
+
const firstValue = options[0]?.value ?? "";
|
|
81
|
+
const isValueControlled = value !== undefined;
|
|
82
|
+
const isOpenControlled = openProp !== undefined;
|
|
83
|
+
const [uncontrolledValue, setUncontrolledValue] = useState(defaultValue ?? firstValue);
|
|
84
|
+
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
85
|
+
const [triggerHovered, setTriggerHovered] = useState(false);
|
|
86
|
+
const [hoveredValue, setHoveredValue] = useState<string | null>(null);
|
|
87
|
+
const selectedValue = isValueControlled ? value : uncontrolledValue;
|
|
88
|
+
const isOpen = isOpenControlled ? openProp : uncontrolledOpen;
|
|
89
|
+
const selectedOption =
|
|
90
|
+
options.find((option) => option.value === selectedValue) ?? options[0] ?? null;
|
|
91
|
+
|
|
92
|
+
useApplyWebClassName(wrapperRef, className?.trim() || undefined);
|
|
93
|
+
|
|
94
|
+
const setOpen = useCallback(
|
|
95
|
+
(next: boolean) => {
|
|
96
|
+
if (disabled || next === isOpen) return;
|
|
97
|
+
if (!isOpenControlled) setUncontrolledOpen(next);
|
|
98
|
+
onOpenChange?.(next);
|
|
99
|
+
},
|
|
100
|
+
[disabled, isOpen, isOpenControlled, onOpenChange],
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const closeAndFocusTrigger = useCallback(() => {
|
|
104
|
+
setOpen(false);
|
|
105
|
+
const node = triggerRef.current as unknown as { focus?: () => void } | null;
|
|
106
|
+
node?.focus?.();
|
|
107
|
+
}, [setOpen]);
|
|
108
|
+
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
if (Platform.OS !== "web" || !isOpen) return;
|
|
111
|
+
|
|
112
|
+
const handlePointerDown = (event: MouseEvent | TouchEvent) => {
|
|
113
|
+
const wrapper = wrapperRef.current as unknown as {
|
|
114
|
+
contains?: (node: Node) => boolean;
|
|
115
|
+
} | null;
|
|
116
|
+
const target = event.target;
|
|
117
|
+
if (wrapper?.contains && target instanceof Node && !wrapper.contains(target)) {
|
|
118
|
+
setOpen(false);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
122
|
+
if (event.key === "Escape") {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
closeAndFocusTrigger();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
document.addEventListener("mousedown", handlePointerDown);
|
|
129
|
+
document.addEventListener("touchstart", handlePointerDown);
|
|
130
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
131
|
+
return () => {
|
|
132
|
+
document.removeEventListener("mousedown", handlePointerDown);
|
|
133
|
+
document.removeEventListener("touchstart", handlePointerDown);
|
|
134
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
135
|
+
};
|
|
136
|
+
}, [closeAndFocusTrigger, isOpen, setOpen]);
|
|
137
|
+
|
|
138
|
+
const selectLanguage = (nextValue: string) => {
|
|
139
|
+
if (nextValue !== selectedValue) {
|
|
140
|
+
if (!isValueControlled) setUncontrolledValue(nextValue);
|
|
141
|
+
onValueChange?.(nextValue);
|
|
142
|
+
}
|
|
143
|
+
closeAndFocusTrigger();
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<View
|
|
148
|
+
{...rest}
|
|
149
|
+
ref={setWrapperRef}
|
|
150
|
+
style={[styles.wrapper, isOpen && styles.wrapperOpen, disabled && styles.disabled, style]}
|
|
151
|
+
>
|
|
152
|
+
<Pressable
|
|
153
|
+
ref={triggerRef}
|
|
154
|
+
onPress={() => setOpen(!isOpen)}
|
|
155
|
+
disabled={disabled || options.length === 0}
|
|
156
|
+
hitSlop={hitSlop}
|
|
157
|
+
accessibilityRole="button"
|
|
158
|
+
accessibilityLabel={accessibilityLabel}
|
|
159
|
+
accessibilityState={{ disabled, expanded: isOpen }}
|
|
160
|
+
onHoverIn={() => setTriggerHovered(true)}
|
|
161
|
+
onHoverOut={() => setTriggerHovered(false)}
|
|
162
|
+
style={({ pressed }) => [
|
|
163
|
+
styles.trigger,
|
|
164
|
+
(triggerHovered || pressed || isOpen) && styles.triggerActive,
|
|
165
|
+
]}
|
|
166
|
+
>
|
|
167
|
+
<GlobeIcon size={ICON_SIZE} color={colors.white} strokeWidth={2} />
|
|
168
|
+
<Text style={styles.triggerLabel} numberOfLines={1}>
|
|
169
|
+
{selectedOption?.label ?? ""}
|
|
170
|
+
</Text>
|
|
171
|
+
<View style={[styles.chevron, isOpen && styles.chevronOpen]}>
|
|
172
|
+
<ChevronDownIcon size={ICON_SIZE} color={colors.white} strokeWidth={2} />
|
|
173
|
+
</View>
|
|
174
|
+
</Pressable>
|
|
175
|
+
|
|
176
|
+
{isOpen && options.length > 0 ? (
|
|
177
|
+
<View style={styles.menu} accessibilityRole="menu">
|
|
178
|
+
{options.map((option) => {
|
|
179
|
+
const selected = option.value === selectedValue;
|
|
180
|
+
return (
|
|
181
|
+
<Pressable
|
|
182
|
+
key={option.value}
|
|
183
|
+
onPress={() => selectLanguage(option.value)}
|
|
184
|
+
accessibilityRole="menuitem"
|
|
185
|
+
accessibilityLabel={option.label}
|
|
186
|
+
accessibilityState={{ selected }}
|
|
187
|
+
onHoverIn={() => setHoveredValue(option.value)}
|
|
188
|
+
onHoverOut={() => setHoveredValue(null)}
|
|
189
|
+
style={({ pressed }) => [
|
|
190
|
+
styles.item,
|
|
191
|
+
selected
|
|
192
|
+
? styles.itemSelected
|
|
193
|
+
: (hoveredValue === option.value || pressed) && styles.itemHovered,
|
|
194
|
+
]}
|
|
195
|
+
>
|
|
196
|
+
<Text style={[styles.itemLabel, selected && styles.itemLabelSelected]}>
|
|
197
|
+
{option.label}
|
|
198
|
+
</Text>
|
|
199
|
+
</Pressable>
|
|
200
|
+
);
|
|
201
|
+
})}
|
|
202
|
+
</View>
|
|
203
|
+
) : null}
|
|
204
|
+
</View>
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
const styles = StyleSheet.create({
|
|
210
|
+
wrapper: {
|
|
211
|
+
position: "relative",
|
|
212
|
+
width: CONTROL_WIDTH,
|
|
213
|
+
alignSelf: "flex-start",
|
|
214
|
+
},
|
|
215
|
+
wrapperOpen: {
|
|
216
|
+
zIndex: 20,
|
|
217
|
+
},
|
|
218
|
+
disabled: {
|
|
219
|
+
opacity: 0.6,
|
|
220
|
+
},
|
|
221
|
+
trigger: {
|
|
222
|
+
width: CONTROL_WIDTH,
|
|
223
|
+
height: CONTROL_HEIGHT,
|
|
224
|
+
flexDirection: "row",
|
|
225
|
+
alignItems: "center",
|
|
226
|
+
gap: 8,
|
|
227
|
+
padding: 8,
|
|
228
|
+
borderRadius: 12,
|
|
229
|
+
backgroundColor: "transparent",
|
|
230
|
+
},
|
|
231
|
+
triggerActive: {
|
|
232
|
+
backgroundColor: colors.grey700,
|
|
233
|
+
},
|
|
234
|
+
triggerLabel: {
|
|
235
|
+
...languageSwitcherTypography,
|
|
236
|
+
...Platform.select({ web: { fontFamily: `${fonts.sans}, sans-serif` } }),
|
|
237
|
+
minWidth: 0,
|
|
238
|
+
flex: 1,
|
|
239
|
+
color: colors.white,
|
|
240
|
+
},
|
|
241
|
+
chevron: {
|
|
242
|
+
width: ICON_SIZE,
|
|
243
|
+
height: ICON_SIZE,
|
|
244
|
+
flexShrink: 0,
|
|
245
|
+
alignItems: "center",
|
|
246
|
+
justifyContent: "center",
|
|
247
|
+
},
|
|
248
|
+
chevronOpen: {
|
|
249
|
+
transform: [{ rotate: "180deg" }],
|
|
250
|
+
},
|
|
251
|
+
menu: {
|
|
252
|
+
position: "absolute",
|
|
253
|
+
top: CONTROL_HEIGHT + 8,
|
|
254
|
+
left: 0,
|
|
255
|
+
width: CONTROL_WIDTH,
|
|
256
|
+
padding: 8,
|
|
257
|
+
gap: 8,
|
|
258
|
+
borderRadius: 12,
|
|
259
|
+
backgroundColor: colors.grey700,
|
|
260
|
+
...Platform.select({
|
|
261
|
+
web: { boxShadow: `0 8px 24px ${colors.black}73` },
|
|
262
|
+
default: { elevation: 8 },
|
|
263
|
+
}),
|
|
264
|
+
},
|
|
265
|
+
item: {
|
|
266
|
+
height: CONTROL_HEIGHT,
|
|
267
|
+
alignItems: "center",
|
|
268
|
+
justifyContent: "center",
|
|
269
|
+
paddingVertical: 8,
|
|
270
|
+
paddingHorizontal: 12,
|
|
271
|
+
borderRadius: 8,
|
|
272
|
+
},
|
|
273
|
+
itemHovered: {
|
|
274
|
+
backgroundColor: colors.whiteAlpha5,
|
|
275
|
+
},
|
|
276
|
+
itemSelected: {
|
|
277
|
+
backgroundColor: colors.primaryMuted,
|
|
278
|
+
},
|
|
279
|
+
itemLabel: {
|
|
280
|
+
...languageSwitcherTypography,
|
|
281
|
+
...Platform.select({ web: { fontFamily: `${fonts.sans}, sans-serif` } }),
|
|
282
|
+
color: colors.white,
|
|
283
|
+
},
|
|
284
|
+
itemLabelSelected: {
|
|
285
|
+
color: colors.primary,
|
|
286
|
+
},
|
|
287
|
+
});
|
package/src/components/index.ts
CHANGED
|
@@ -10,6 +10,9 @@ export type { CounterProps } from "./Counter";
|
|
|
10
10
|
export { SegmentedToggle } from "./SegmentedToggle";
|
|
11
11
|
export type { SegmentedToggleOption, SegmentedToggleProps } from "./SegmentedToggle";
|
|
12
12
|
|
|
13
|
+
export { LanguageSwitcher, defaultLanguageOptions } from "./LanguageSwitcher";
|
|
14
|
+
export type { LanguageOption, LanguageSwitcherProps } from "./LanguageSwitcher";
|
|
15
|
+
|
|
13
16
|
export { StatCard } from "./StatCard";
|
|
14
17
|
export type { StatCardProps } from "./StatCard";
|
|
15
18
|
|
|
@@ -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 { GLOBE_PATH } from "./globePath";
|
|
5
|
+
|
|
6
|
+
export function GlobeIcon({ size = 20, color = colors.primary, strokeWidth = 2 }: IconProps) {
|
|
7
|
+
return (
|
|
8
|
+
<Svg width={size} height={size} viewBox="0 0 20 20" fill="none">
|
|
9
|
+
<Path
|
|
10
|
+
d={GLOBE_PATH}
|
|
11
|
+
stroke={color}
|
|
12
|
+
strokeWidth={strokeWidth}
|
|
13
|
+
strokeLinecap="round"
|
|
14
|
+
strokeLinejoin="round"
|
|
15
|
+
/>
|
|
16
|
+
</Svg>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { GLOBE_PATH } from "./globePath";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { colors } from "../../theme";
|
|
2
|
+
import type { IconProps } from "../types";
|
|
3
|
+
import { GLOBE_PATH } from "./globePath";
|
|
4
|
+
|
|
5
|
+
export function GlobeIcon({ size = 20, color = colors.primary, strokeWidth = 2 }: IconProps) {
|
|
6
|
+
return (
|
|
7
|
+
<svg
|
|
8
|
+
width={size}
|
|
9
|
+
height={size}
|
|
10
|
+
viewBox="0 0 20 20"
|
|
11
|
+
fill="none"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
aria-hidden
|
|
14
|
+
>
|
|
15
|
+
<path
|
|
16
|
+
d={GLOBE_PATH}
|
|
17
|
+
stroke={color}
|
|
18
|
+
strokeWidth={strokeWidth}
|
|
19
|
+
strokeLinecap="round"
|
|
20
|
+
strokeLinejoin="round"
|
|
21
|
+
/>
|
|
22
|
+
</svg>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { GLOBE_PATH } from "./globePath";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
/** Globe paths — 20×20 viewBox. */
|
|
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";
|
package/src/icons/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ export { ChevronLeftIcon } from "./ChevronLeft";
|
|
|
19
19
|
export { ClockIcon } from "./Clock";
|
|
20
20
|
export { FacebookIcon } from "./Facebook";
|
|
21
21
|
export { HeartIcon } from "./Heart";
|
|
22
|
+
export { GlobeIcon } from "./Globe";
|
|
22
23
|
export { HelpCircleIcon } from "./HelpCircle";
|
|
23
24
|
export { HomeIcon } from "./Home";
|
|
24
25
|
export { InstagramIcon } from "./Instagram";
|
package/src/icons/registry.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { ChevronLeftIcon } from "./ChevronLeft";
|
|
|
12
12
|
import { ClockIcon } from "./Clock";
|
|
13
13
|
import { FacebookIcon } from "./Facebook";
|
|
14
14
|
import { HeartIcon } from "./Heart";
|
|
15
|
+
import { GlobeIcon } from "./Globe";
|
|
15
16
|
import { HelpCircleIcon } from "./HelpCircle";
|
|
16
17
|
import { HomeIcon } from "./Home";
|
|
17
18
|
import { InstagramIcon } from "./Instagram";
|
|
@@ -50,6 +51,7 @@ export const icons = {
|
|
|
50
51
|
clock: ClockIcon,
|
|
51
52
|
facebook: FacebookIcon,
|
|
52
53
|
heart: HeartIcon,
|
|
54
|
+
globe: GlobeIcon,
|
|
53
55
|
helpCircle: HelpCircleIcon,
|
|
54
56
|
home: HomeIcon,
|
|
55
57
|
instagram: InstagramIcon,
|
|
@@ -98,7 +100,7 @@ export const iconGroups = {
|
|
|
98
100
|
objects: ["bag", "building", "clock", "user", "usersAlt", "video"],
|
|
99
101
|
communication: ["mail", "phone"],
|
|
100
102
|
social: ["facebook", "instagram", "linkedin"],
|
|
101
|
-
interface: ["layoutGrid", "sidebar", "settings", "helpCircle", "logOut"],
|
|
103
|
+
interface: ["globe", "layoutGrid", "sidebar", "settings", "helpCircle", "logOut"],
|
|
102
104
|
} as const satisfies Record<string, readonly IconName[]>;
|
|
103
105
|
|
|
104
106
|
export type IconGroup = keyof typeof iconGroups;
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,8 @@ export {
|
|
|
3
3
|
Badge,
|
|
4
4
|
Counter,
|
|
5
5
|
SegmentedToggle,
|
|
6
|
+
LanguageSwitcher,
|
|
7
|
+
defaultLanguageOptions,
|
|
6
8
|
StatCard,
|
|
7
9
|
Button,
|
|
8
10
|
Checkbox,
|
|
@@ -22,6 +24,8 @@ export type {
|
|
|
22
24
|
CounterProps,
|
|
23
25
|
SegmentedToggleOption,
|
|
24
26
|
SegmentedToggleProps,
|
|
27
|
+
LanguageOption,
|
|
28
|
+
LanguageSwitcherProps,
|
|
25
29
|
StatCardProps,
|
|
26
30
|
ButtonProps,
|
|
27
31
|
ButtonSize,
|
|
@@ -68,6 +72,7 @@ export {
|
|
|
68
72
|
ClockIcon,
|
|
69
73
|
FacebookIcon,
|
|
70
74
|
HeartIcon,
|
|
75
|
+
GlobeIcon,
|
|
71
76
|
HelpCircleIcon,
|
|
72
77
|
HomeIcon,
|
|
73
78
|
InstagramIcon,
|
|
@@ -106,6 +111,7 @@ export {
|
|
|
106
111
|
badgeTypography,
|
|
107
112
|
statCardTypography,
|
|
108
113
|
segmentedToggleTypography,
|
|
114
|
+
languageSwitcherTypography,
|
|
109
115
|
counterTypography,
|
|
110
116
|
ratingTypography,
|
|
111
117
|
commentCardTypography,
|
package/src/theme/index.ts
CHANGED
package/src/theme/typography.ts
CHANGED
|
@@ -47,6 +47,15 @@ export const segmentedToggleTypography = {
|
|
|
47
47
|
letterSpacing: 0,
|
|
48
48
|
} as const;
|
|
49
49
|
|
|
50
|
+
/** Language switcher trigger and option labels — SemiBold 14. */
|
|
51
|
+
export const languageSwitcherTypography = {
|
|
52
|
+
fontFamily: fonts.sans,
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
fontWeight: "600" as const,
|
|
55
|
+
lineHeight: 19,
|
|
56
|
+
letterSpacing: 0,
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
50
59
|
/** Counter value — Bold 14, line-height 100%, letter-spacing 0. */
|
|
51
60
|
export const counterTypography = {
|
|
52
61
|
fontFamily: fonts.sans,
|