@ecohouse/ui 0.1.53 → 0.1.55
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/dist/index.cjs +267 -128
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +267 -128
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.stories.tsx +8 -0
- package/src/components/Avatar/Avatar.tsx +103 -8
- package/src/components/Avatar/index.ts +1 -1
- package/src/components/LanguageSwitcher/LanguageSwitcher.tsx +45 -25
- package/src/components/Select/Select.tsx +163 -105
- package/src/components/index.ts +1 -1
- package/src/index.ts +1 -0
- package/src/theme/colors.test.ts +2 -1
- package/src/theme/colors.ts +2 -1
package/package.json
CHANGED
|
@@ -26,6 +26,7 @@ const accountMenuItems: AvatarMenuItem[] = [
|
|
|
26
26
|
onPress: fn(),
|
|
27
27
|
backgroundColor: colors.redMuted,
|
|
28
28
|
hoverColor: colors.redHover,
|
|
29
|
+
labelStyle: { color: colors.red },
|
|
29
30
|
},
|
|
30
31
|
];
|
|
31
32
|
|
|
@@ -37,12 +38,14 @@ const meta = {
|
|
|
37
38
|
email: "anna.hakobyan@example.com",
|
|
38
39
|
imageUrl: "https://i.pravatar.cc/150?img=47",
|
|
39
40
|
showChevron: true,
|
|
41
|
+
variant: "light",
|
|
40
42
|
disabled: false,
|
|
41
43
|
onPress: fn(),
|
|
42
44
|
},
|
|
43
45
|
argTypes: {
|
|
44
46
|
disabled: { control: "boolean" },
|
|
45
47
|
showChevron: { control: "boolean" },
|
|
48
|
+
variant: { control: "inline-radio", options: ["light", "dark"] },
|
|
46
49
|
onPress: { action: "pressed" },
|
|
47
50
|
},
|
|
48
51
|
parameters: {
|
|
@@ -55,6 +58,11 @@ type Story = StoryObj<typeof meta>;
|
|
|
55
58
|
|
|
56
59
|
export const Default: Story = {};
|
|
57
60
|
|
|
61
|
+
/** Original solid avatar design, preserved through the `dark` variant. */
|
|
62
|
+
export const Dark: Story = {
|
|
63
|
+
args: { variant: "dark" },
|
|
64
|
+
};
|
|
65
|
+
|
|
58
66
|
/** No `imageUrl` — falls back to the placeholder person icon. */
|
|
59
67
|
export const WithoutImage: Story = {
|
|
60
68
|
args: { imageUrl: undefined },
|
|
@@ -28,13 +28,15 @@ import { MenuItem, type MenuItemProps } from "../../primitives";
|
|
|
28
28
|
/** Data shape for an Avatar dropdown option — same fields as `MenuItem`. */
|
|
29
29
|
export type AvatarMenuItem = Pick<
|
|
30
30
|
MenuItemProps,
|
|
31
|
-
"icon" | "label" | "disabled" | "backgroundColor" | "hoverColor"
|
|
31
|
+
"icon" | "label" | "disabled" | "backgroundColor" | "hoverColor" | "labelStyle"
|
|
32
32
|
> & {
|
|
33
33
|
/** Stable identity for React keys; falls back to `label`. */
|
|
34
34
|
key?: string;
|
|
35
35
|
onPress: () => void;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
export type AvatarVariant = "light" | "dark";
|
|
39
|
+
|
|
38
40
|
export interface AvatarProps extends Omit<
|
|
39
41
|
PressableProps,
|
|
40
42
|
"onPress" | "disabled" | "style" | "children" | "hitSlop"
|
|
@@ -51,6 +53,8 @@ export interface AvatarProps extends Omit<
|
|
|
51
53
|
disabled?: boolean;
|
|
52
54
|
/** Trailing chevron, typically used to hint a dropdown/menu. */
|
|
53
55
|
showChevron?: boolean;
|
|
56
|
+
/** Visual treatment. `light` is the translucent header design; `dark` preserves the original solid design. */
|
|
57
|
+
variant?: AvatarVariant;
|
|
54
58
|
/**
|
|
55
59
|
* Account-menu options, supplied entirely by the consumer (icon, label,
|
|
56
60
|
* action, and per-item colors). When set, pressing the chip opens a
|
|
@@ -69,11 +73,28 @@ export interface AvatarProps extends Omit<
|
|
|
69
73
|
}
|
|
70
74
|
|
|
71
75
|
const IMAGE_SIZE = 48;
|
|
76
|
+
const CONTAINER_HEIGHT = 56;
|
|
72
77
|
const CONTAINER_RADIUS = 71;
|
|
73
78
|
const CHEVRON_SIZE = 20;
|
|
74
79
|
const FALLBACK_ICON_SIZE = 20;
|
|
75
80
|
const MENU_RADIUS = 16;
|
|
76
81
|
|
|
82
|
+
const triggerWebBlurStyle: ViewStyle =
|
|
83
|
+
Platform.OS === "web"
|
|
84
|
+
? ({
|
|
85
|
+
backdropFilter: "blur(40px)",
|
|
86
|
+
WebkitBackdropFilter: "blur(40px)",
|
|
87
|
+
} as ViewStyle)
|
|
88
|
+
: {};
|
|
89
|
+
|
|
90
|
+
const menuWebBlurStyle: ViewStyle =
|
|
91
|
+
Platform.OS === "web"
|
|
92
|
+
? ({
|
|
93
|
+
backdropFilter: "blur(40px)",
|
|
94
|
+
WebkitBackdropFilter: "blur(40px)",
|
|
95
|
+
} as ViewStyle)
|
|
96
|
+
: {};
|
|
97
|
+
|
|
77
98
|
export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(function Avatar(
|
|
78
99
|
{
|
|
79
100
|
name,
|
|
@@ -82,6 +103,7 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
82
103
|
onPress,
|
|
83
104
|
disabled = false,
|
|
84
105
|
showChevron = true,
|
|
106
|
+
variant = "light",
|
|
85
107
|
menuItems,
|
|
86
108
|
open: openProp,
|
|
87
109
|
defaultOpen = false,
|
|
@@ -106,6 +128,7 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
106
128
|
const isOpenControlled = typeof openProp === "boolean";
|
|
107
129
|
const [uncontrolledOpen, setUncontrolledOpen] = useState(defaultOpen);
|
|
108
130
|
const isOpen = hasMenu && (isOpenControlled ? openProp : uncontrolledOpen);
|
|
131
|
+
const isLightVariant = variant === "light";
|
|
109
132
|
|
|
110
133
|
useApplyWebClassName(containerRef, className);
|
|
111
134
|
|
|
@@ -194,7 +217,13 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
194
217
|
accessibilityState={
|
|
195
218
|
isInteractive ? { disabled, expanded: hasMenu ? isOpen : undefined } : undefined
|
|
196
219
|
}
|
|
197
|
-
style={[
|
|
220
|
+
style={[
|
|
221
|
+
styles.root,
|
|
222
|
+
isLightVariant ? styles.rootLight : styles.rootDark,
|
|
223
|
+
isLightVariant && triggerWebBlurStyle,
|
|
224
|
+
disabled && styles.disabled,
|
|
225
|
+
style,
|
|
226
|
+
]}
|
|
198
227
|
>
|
|
199
228
|
<View style={styles.imageWrap}>
|
|
200
229
|
{hasImage ? (
|
|
@@ -204,8 +233,16 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
204
233
|
onError={() => setImageFailed(true)}
|
|
205
234
|
/>
|
|
206
235
|
) : (
|
|
207
|
-
<View
|
|
208
|
-
|
|
236
|
+
<View
|
|
237
|
+
style={[
|
|
238
|
+
styles.imageFallback,
|
|
239
|
+
isLightVariant ? styles.imageFallbackLight : styles.imageFallbackDark,
|
|
240
|
+
]}
|
|
241
|
+
>
|
|
242
|
+
<UserIcon
|
|
243
|
+
size={FALLBACK_ICON_SIZE}
|
|
244
|
+
color={isLightVariant ? colors.white : colors.primary}
|
|
245
|
+
/>
|
|
209
246
|
</View>
|
|
210
247
|
)}
|
|
211
248
|
</View>
|
|
@@ -215,7 +252,10 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
215
252
|
{name}
|
|
216
253
|
</Text>
|
|
217
254
|
{email ? (
|
|
218
|
-
<Text
|
|
255
|
+
<Text
|
|
256
|
+
style={[styles.email, isLightVariant ? styles.emailLight : styles.emailDark]}
|
|
257
|
+
numberOfLines={1}
|
|
258
|
+
>
|
|
219
259
|
{email}
|
|
220
260
|
</Text>
|
|
221
261
|
) : null}
|
|
@@ -223,13 +263,23 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
223
263
|
|
|
224
264
|
{showChevron ? (
|
|
225
265
|
<View style={[styles.chevronSlot, isOpen && styles.chevronOpen]}>
|
|
226
|
-
<ChevronDownIcon
|
|
266
|
+
<ChevronDownIcon
|
|
267
|
+
size={CHEVRON_SIZE}
|
|
268
|
+
color={isLightVariant ? colors.white : isOpen ? colors.primary : colors.grey100}
|
|
269
|
+
/>
|
|
227
270
|
</View>
|
|
228
271
|
) : null}
|
|
229
272
|
</Pressable>
|
|
230
273
|
|
|
231
274
|
{hasMenu && isOpen ? (
|
|
232
|
-
<View
|
|
275
|
+
<View
|
|
276
|
+
style={[
|
|
277
|
+
styles.menu,
|
|
278
|
+
isLightVariant ? styles.menuLight : styles.menuDark,
|
|
279
|
+
isLightVariant && menuWebBlurStyle,
|
|
280
|
+
menuWidth != null && { right: undefined, width: menuWidth },
|
|
281
|
+
]}
|
|
282
|
+
>
|
|
233
283
|
{resolvedMenuItems.map((item) => {
|
|
234
284
|
const itemKey = item.key ?? item.label;
|
|
235
285
|
return (
|
|
@@ -239,8 +289,16 @@ export const Avatar = forwardRef<ComponentRef<typeof View>, AvatarProps>(functio
|
|
|
239
289
|
label={item.label}
|
|
240
290
|
disabled={item.disabled}
|
|
241
291
|
backgroundColor={item.backgroundColor}
|
|
242
|
-
hoverColor={item.hoverColor}
|
|
292
|
+
hoverColor={item.hoverColor ?? (isLightVariant ? colors.whiteAlpha7 : undefined)}
|
|
293
|
+
labelStyle={item.labelStyle}
|
|
243
294
|
onPress={() => handleItemPress(item)}
|
|
295
|
+
style={
|
|
296
|
+
isLightVariant
|
|
297
|
+
? item.backgroundColor
|
|
298
|
+
? styles.emphasizedMenuItem
|
|
299
|
+
: styles.menuItem
|
|
300
|
+
: undefined
|
|
301
|
+
}
|
|
244
302
|
/>
|
|
245
303
|
);
|
|
246
304
|
})}
|
|
@@ -259,6 +317,7 @@ const styles = StyleSheet.create({
|
|
|
259
317
|
zIndex: 20,
|
|
260
318
|
},
|
|
261
319
|
root: {
|
|
320
|
+
height: CONTAINER_HEIGHT,
|
|
262
321
|
flexDirection: "row",
|
|
263
322
|
alignItems: "center",
|
|
264
323
|
alignSelf: "flex-start",
|
|
@@ -268,6 +327,11 @@ const styles = StyleSheet.create({
|
|
|
268
327
|
paddingLeft: 4,
|
|
269
328
|
paddingRight: 12,
|
|
270
329
|
borderRadius: CONTAINER_RADIUS,
|
|
330
|
+
},
|
|
331
|
+
rootLight: {
|
|
332
|
+
backgroundColor: colors.whiteAlpha5,
|
|
333
|
+
},
|
|
334
|
+
rootDark: {
|
|
271
335
|
backgroundColor: colors.grey700,
|
|
272
336
|
},
|
|
273
337
|
disabled: {
|
|
@@ -289,6 +353,14 @@ const styles = StyleSheet.create({
|
|
|
289
353
|
height: IMAGE_SIZE,
|
|
290
354
|
alignItems: "center",
|
|
291
355
|
justifyContent: "center",
|
|
356
|
+
},
|
|
357
|
+
imageFallbackLight: {
|
|
358
|
+
borderRadius: IMAGE_SIZE / 2,
|
|
359
|
+
backgroundColor: "transparent",
|
|
360
|
+
borderWidth: 1,
|
|
361
|
+
borderColor: colors.whiteAlpha10,
|
|
362
|
+
},
|
|
363
|
+
imageFallbackDark: {
|
|
292
364
|
backgroundColor: colors.grey600,
|
|
293
365
|
},
|
|
294
366
|
textColumn: {
|
|
@@ -302,6 +374,11 @@ const styles = StyleSheet.create({
|
|
|
302
374
|
},
|
|
303
375
|
email: {
|
|
304
376
|
...avatarTypography.email,
|
|
377
|
+
},
|
|
378
|
+
emailLight: {
|
|
379
|
+
color: colors.whiteAlpha60,
|
|
380
|
+
},
|
|
381
|
+
emailDark: {
|
|
305
382
|
color: colors.grey0,
|
|
306
383
|
},
|
|
307
384
|
chevronSlot: {
|
|
@@ -324,6 +401,14 @@ const styles = StyleSheet.create({
|
|
|
324
401
|
borderRadius: MENU_RADIUS,
|
|
325
402
|
padding: 8,
|
|
326
403
|
gap: 8,
|
|
404
|
+
},
|
|
405
|
+
menuLight: {
|
|
406
|
+
borderWidth: 1,
|
|
407
|
+
borderColor: colors.whiteAlpha10,
|
|
408
|
+
backgroundColor: colors.whiteAlpha5,
|
|
409
|
+
overflow: "hidden",
|
|
410
|
+
},
|
|
411
|
+
menuDark: {
|
|
327
412
|
backgroundColor: colors.grey700,
|
|
328
413
|
...Platform.select({
|
|
329
414
|
web: {
|
|
@@ -334,4 +419,14 @@ const styles = StyleSheet.create({
|
|
|
334
419
|
},
|
|
335
420
|
}),
|
|
336
421
|
},
|
|
422
|
+
menuItem: {
|
|
423
|
+
height: 32,
|
|
424
|
+
minHeight: 32,
|
|
425
|
+
paddingVertical: 6,
|
|
426
|
+
},
|
|
427
|
+
emphasizedMenuItem: {
|
|
428
|
+
height: 46,
|
|
429
|
+
minHeight: 46,
|
|
430
|
+
paddingVertical: 11,
|
|
431
|
+
},
|
|
337
432
|
});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { Avatar } from "./Avatar";
|
|
2
|
-
export type { AvatarProps, AvatarMenuItem } from "./Avatar";
|
|
2
|
+
export type { AvatarProps, AvatarMenuItem, AvatarVariant } from "./Avatar";
|
|
@@ -60,15 +60,24 @@ export const defaultLanguageOptions = [
|
|
|
60
60
|
|
|
61
61
|
const CONTROL_WIDTH = 96;
|
|
62
62
|
const CONTROL_HEIGHT = 35;
|
|
63
|
+
const MENU_WIDTH = 90;
|
|
63
64
|
const ICON_SIZE = 16;
|
|
64
65
|
const ICON_BUTTON_SIZE = 32;
|
|
65
66
|
const ICON_MENU_WIDTH = 67;
|
|
66
67
|
const ICON_MENU_HEIGHT = 129;
|
|
67
68
|
const ICON_MENU_TOP = 39;
|
|
68
69
|
const ITEM_HEIGHT = 35;
|
|
69
|
-
/** Active option wash
|
|
70
|
+
/** Active option wash for the compact account-header variant. */
|
|
70
71
|
const ACTIVE_ITEM_BACKGROUND = "#E38E5E12";
|
|
71
72
|
|
|
73
|
+
const menuWebBlurStyle: ViewStyle =
|
|
74
|
+
Platform.OS === "web"
|
|
75
|
+
? ({
|
|
76
|
+
backdropFilter: "blur(40px)",
|
|
77
|
+
WebkitBackdropFilter: "blur(40px)",
|
|
78
|
+
} as ViewStyle)
|
|
79
|
+
: {};
|
|
80
|
+
|
|
72
81
|
export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSwitcherProps>(
|
|
73
82
|
function LanguageSwitcher(
|
|
74
83
|
{
|
|
@@ -184,7 +193,7 @@ export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSw
|
|
|
184
193
|
onHoverOut={() => setTriggerHovered(false)}
|
|
185
194
|
style={({ pressed }) => [
|
|
186
195
|
isIconVariant ? styles.triggerIcon : styles.trigger,
|
|
187
|
-
!isIconVariant && (triggerHovered || pressed
|
|
196
|
+
!isIconVariant && (triggerHovered || pressed) && styles.triggerActive,
|
|
188
197
|
]}
|
|
189
198
|
>
|
|
190
199
|
<GlobeIcon size={ICON_SIZE} color={colors.white} strokeWidth={isIconVariant ? 1.5 : 2} />
|
|
@@ -202,7 +211,11 @@ export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSw
|
|
|
202
211
|
|
|
203
212
|
{isOpen && options.length > 0 ? (
|
|
204
213
|
<View
|
|
205
|
-
style={[
|
|
214
|
+
style={[
|
|
215
|
+
styles.menu,
|
|
216
|
+
!isIconVariant && menuWebBlurStyle,
|
|
217
|
+
isIconVariant ? styles.menuIcon : styles.menuDefault,
|
|
218
|
+
]}
|
|
206
219
|
accessibilityRole="menu"
|
|
207
220
|
>
|
|
208
221
|
{options.map((option) => {
|
|
@@ -218,7 +231,7 @@ export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSw
|
|
|
218
231
|
onHoverOut={() => setHoveredValue(null)}
|
|
219
232
|
style={({ pressed }) => [
|
|
220
233
|
styles.item,
|
|
221
|
-
isIconVariant ? styles.itemIcon :
|
|
234
|
+
isIconVariant ? styles.itemIcon : styles.itemDefault,
|
|
222
235
|
selected
|
|
223
236
|
? isIconVariant
|
|
224
237
|
? styles.itemSelectedIcon
|
|
@@ -226,15 +239,17 @@ export const LanguageSwitcher = forwardRef<ComponentRef<typeof View>, LanguageSw
|
|
|
226
239
|
: (hoveredValue === option.value || pressed) && styles.itemHovered,
|
|
227
240
|
]}
|
|
228
241
|
>
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
242
|
+
{({ pressed }) => (
|
|
243
|
+
<Text
|
|
244
|
+
style={[
|
|
245
|
+
styles.itemLabel,
|
|
246
|
+
(selected || hoveredValue === option.value || pressed) &&
|
|
247
|
+
styles.itemLabelActive,
|
|
248
|
+
]}
|
|
249
|
+
>
|
|
250
|
+
{option.label}
|
|
251
|
+
</Text>
|
|
252
|
+
)}
|
|
238
253
|
</Pressable>
|
|
239
254
|
);
|
|
240
255
|
})}
|
|
@@ -314,10 +329,14 @@ const styles = StyleSheet.create({
|
|
|
314
329
|
},
|
|
315
330
|
menuDefault: {
|
|
316
331
|
top: CONTROL_HEIGHT + 8,
|
|
317
|
-
left:
|
|
318
|
-
width:
|
|
319
|
-
|
|
320
|
-
|
|
332
|
+
left: (CONTROL_WIDTH - MENU_WIDTH) / 2,
|
|
333
|
+
width: MENU_WIDTH,
|
|
334
|
+
height: 129,
|
|
335
|
+
gap: 4,
|
|
336
|
+
backgroundColor: colors.whiteAlpha5,
|
|
337
|
+
borderWidth: 1,
|
|
338
|
+
borderColor: colors.whiteAlpha10,
|
|
339
|
+
borderRadius: 16,
|
|
321
340
|
},
|
|
322
341
|
menuIcon: {
|
|
323
342
|
top: ICON_MENU_TOP,
|
|
@@ -345,11 +364,15 @@ const styles = StyleSheet.create({
|
|
|
345
364
|
width: 51,
|
|
346
365
|
alignSelf: "stretch",
|
|
347
366
|
},
|
|
367
|
+
itemDefault: {
|
|
368
|
+
width: 74,
|
|
369
|
+
alignSelf: "center",
|
|
370
|
+
},
|
|
348
371
|
itemHovered: {
|
|
349
|
-
backgroundColor: colors.
|
|
372
|
+
backgroundColor: colors.whiteAlpha7,
|
|
350
373
|
},
|
|
351
374
|
itemSelected: {
|
|
352
|
-
backgroundColor: colors.
|
|
375
|
+
backgroundColor: colors.whiteAlpha7,
|
|
353
376
|
},
|
|
354
377
|
itemSelectedIcon: {
|
|
355
378
|
backgroundColor: ACTIVE_ITEM_BACKGROUND,
|
|
@@ -357,12 +380,9 @@ const styles = StyleSheet.create({
|
|
|
357
380
|
itemLabel: {
|
|
358
381
|
...languageSwitcherTypography,
|
|
359
382
|
...Platform.select({ web: { fontFamily: `${fonts.sans}, sans-serif` } }),
|
|
360
|
-
color: colors.
|
|
361
|
-
},
|
|
362
|
-
itemLabelSelected: {
|
|
363
|
-
color: colors.primary,
|
|
383
|
+
color: colors.whiteAlpha60,
|
|
364
384
|
},
|
|
365
|
-
|
|
366
|
-
color: colors.
|
|
385
|
+
itemLabelActive: {
|
|
386
|
+
color: colors.white,
|
|
367
387
|
},
|
|
368
388
|
});
|