@ecohouse/ui 0.1.4 → 0.1.6
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 +66 -15
- package/dist/index.cjs +1293 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +358 -154
- package/dist/index.d.ts +358 -154
- package/dist/index.js +1278 -149
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Avatar/Avatar.tsx +19 -71
- package/src/components/Badge/Badge.stories.tsx +52 -0
- package/src/components/Badge/Badge.tsx +131 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/Counter/Counter.stories.tsx +46 -0
- package/src/components/Counter/Counter.tsx +135 -0
- package/src/components/Counter/index.ts +2 -0
- package/src/components/DatePicker/DatePicker.stories.tsx +172 -0
- package/src/components/DatePicker/DatePicker.tsx +628 -0
- package/src/components/DatePicker/index.ts +7 -0
- package/src/components/SegmentedToggle/SegmentedToggle.stories.tsx +63 -0
- package/src/components/SegmentedToggle/SegmentedToggle.tsx +221 -0
- package/src/components/SegmentedToggle/index.ts +2 -0
- package/src/components/StatCard/StatCard.stories.tsx +20 -0
- package/src/components/StatCard/StatCard.tsx +61 -0
- package/src/components/StatCard/index.ts +2 -0
- package/src/components/index.ts +20 -0
- package/src/icons/CalendarOutline/CalendarOutlineIcon.tsx +25 -0
- package/src/icons/CalendarOutline/CalendarOutlineIcon.web.tsx +31 -0
- package/src/icons/CalendarOutline/calendarOutlinePath.ts +3 -0
- package/src/icons/CalendarOutline/index.ts +1 -0
- package/src/icons/ChevronLeft/ChevronLeftIcon.tsx +21 -0
- package/src/icons/ChevronLeft/ChevronLeftIcon.web.tsx +27 -0
- package/src/icons/ChevronLeft/chevronLeftPath.ts +2 -0
- package/src/icons/ChevronLeft/index.ts +1 -0
- package/src/icons/Minus/MinusIcon.tsx +20 -0
- package/src/icons/Minus/MinusIcon.web.tsx +19 -0
- package/src/icons/Minus/index.ts +1 -0
- package/src/icons/Minus/minusPath.ts +1 -0
- package/src/icons/Plus/PlusIcon.tsx +20 -0
- package/src/icons/Plus/PlusIcon.web.tsx +19 -0
- package/src/icons/Plus/index.ts +1 -0
- package/src/icons/Plus/plusPath.ts +1 -0
- package/src/icons/Sidebar/SidebarIcon.tsx +21 -0
- package/src/icons/Sidebar/SidebarIcon.web.tsx +27 -0
- package/src/icons/Sidebar/index.ts +1 -0
- package/src/icons/Sidebar/sidebarPath.ts +3 -0
- package/src/icons/UsersAlt/UsersAltIcon.tsx +14 -0
- package/src/icons/UsersAlt/UsersAltIcon.web.tsx +13 -0
- package/src/icons/UsersAlt/index.ts +1 -0
- package/src/icons/UsersAlt/usersAltPath.ts +2 -0
- package/src/icons/index.ts +6 -0
- package/src/icons/registry.ts +29 -4
- package/src/index.ts +38 -1
- package/src/primitives/MenuItem/MenuItem.stories.tsx +98 -0
- package/src/primitives/MenuItem/MenuItem.tsx +138 -0
- package/src/primitives/MenuItem/index.ts +2 -0
- package/src/primitives/index.ts +2 -0
- package/src/theme/colors.test.ts +6 -0
- package/src/theme/colors.ts +7 -0
- package/src/theme/index.ts +5 -0
- package/src/theme/typography.ts +76 -0
- package/src/utils/dateUtils.ts +86 -0
package/src/icons/index.ts
CHANGED
|
@@ -10,10 +10,12 @@ export { BagIcon } from "./Bag";
|
|
|
10
10
|
export { BellIcon } from "./Bell";
|
|
11
11
|
export { BuildingIcon } from "./Building";
|
|
12
12
|
export { CalendarIcon } from "./Calendar";
|
|
13
|
+
export { CalendarOutlineIcon } from "./CalendarOutline";
|
|
13
14
|
export { CameraIcon } from "./Camera";
|
|
14
15
|
export { CheckIcon } from "./Check";
|
|
15
16
|
export { CheckBadgeIcon } from "./CheckBadge";
|
|
16
17
|
export { ChevronDownIcon } from "./ChevronDown";
|
|
18
|
+
export { ChevronLeftIcon } from "./ChevronLeft";
|
|
17
19
|
export { ClockIcon } from "./Clock";
|
|
18
20
|
export { FacebookIcon } from "./Facebook";
|
|
19
21
|
export { HeartIcon } from "./Heart";
|
|
@@ -27,8 +29,12 @@ export { LogOutIcon } from "./LogOut";
|
|
|
27
29
|
export { MailIcon } from "./Mail";
|
|
28
30
|
export { NavigateIcon } from "./Navigate";
|
|
29
31
|
export { PhoneIcon } from "./Phone";
|
|
32
|
+
export { PlusIcon } from "./Plus";
|
|
33
|
+
export { MinusIcon } from "./Minus";
|
|
30
34
|
export { SearchIcon } from "./Search";
|
|
31
35
|
export { SettingsIcon } from "./Settings";
|
|
32
36
|
export { ShowIcon } from "./Show";
|
|
37
|
+
export { SidebarIcon } from "./Sidebar";
|
|
33
38
|
export { UserIcon } from "./User";
|
|
39
|
+
export { UsersAltIcon } from "./UsersAlt";
|
|
34
40
|
export { VideoIcon } from "./Video";
|
package/src/icons/registry.ts
CHANGED
|
@@ -3,10 +3,12 @@ import { BagIcon } from "./Bag";
|
|
|
3
3
|
import { BellIcon } from "./Bell";
|
|
4
4
|
import { BuildingIcon } from "./Building";
|
|
5
5
|
import { CalendarIcon } from "./Calendar";
|
|
6
|
+
import { CalendarOutlineIcon } from "./CalendarOutline";
|
|
6
7
|
import { CameraIcon } from "./Camera";
|
|
7
8
|
import { CheckIcon } from "./Check";
|
|
8
9
|
import { CheckBadgeIcon } from "./CheckBadge";
|
|
9
10
|
import { ChevronDownIcon } from "./ChevronDown";
|
|
11
|
+
import { ChevronLeftIcon } from "./ChevronLeft";
|
|
10
12
|
import { ClockIcon } from "./Clock";
|
|
11
13
|
import { FacebookIcon } from "./Facebook";
|
|
12
14
|
import { HeartIcon } from "./Heart";
|
|
@@ -18,12 +20,16 @@ import { LayoutGridIcon } from "./LayoutGrid";
|
|
|
18
20
|
import { LinkedInIcon } from "./LinkedIn";
|
|
19
21
|
import { LogOutIcon } from "./LogOut";
|
|
20
22
|
import { MailIcon } from "./Mail";
|
|
23
|
+
import { MinusIcon } from "./Minus";
|
|
21
24
|
import { NavigateIcon } from "./Navigate";
|
|
22
25
|
import { PhoneIcon } from "./Phone";
|
|
26
|
+
import { PlusIcon } from "./Plus";
|
|
23
27
|
import { SearchIcon } from "./Search";
|
|
24
28
|
import { SettingsIcon } from "./Settings";
|
|
25
29
|
import { ShowIcon } from "./Show";
|
|
30
|
+
import { SidebarIcon } from "./Sidebar";
|
|
26
31
|
import { UserIcon } from "./User";
|
|
32
|
+
import { UsersAltIcon } from "./UsersAlt";
|
|
27
33
|
import { VideoIcon } from "./Video";
|
|
28
34
|
import type { IconComponent } from "./types";
|
|
29
35
|
|
|
@@ -33,10 +39,12 @@ export const icons = {
|
|
|
33
39
|
bell: BellIcon,
|
|
34
40
|
building: BuildingIcon,
|
|
35
41
|
calendar: CalendarIcon,
|
|
42
|
+
calendarOutline: CalendarOutlineIcon,
|
|
36
43
|
camera: CameraIcon,
|
|
37
44
|
check: CheckIcon,
|
|
38
45
|
checkBadge: CheckBadgeIcon,
|
|
39
46
|
chevronDown: ChevronDownIcon,
|
|
47
|
+
chevronLeft: ChevronLeftIcon,
|
|
40
48
|
clock: ClockIcon,
|
|
41
49
|
facebook: FacebookIcon,
|
|
42
50
|
heart: HeartIcon,
|
|
@@ -48,12 +56,16 @@ export const icons = {
|
|
|
48
56
|
linkedin: LinkedInIcon,
|
|
49
57
|
logOut: LogOutIcon,
|
|
50
58
|
mail: MailIcon,
|
|
59
|
+
minus: MinusIcon,
|
|
51
60
|
navigate: NavigateIcon,
|
|
52
61
|
phone: PhoneIcon,
|
|
62
|
+
plus: PlusIcon,
|
|
53
63
|
search: SearchIcon,
|
|
54
64
|
settings: SettingsIcon,
|
|
55
65
|
show: ShowIcon,
|
|
66
|
+
sidebar: SidebarIcon,
|
|
56
67
|
user: UserIcon,
|
|
68
|
+
usersAlt: UsersAltIcon,
|
|
57
69
|
video: VideoIcon,
|
|
58
70
|
} as const satisfies Record<string, IconComponent>;
|
|
59
71
|
|
|
@@ -62,12 +74,25 @@ export type IconName = keyof typeof icons;
|
|
|
62
74
|
export const iconNames = Object.keys(icons) as IconName[];
|
|
63
75
|
|
|
64
76
|
export const iconGroups = {
|
|
65
|
-
navigation: ["arrowRight", "chevronDown", "home", "navigate"],
|
|
66
|
-
actions: [
|
|
67
|
-
|
|
77
|
+
navigation: ["arrowRight", "chevronDown", "chevronLeft", "home", "navigate"],
|
|
78
|
+
actions: [
|
|
79
|
+
"show",
|
|
80
|
+
"bell",
|
|
81
|
+
"camera",
|
|
82
|
+
"key",
|
|
83
|
+
"check",
|
|
84
|
+
"checkBadge",
|
|
85
|
+
"search",
|
|
86
|
+
"calendar",
|
|
87
|
+
"calendarOutline",
|
|
88
|
+
"heart",
|
|
89
|
+
"minus",
|
|
90
|
+
"plus",
|
|
91
|
+
],
|
|
92
|
+
objects: ["bag", "building", "clock", "user", "usersAlt", "video"],
|
|
68
93
|
communication: ["mail", "phone"],
|
|
69
94
|
social: ["facebook", "instagram", "linkedin"],
|
|
70
|
-
interface: ["layoutGrid", "settings", "helpCircle", "logOut"],
|
|
95
|
+
interface: ["layoutGrid", "sidebar", "settings", "helpCircle", "logOut"],
|
|
71
96
|
} as const satisfies Record<string, readonly IconName[]>;
|
|
72
97
|
|
|
73
98
|
export type IconGroup = keyof typeof iconGroups;
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,35 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
Avatar,
|
|
3
|
+
Badge,
|
|
4
|
+
Counter,
|
|
5
|
+
SegmentedToggle,
|
|
6
|
+
StatCard,
|
|
7
|
+
Button,
|
|
8
|
+
Checkbox,
|
|
9
|
+
DatePicker,
|
|
10
|
+
Input,
|
|
11
|
+
Select,
|
|
12
|
+
Textarea,
|
|
13
|
+
Toggle,
|
|
14
|
+
} from "./components";
|
|
2
15
|
export type {
|
|
3
16
|
AvatarProps,
|
|
4
17
|
AvatarMenuItem,
|
|
18
|
+
BadgeProps,
|
|
19
|
+
BadgeVariant,
|
|
20
|
+
CounterProps,
|
|
21
|
+
SegmentedToggleOption,
|
|
22
|
+
SegmentedToggleProps,
|
|
23
|
+
StatCardProps,
|
|
5
24
|
ButtonProps,
|
|
6
25
|
ButtonSize,
|
|
7
26
|
ButtonVariant,
|
|
8
27
|
CheckboxProps,
|
|
9
28
|
CheckboxVariant,
|
|
29
|
+
DatePickerProps,
|
|
30
|
+
DatePickerSingleProps,
|
|
31
|
+
DatePickerRangeProps,
|
|
32
|
+
DateRange,
|
|
10
33
|
InputProps,
|
|
11
34
|
InputSize,
|
|
12
35
|
SelectOption,
|
|
@@ -17,6 +40,9 @@ export type {
|
|
|
17
40
|
ToggleProps,
|
|
18
41
|
} from "./components";
|
|
19
42
|
|
|
43
|
+
export { MenuItem } from "./primitives";
|
|
44
|
+
export type { MenuItemProps } from "./primitives";
|
|
45
|
+
|
|
20
46
|
export {
|
|
21
47
|
Icon,
|
|
22
48
|
icons,
|
|
@@ -29,10 +55,12 @@ export {
|
|
|
29
55
|
BellIcon,
|
|
30
56
|
BuildingIcon,
|
|
31
57
|
CalendarIcon,
|
|
58
|
+
CalendarOutlineIcon,
|
|
32
59
|
CameraIcon,
|
|
33
60
|
CheckIcon,
|
|
34
61
|
CheckBadgeIcon,
|
|
35
62
|
ChevronDownIcon,
|
|
63
|
+
ChevronLeftIcon,
|
|
36
64
|
ClockIcon,
|
|
37
65
|
FacebookIcon,
|
|
38
66
|
HeartIcon,
|
|
@@ -44,12 +72,16 @@ export {
|
|
|
44
72
|
LinkedInIcon,
|
|
45
73
|
LogOutIcon,
|
|
46
74
|
MailIcon,
|
|
75
|
+
MinusIcon,
|
|
47
76
|
NavigateIcon,
|
|
48
77
|
PhoneIcon,
|
|
78
|
+
PlusIcon,
|
|
49
79
|
SearchIcon,
|
|
50
80
|
SettingsIcon,
|
|
51
81
|
ShowIcon,
|
|
82
|
+
SidebarIcon,
|
|
52
83
|
UserIcon,
|
|
84
|
+
UsersAltIcon,
|
|
53
85
|
VideoIcon,
|
|
54
86
|
} from "./icons";
|
|
55
87
|
export type { IconProps, IconName, IconGroup, IconComponent, IconByNameProps } from "./icons";
|
|
@@ -58,7 +90,12 @@ export {
|
|
|
58
90
|
colors,
|
|
59
91
|
grey,
|
|
60
92
|
avatarTypography,
|
|
93
|
+
badgeTypography,
|
|
94
|
+
statCardTypography,
|
|
95
|
+
segmentedToggleTypography,
|
|
96
|
+
counterTypography,
|
|
61
97
|
buttonTypography,
|
|
98
|
+
datePickerTypography,
|
|
62
99
|
inputTypography,
|
|
63
100
|
selectTypography,
|
|
64
101
|
textareaTypography,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { fn } from "storybook/test";
|
|
3
|
+
import { View, StyleSheet } from "react-native";
|
|
4
|
+
import { MenuItem } from "./MenuItem";
|
|
5
|
+
import {
|
|
6
|
+
CalendarIcon,
|
|
7
|
+
HeartIcon,
|
|
8
|
+
HelpCircleIcon,
|
|
9
|
+
LayoutGridIcon,
|
|
10
|
+
LogOutIcon,
|
|
11
|
+
SettingsIcon,
|
|
12
|
+
VideoIcon,
|
|
13
|
+
} from "../../icons";
|
|
14
|
+
import { colors } from "../../theme";
|
|
15
|
+
|
|
16
|
+
const meta = {
|
|
17
|
+
title: "Primitives/MenuItem",
|
|
18
|
+
component: MenuItem,
|
|
19
|
+
args: {
|
|
20
|
+
label: "Bookings",
|
|
21
|
+
icon: <CalendarIcon size={16} />,
|
|
22
|
+
onPress: fn(),
|
|
23
|
+
disabled: false,
|
|
24
|
+
},
|
|
25
|
+
argTypes: {
|
|
26
|
+
disabled: { control: "boolean" },
|
|
27
|
+
backgroundColor: { control: "color" },
|
|
28
|
+
hoverColor: { control: "color" },
|
|
29
|
+
onPress: { action: "pressed" },
|
|
30
|
+
},
|
|
31
|
+
parameters: {
|
|
32
|
+
backgrounds: { default: "black" },
|
|
33
|
+
},
|
|
34
|
+
decorators: [
|
|
35
|
+
(Story) => (
|
|
36
|
+
<View style={storyStyles.frame}>
|
|
37
|
+
<Story />
|
|
38
|
+
</View>
|
|
39
|
+
),
|
|
40
|
+
],
|
|
41
|
+
} satisfies Meta<typeof MenuItem>;
|
|
42
|
+
|
|
43
|
+
export default meta;
|
|
44
|
+
type Story = StoryObj<typeof meta>;
|
|
45
|
+
|
|
46
|
+
export const Default: Story = {};
|
|
47
|
+
|
|
48
|
+
export const WithoutIcon: Story = {
|
|
49
|
+
args: { icon: undefined },
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export const Disabled: Story = {
|
|
53
|
+
args: { disabled: true },
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
/** Optional resting / hover colors — transparent + grey600 by default. */
|
|
57
|
+
export const CustomColors: Story = {
|
|
58
|
+
args: {
|
|
59
|
+
label: "Log out",
|
|
60
|
+
icon: <LogOutIcon size={16} />,
|
|
61
|
+
backgroundColor: colors.redMuted,
|
|
62
|
+
hoverColor: colors.redHover,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const AllStates: Story = {
|
|
67
|
+
render: () => (
|
|
68
|
+
<View style={storyStyles.column}>
|
|
69
|
+
<MenuItem label="Bookings" icon={<CalendarIcon size={16} />} onPress={fn()} />
|
|
70
|
+
<MenuItem label="Digital keys" icon={<LayoutGridIcon size={16} />} onPress={fn()} />
|
|
71
|
+
<MenuItem label="Cameras" icon={<VideoIcon size={16} />} onPress={fn()} />
|
|
72
|
+
<MenuItem label="Favorites" icon={<HeartIcon size={16} />} onPress={fn()} />
|
|
73
|
+
<MenuItem label="Support" icon={<HelpCircleIcon size={16} />} onPress={fn()} />
|
|
74
|
+
<MenuItem label="Settings" icon={<SettingsIcon size={16} />} onPress={fn()} />
|
|
75
|
+
<MenuItem
|
|
76
|
+
label="Log out"
|
|
77
|
+
icon={<LogOutIcon size={16} />}
|
|
78
|
+
onPress={fn()}
|
|
79
|
+
backgroundColor={colors.redMuted}
|
|
80
|
+
hoverColor={colors.redHover}
|
|
81
|
+
/>
|
|
82
|
+
<MenuItem label="Disabled" icon={<SettingsIcon size={16} />} disabled onPress={fn()} />
|
|
83
|
+
</View>
|
|
84
|
+
),
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const storyStyles = StyleSheet.create({
|
|
88
|
+
frame: {
|
|
89
|
+
width: 282,
|
|
90
|
+
padding: 8,
|
|
91
|
+
borderRadius: 16,
|
|
92
|
+
gap: 8,
|
|
93
|
+
backgroundColor: colors.grey700,
|
|
94
|
+
},
|
|
95
|
+
column: {
|
|
96
|
+
gap: 8,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { forwardRef, useMemo, useRef, useState, type ComponentRef, type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
type GestureResponderEvent,
|
|
8
|
+
type PressableProps,
|
|
9
|
+
type StyleProp,
|
|
10
|
+
type TextStyle,
|
|
11
|
+
type ViewStyle,
|
|
12
|
+
} from "react-native";
|
|
13
|
+
import { avatarTypography, colors } from "../../theme";
|
|
14
|
+
import { mergeRefs } from "../../utils/mergeRefs";
|
|
15
|
+
import { useApplyWebClassName } from "../../utils/useApplyWebClassName";
|
|
16
|
+
|
|
17
|
+
export interface MenuItemProps extends Omit<
|
|
18
|
+
PressableProps,
|
|
19
|
+
"onPress" | "disabled" | "style" | "children" | "hitSlop"
|
|
20
|
+
> {
|
|
21
|
+
/** Leading icon, e.g. `<CalendarIcon size={16} />` — pass it pre-colored. */
|
|
22
|
+
icon?: ReactNode;
|
|
23
|
+
label: string;
|
|
24
|
+
onPress?: (event: GestureResponderEvent) => void;
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
/** Row background at rest. Defaults to transparent. */
|
|
27
|
+
backgroundColor?: string;
|
|
28
|
+
/** Row background on hover/press. Defaults to `colors.grey600`. */
|
|
29
|
+
hoverColor?: string;
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
labelStyle?: StyleProp<TextStyle>;
|
|
32
|
+
className?: string;
|
|
33
|
+
hitSlop?: PressableProps["hitSlop"];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ITEM_RADIUS = 12;
|
|
37
|
+
const ICON_SIZE = 16;
|
|
38
|
+
|
|
39
|
+
export const MenuItem = forwardRef<ComponentRef<typeof Pressable>, MenuItemProps>(function MenuItem(
|
|
40
|
+
{
|
|
41
|
+
icon,
|
|
42
|
+
label,
|
|
43
|
+
onPress,
|
|
44
|
+
disabled = false,
|
|
45
|
+
backgroundColor,
|
|
46
|
+
hoverColor,
|
|
47
|
+
style,
|
|
48
|
+
labelStyle,
|
|
49
|
+
className,
|
|
50
|
+
accessibilityLabel,
|
|
51
|
+
hitSlop = 4,
|
|
52
|
+
onHoverIn,
|
|
53
|
+
onHoverOut,
|
|
54
|
+
onPressIn,
|
|
55
|
+
onPressOut,
|
|
56
|
+
...rest
|
|
57
|
+
},
|
|
58
|
+
forwardedRef,
|
|
59
|
+
) {
|
|
60
|
+
const containerRef = useRef<ComponentRef<typeof Pressable>>(null);
|
|
61
|
+
const setContainerRef = useMemo(() => mergeRefs(containerRef, forwardedRef), [forwardedRef]);
|
|
62
|
+
const [hovered, setHovered] = useState(false);
|
|
63
|
+
|
|
64
|
+
useApplyWebClassName(containerRef, className);
|
|
65
|
+
|
|
66
|
+
const resolvedBackground = hovered
|
|
67
|
+
? (hoverColor ?? colors.grey600)
|
|
68
|
+
: (backgroundColor ?? "transparent");
|
|
69
|
+
const resolvedAccessibilityLabel = accessibilityLabel ?? label;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<Pressable
|
|
73
|
+
{...rest}
|
|
74
|
+
ref={setContainerRef}
|
|
75
|
+
onPress={onPress}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
hitSlop={hitSlop}
|
|
78
|
+
onHoverIn={(event) => {
|
|
79
|
+
setHovered(true);
|
|
80
|
+
onHoverIn?.(event);
|
|
81
|
+
}}
|
|
82
|
+
onHoverOut={(event) => {
|
|
83
|
+
setHovered(false);
|
|
84
|
+
onHoverOut?.(event);
|
|
85
|
+
}}
|
|
86
|
+
onPressIn={(event) => {
|
|
87
|
+
setHovered(true);
|
|
88
|
+
onPressIn?.(event);
|
|
89
|
+
}}
|
|
90
|
+
onPressOut={(event) => {
|
|
91
|
+
setHovered(false);
|
|
92
|
+
onPressOut?.(event);
|
|
93
|
+
}}
|
|
94
|
+
accessibilityRole="menuitem"
|
|
95
|
+
accessibilityLabel={resolvedAccessibilityLabel}
|
|
96
|
+
accessibilityState={{ disabled }}
|
|
97
|
+
style={[
|
|
98
|
+
styles.root,
|
|
99
|
+
{ backgroundColor: resolvedBackground },
|
|
100
|
+
disabled && styles.disabled,
|
|
101
|
+
style,
|
|
102
|
+
]}
|
|
103
|
+
>
|
|
104
|
+
{icon ? <View style={styles.iconSlot}>{icon}</View> : null}
|
|
105
|
+
<Text style={[styles.label, labelStyle]} numberOfLines={1}>
|
|
106
|
+
{label}
|
|
107
|
+
</Text>
|
|
108
|
+
</Pressable>
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const styles = StyleSheet.create({
|
|
113
|
+
root: {
|
|
114
|
+
minHeight: 40,
|
|
115
|
+
borderRadius: ITEM_RADIUS,
|
|
116
|
+
paddingVertical: 12,
|
|
117
|
+
paddingHorizontal: 12,
|
|
118
|
+
flexDirection: "row",
|
|
119
|
+
alignItems: "center",
|
|
120
|
+
gap: 8,
|
|
121
|
+
alignSelf: "stretch",
|
|
122
|
+
},
|
|
123
|
+
disabled: {
|
|
124
|
+
opacity: 0.5,
|
|
125
|
+
},
|
|
126
|
+
iconSlot: {
|
|
127
|
+
width: ICON_SIZE,
|
|
128
|
+
height: ICON_SIZE,
|
|
129
|
+
alignItems: "center",
|
|
130
|
+
justifyContent: "center",
|
|
131
|
+
flexShrink: 0,
|
|
132
|
+
},
|
|
133
|
+
label: {
|
|
134
|
+
...avatarTypography.menuItem,
|
|
135
|
+
color: colors.white,
|
|
136
|
+
flex: 1,
|
|
137
|
+
},
|
|
138
|
+
});
|
package/src/theme/colors.test.ts
CHANGED
|
@@ -6,6 +6,12 @@ describe("design tokens", () => {
|
|
|
6
6
|
it("exposes primary and button typography used by Button", () => {
|
|
7
7
|
expect(colors.primary).toBe("#B46436");
|
|
8
8
|
expect(colors.dark).toBe("#090909");
|
|
9
|
+
expect(colors.green).toBe("#34A853");
|
|
10
|
+
expect(colors.whiteAlpha86).toBe("#FFFFFFDB");
|
|
11
|
+
expect(colors.whiteAlpha40).toBe("#FFFFFF66");
|
|
12
|
+
expect(colors.whiteAlpha20).toBe("#FFFFFF33");
|
|
13
|
+
expect(colors.whiteAlpha10).toBe("#FFFFFF1A");
|
|
14
|
+
expect(colors.whiteAlpha5).toBe("#FFFFFF0D");
|
|
9
15
|
expect(buttonTypography.lg.fontSize).toBe(14);
|
|
10
16
|
expect(buttonTypography.lg.fontWeight).toBe("600");
|
|
11
17
|
});
|
package/src/theme/colors.ts
CHANGED
|
@@ -22,10 +22,17 @@ export const colors = {
|
|
|
22
22
|
primary: "#B46436",
|
|
23
23
|
black: "#000000",
|
|
24
24
|
white: "#ffffff",
|
|
25
|
+
whiteAlpha86: "#FFFFFFDB",
|
|
26
|
+
whiteAlpha40: "#FFFFFF66",
|
|
27
|
+
whiteAlpha20: "#FFFFFF33",
|
|
28
|
+
whiteAlpha10: "#FFFFFF1A",
|
|
29
|
+
whiteAlpha5: "#FFFFFF0D",
|
|
25
30
|
dark: "#090909",
|
|
26
31
|
red: "#A63E3E",
|
|
27
32
|
lightGrey: "#B7B7B7",
|
|
33
|
+
green: "#34A853",
|
|
28
34
|
primaryMuted: "#B464360F",
|
|
35
|
+
primaryHover: "#B4643633",
|
|
29
36
|
redMuted: "#A63E3E0F",
|
|
30
37
|
redHover: "#A63E3E33",
|
|
31
38
|
|
package/src/theme/index.ts
CHANGED
|
@@ -4,7 +4,12 @@ export type { GreyStep } from "./colors";
|
|
|
4
4
|
export {
|
|
5
5
|
fonts,
|
|
6
6
|
avatarTypography,
|
|
7
|
+
badgeTypography,
|
|
8
|
+
statCardTypography,
|
|
9
|
+
segmentedToggleTypography,
|
|
10
|
+
counterTypography,
|
|
7
11
|
buttonTypography,
|
|
12
|
+
datePickerTypography,
|
|
8
13
|
inputTypography,
|
|
9
14
|
selectTypography,
|
|
10
15
|
textareaTypography,
|
package/src/theme/typography.ts
CHANGED
|
@@ -20,6 +20,42 @@ export const buttonTypography = {
|
|
|
20
20
|
},
|
|
21
21
|
} as const;
|
|
22
22
|
|
|
23
|
+
/** Badge label — Regular 14, line-height 112%, letter-spacing 0. */
|
|
24
|
+
export const badgeTypography = {
|
|
25
|
+
fontFamily: fonts.sans,
|
|
26
|
+
fontSize: 14,
|
|
27
|
+
fontWeight: "400" as const,
|
|
28
|
+
lineHeight: 16,
|
|
29
|
+
letterSpacing: 0,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
/** Stat card label — Medium 16, line-height 100%, letter-spacing 0. */
|
|
33
|
+
export const statCardTypography = {
|
|
34
|
+
fontFamily: fonts.sans,
|
|
35
|
+
fontSize: 16,
|
|
36
|
+
fontWeight: "500" as const,
|
|
37
|
+
lineHeight: 16,
|
|
38
|
+
letterSpacing: 0,
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
/** Segmented toggle label — Medium 14, line-height 100%, letter-spacing 0. */
|
|
42
|
+
export const segmentedToggleTypography = {
|
|
43
|
+
fontFamily: fonts.sans,
|
|
44
|
+
fontSize: 14,
|
|
45
|
+
fontWeight: "500" as const,
|
|
46
|
+
lineHeight: 14,
|
|
47
|
+
letterSpacing: 0,
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
/** Counter value — Bold 14, line-height 100%, letter-spacing 0. */
|
|
51
|
+
export const counterTypography = {
|
|
52
|
+
fontFamily: fonts.sans,
|
|
53
|
+
fontSize: 14,
|
|
54
|
+
fontWeight: "700" as const,
|
|
55
|
+
lineHeight: 14,
|
|
56
|
+
letterSpacing: 0,
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
23
59
|
/** Input label / field / hint typography. */
|
|
24
60
|
export const inputTypography = {
|
|
25
61
|
label: {
|
|
@@ -95,6 +131,46 @@ export const avatarTypography = {
|
|
|
95
131
|
},
|
|
96
132
|
} as const;
|
|
97
133
|
|
|
134
|
+
/**
|
|
135
|
+
* DatePicker typography — trigger field text plus the calendar-panel type
|
|
136
|
+
* (month heading, weekday header, day-cell numbers). No label/hint chrome —
|
|
137
|
+
* the Figma trigger is a standalone field. Figma didn't spec the month
|
|
138
|
+
* heading / day-cell number sizes, so those reuse the existing type scale
|
|
139
|
+
* for visual consistency with the rest of the kit.
|
|
140
|
+
*/
|
|
141
|
+
export const datePickerTypography = {
|
|
142
|
+
field: {
|
|
143
|
+
fontFamily: fonts.sans,
|
|
144
|
+
fontSize: 12,
|
|
145
|
+
fontWeight: "400" as const,
|
|
146
|
+
lineHeight: 12,
|
|
147
|
+
letterSpacing: 0.36,
|
|
148
|
+
},
|
|
149
|
+
/** Calendar month/year heading — Bold, centered, White. */
|
|
150
|
+
monthHeading: {
|
|
151
|
+
fontFamily: fonts.sans,
|
|
152
|
+
fontSize: 14,
|
|
153
|
+
fontWeight: "700" as const,
|
|
154
|
+
lineHeight: 14,
|
|
155
|
+
letterSpacing: 0,
|
|
156
|
+
},
|
|
157
|
+
/** Weekday header (ԵՐ, ԵՔ, ...) — Regular, uppercase, centered, Grey/300. */
|
|
158
|
+
weekday: {
|
|
159
|
+
fontFamily: fonts.sans,
|
|
160
|
+
fontSize: 12,
|
|
161
|
+
fontWeight: "400" as const,
|
|
162
|
+
lineHeight: 12,
|
|
163
|
+
letterSpacing: 0,
|
|
164
|
+
},
|
|
165
|
+
day: {
|
|
166
|
+
fontFamily: fonts.sans,
|
|
167
|
+
fontSize: 14,
|
|
168
|
+
fontWeight: "400" as const,
|
|
169
|
+
lineHeight: 14,
|
|
170
|
+
letterSpacing: 0,
|
|
171
|
+
},
|
|
172
|
+
} as const;
|
|
173
|
+
|
|
98
174
|
/** Textarea label / field / hint typography — Medium/Regular 12–14, letter-spacing 3%. */
|
|
99
175
|
export const textareaTypography = {
|
|
100
176
|
label: {
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Small, dependency-free date helpers for the DatePicker component. These only
|
|
3
|
+
* ever operate on local calendar days (time-of-day is ignored/zeroed) so
|
|
4
|
+
* comparisons stay simple and predictable across the whole component.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export function startOfDay(date: Date): Date {
|
|
8
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function isSameDay(a: Date | null | undefined, b: Date | null | undefined): boolean {
|
|
12
|
+
if (a == null || b == null) return false;
|
|
13
|
+
return (
|
|
14
|
+
a.getFullYear() === b.getFullYear() &&
|
|
15
|
+
a.getMonth() === b.getMonth() &&
|
|
16
|
+
a.getDate() === b.getDate()
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isSameMonth(a: Date, b: Date): boolean {
|
|
21
|
+
return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function addMonths(date: Date, amount: number): Date {
|
|
25
|
+
return new Date(date.getFullYear(), date.getMonth() + amount, 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function isBefore(a: Date, b: Date): boolean {
|
|
29
|
+
return startOfDay(a).getTime() < startOfDay(b).getTime();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isAfter(a: Date, b: Date): boolean {
|
|
33
|
+
return startOfDay(a).getTime() > startOfDay(b).getTime();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isWithinRange(date: Date, start: Date, end: Date): boolean {
|
|
37
|
+
const time = startOfDay(date).getTime();
|
|
38
|
+
return time >= startOfDay(start).getTime() && time <= startOfDay(end).getTime();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function clampDate(date: Date, min?: Date | null, max?: Date | null): Date {
|
|
42
|
+
if (min && isBefore(date, min)) return startOfDay(min);
|
|
43
|
+
if (max && isAfter(date, max)) return startOfDay(max);
|
|
44
|
+
return startOfDay(date);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function isDateDisabled(date: Date, min?: Date | null, max?: Date | null): boolean {
|
|
48
|
+
if (min && isBefore(date, min)) return true;
|
|
49
|
+
if (max && isAfter(date, max)) return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Zero-padded "DD.MM.YYYY" — matches the Figma reference formatting. */
|
|
54
|
+
export function formatDate(date: Date): string {
|
|
55
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
56
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
57
|
+
return `${day}.${month}.${date.getFullYear()}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Builds a 6×7 grid of calendar days for the month containing `monthDate`,
|
|
62
|
+
* including the leading/trailing days from adjacent months needed to fill
|
|
63
|
+
* whole weeks. `weekStartsOn` follows the JS `Date#getDay()` convention
|
|
64
|
+
* (0 = Sunday, 1 = Monday, ...).
|
|
65
|
+
*/
|
|
66
|
+
export function getMonthMatrix(monthDate: Date, weekStartsOn: 0 | 1 = 1): Date[][] {
|
|
67
|
+
const year = monthDate.getFullYear();
|
|
68
|
+
const month = monthDate.getMonth();
|
|
69
|
+
const firstOfMonth = new Date(year, month, 1);
|
|
70
|
+
|
|
71
|
+
const firstWeekday = firstOfMonth.getDay();
|
|
72
|
+
const leadingDays = (firstWeekday - weekStartsOn + 7) % 7;
|
|
73
|
+
const gridStart = new Date(year, month, 1 - leadingDays);
|
|
74
|
+
|
|
75
|
+
const weeks: Date[][] = [];
|
|
76
|
+
let cursor = gridStart;
|
|
77
|
+
for (let week = 0; week < 6; week += 1) {
|
|
78
|
+
const days: Date[] = [];
|
|
79
|
+
for (let day = 0; day < 7; day += 1) {
|
|
80
|
+
days.push(cursor);
|
|
81
|
+
cursor = new Date(cursor.getFullYear(), cursor.getMonth(), cursor.getDate() + 1);
|
|
82
|
+
}
|
|
83
|
+
weeks.push(days);
|
|
84
|
+
}
|
|
85
|
+
return weeks;
|
|
86
|
+
}
|