@swan-io/lake 15.5.2 → 16.0.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swan-io/lake",
3
- "version": "15.5.2",
3
+ "version": "16.0.0",
4
4
  "engines": {
5
5
  "node": ">22.12.0"
6
6
  },
@@ -2,7 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useId } from "react";
3
3
  import { Pressable, StyleSheet, View } from "react-native";
4
4
  import { backgroundColor, colors, spacings } from "../constants/design";
5
- import { useDisclosure } from "../hooks/useDisclosure";
5
+ import { useBoolean } from "../hooks/useBoolean";
6
6
  import { Icon } from "./Icon";
7
7
  import { LakeText } from "./LakeText";
8
8
  import { Space } from "./Space";
@@ -40,6 +40,6 @@ const styles = StyleSheet.create({
40
40
  });
41
41
  export const Accordion = ({ children, trigger, style, contentContainerStyle }) => {
42
42
  const id = useId();
43
- const [isOpen, { toggle }] = useDisclosure(false);
43
+ const [isOpen, { toggle }] = useBoolean(false);
44
44
  return (_jsxs(View, { children: [_jsxs(Pressable, { "aria-controls": id, role: "button", "aria-expanded": isOpen, onPress: toggle, style: [styles.trigger, style], children: [_jsx(Icon, { name: "chevron-right-filled", size: 12, color: colors.gray[500], style: [styles.arrow, isOpen && styles.arrowOpen] }), _jsx(Space, { width: 20 }), typeof trigger === "string" ? (_jsx(LakeText, { variant: "smallMedium", color: colors.gray[900], children: trigger })) : (trigger)] }), _jsx(View, { "aria-labelledby": id, "aria-hidden": !isOpen, role: "region", style: [styles.contentContainer, isOpen && styles.contentContainerDisplayed], children: _jsx(View, { style: styles.contentInner, children: _jsx(View, { style: [styles.content, contentContainerStyle], children: children }) }) })] }));
45
45
  };
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { createContext, useContext, useRef } from "react";
3
3
  import { StyleSheet, View } from "react-native";
4
4
  import { colors, spacings } from "../constants/design";
5
- import { useDisclosure } from "../hooks/useDisclosure";
5
+ import { useBoolean } from "../hooks/useBoolean";
6
6
  import { noop } from "../utils/function";
7
7
  import { LakeButton } from "./LakeButton";
8
8
  import { LakeText } from "./LakeText";
@@ -28,7 +28,7 @@ const styles = StyleSheet.create({
28
28
  });
29
29
  const ContextMenuContext = createContext({ close: noop });
30
30
  export const ContextMenu = ({ children, ariaLabel, withPill = false, verticalPlacement, }) => {
31
- const [isOpen, { open, close }] = useDisclosure(false);
31
+ const [isOpen, { on: open, off: close }] = useBoolean(false);
32
32
  const triggerRef = useRef(null);
33
33
  return (_jsxs(ContextMenuContext.Provider, { value: { close }, children: [_jsx(LakeButton, { ref: triggerRef, mode: "tertiary", size: "small", icon: "more-horizontal-filled", color: "gray", onPress: open, ariaLabel: ariaLabel, pill: withPill }), _jsx(Popover, { referenceRef: triggerRef, visible: isOpen, onDismiss: close, verticalPlacement: verticalPlacement, children: _jsx(View, { style: styles.container, children: children }) })] }));
34
34
  };
@@ -3,6 +3,7 @@ import { StyleProp, ViewStyle } from "react-native";
3
3
  type AlertVariant = "info" | "warning" | "error" | "success" | "neutral";
4
4
  type Props = {
5
5
  anchored?: boolean;
6
+ inPopover?: boolean;
6
7
  variant: AlertVariant;
7
8
  children?: ReactNode;
8
9
  callToAction?: ReactNode;
@@ -11,5 +12,5 @@ type Props = {
11
12
  style?: StyleProp<ViewStyle>;
12
13
  tag?: string;
13
14
  };
14
- export declare const LakeAlert: ({ anchored, variant, title, subtitle, children, style, callToAction, tag, }: Props) => import("react").JSX.Element;
15
+ export declare const LakeAlert: ({ anchored, inPopover, variant, title, subtitle, children, style, callToAction, tag, }: Props) => import("react").JSX.Element;
15
16
  export {};
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { StyleSheet, View } from "react-native";
3
3
  import { match } from "ts-pattern";
4
4
  import { commonStyles } from "../constants/commonStyles";
5
- import { breakpoints, colors, shadows } from "../constants/design";
5
+ import { breakpoints, colors, radii, shadows } from "../constants/design";
6
6
  import { useResponsive } from "../hooks/useResponsive";
7
7
  import { isNotNullish, isNotNullishOrEmpty } from "../utils/nullish";
8
8
  import { Box } from "./Box";
@@ -14,7 +14,7 @@ const styles = StyleSheet.create({
14
14
  base: {
15
15
  paddingVertical: 20,
16
16
  paddingHorizontal: 24,
17
- borderRadius: 4,
17
+ borderRadius: radii[4],
18
18
  borderWidth: 0.5,
19
19
  borderLeftWidth: 4,
20
20
  boxShadow: shadows.tile,
@@ -25,6 +25,9 @@ const styles = StyleSheet.create({
25
25
  marginBottom: -1,
26
26
  boxShadow: "none",
27
27
  },
28
+ inPopover: {
29
+ borderRadius: radii[8],
30
+ },
28
31
  content: {
29
32
  marginLeft: 40,
30
33
  },
@@ -71,7 +74,7 @@ const alertLeftBorder = {
71
74
  neutral: colors.gray[500],
72
75
  };
73
76
  const isText = (node) => typeof node === "string" || typeof node === "number";
74
- export const LakeAlert = ({ anchored = false, variant, title, subtitle, children, style, callToAction, tag, }) => {
77
+ export const LakeAlert = ({ anchored = false, inPopover = false, variant, title, subtitle, children, style, callToAction, tag, }) => {
75
78
  const color = alertColor[variant];
76
79
  const icon = alertIcon[variant];
77
80
  const { desktop } = useResponsive(breakpoints.medium);
@@ -90,6 +93,7 @@ export const LakeAlert = ({ anchored = false, variant, title, subtitle, children
90
93
  borderColor: alertBorder[variant],
91
94
  },
92
95
  anchored ? styles.anchored : { borderLeftColor: alertLeftBorder[variant] },
96
+ inPopover && styles.inPopover,
93
97
  style,
94
98
  ], children: [_jsxs(Box, { direction: "row", alignItems: "center", children: [icon != null ? (_jsxs(_Fragment, { children: [_jsx(Icon, { name: icon, color: color, size: 20 }), _jsx(Space, { width: 20 })] })) : null, _jsxs(View, { style: commonStyles.fill, children: [_jsxs(Box, { direction: "row", justifyContent: "spaceBetween", children: [_jsx(LakeText, { color: color, variant: "medium", children: title }), tag && _jsx(Tag, { color: translateColorTag(), children: tag })] }), isNotNullishOrEmpty(subtitle) && _jsx(LakeText, { color: color, children: subtitle })] }), isNotNullish(callToAction) && _jsx(View, { style: styles.callToAction, children: callToAction })] }), isNotNullish(children) && (_jsxs(View, { style: desktop && icon != null && styles.content, children: [_jsx(Space, { height: 12 }), isText(children) ? _jsx(LakeText, { color: color, children: children }) : children] }))] }));
95
99
  };
@@ -5,6 +5,5 @@ type Props = {
5
5
  size?: number;
6
6
  valueToCopy: string;
7
7
  };
8
- export declare const copyButtondefaultSize = 21;
9
8
  export declare const LakeCopyButton: ({ color, copiedText, copyText, size, valueToCopy, }: Props) => import("react").JSX.Element;
10
9
  export {};
@@ -4,7 +4,7 @@ import { setClipboardText } from "../utils/clipboard";
4
4
  import { Icon } from "./Icon";
5
5
  import { LakeTooltip } from "./LakeTooltip";
6
6
  import { Pressable } from "./Pressable";
7
- export const copyButtondefaultSize = 21;
7
+ const copyButtondefaultSize = 21;
8
8
  export const LakeCopyButton = ({ color, copiedText, copyText, size = copyButtondefaultSize, valueToCopy, }) => {
9
9
  const [visibleState, setVisibleState] = useState("copy");
10
10
  return (_jsx(LakeTooltip, { describedBy: "copy", onHide: () => setVisibleState("copy"), togglableOnFocus: true, content: visibleState === "copy" ? copyText : copiedText, children: _jsx(Pressable, { onPress: event => {
@@ -5,7 +5,6 @@ import { t } from "../../../shared-business/src/utils/i18n";
5
5
  import { commonStyles } from "../constants/commonStyles";
6
6
  import { backgroundColor, colors, invariantColors, radii, shadows, spacings, texts, } from "../constants/design";
7
7
  import { useBoolean } from "../hooks/useBoolean";
8
- import { useDisclosure } from "../hooks/useDisclosure";
9
8
  import { useMergeRefs } from "../hooks/useMergeRefs";
10
9
  import { getFocusableElements } from "../utils/a11y";
11
10
  import { setHighlightApi } from "../utils/highlights";
@@ -163,7 +162,7 @@ export const LakeSelect = ({ ref, title, items, valueStyle, size, color = "curre
163
162
  const currentlyTypedRef = useRef(undefined);
164
163
  const listItemRefs = useRef(Array(items.length));
165
164
  const mergedRef = useMergeRefs(inputRef, ref);
166
- const [visible, { close, open }] = useDisclosure(false);
165
+ const [visible, { on: open, off: close }] = useBoolean(false);
167
166
  const hasValue = isNotNullish(value);
168
167
  const isSmall = size === "small";
169
168
  const itemValue = items.find(i => i.value === value);
@@ -16,7 +16,5 @@ type Props = {
16
16
  style?: StyleProp<ViewStyle>;
17
17
  };
18
18
  export declare const LakeStepper: ({ steps, activeStepId, style }: Props) => import("react").JSX.Element;
19
- /** @deprecated Use <MobileStepper> instead */
20
- export declare const MobileStepTitle: ({ steps, activeStepId }: Props) => import("react").JSX.Element | null;
21
19
  export declare const MobileStepper: ({ steps, activeStepId }: Props) => import("react").JSX.Element | null;
22
20
  export {};
@@ -4,9 +4,7 @@ import { Link } from "@swan-io/chicane";
4
4
  import { StyleSheet, View } from "react-native";
5
5
  import { match, P } from "ts-pattern";
6
6
  import { colors } from "../constants/design";
7
- import { Box } from "./Box";
8
7
  import { Grid } from "./Grid";
9
- import { LakeHeading } from "./LakeHeading";
10
8
  import { LakeText } from "./LakeText";
11
9
  import { Space } from "./Space";
12
10
  const linkStyle = {
@@ -44,23 +42,6 @@ const styles = StyleSheet.create({
44
42
  progressBarError: {
45
43
  backgroundColor: colors.negative[500],
46
44
  },
47
- mobileContainer: {
48
- borderBottomWidth: 1,
49
- borderBottomColor: colors.gray[100],
50
- paddingBottom: 10,
51
- },
52
- mobileNumber: {
53
- minWidth: 28,
54
- height: 24,
55
- borderRadius: 12,
56
- borderWidth: 1,
57
- borderColor: colors.current[100],
58
- backgroundColor: colors.current[50],
59
- },
60
- mobileNumberError: {
61
- borderColor: colors.negative[100],
62
- backgroundColor: colors.negative[50],
63
- },
64
45
  });
65
46
  const stylesMobile = StyleSheet.create({
66
47
  progress: {
@@ -127,26 +108,6 @@ export const LakeStepper = ({ steps, activeStepId, style }) => {
127
108
  }) })] }, currentId));
128
109
  }) }));
129
110
  };
130
- /** @deprecated Use <MobileStepper> instead */
131
- export const MobileStepTitle = ({ steps, activeStepId }) => {
132
- const currentStep = Option.fromNullable(steps
133
- .flatMap((step, index) => match(step)
134
- .with({ id: P.string }, step => ({ ...step, number: `${index + 1}` }))
135
- .with({ children: P.array(P.any) }, ({ children }) => children.map((child, subIndex) => ({
136
- ...child,
137
- number: `${index + 1}.${subIndex + 1}`,
138
- })))
139
- .exhaustive())
140
- .find(({ id }) => id === activeStepId)).map(({ label, hasErrors, number }) => ({
141
- number,
142
- label,
143
- isErrorState: hasErrors === true,
144
- }));
145
- return currentStep.match({
146
- Some: ({ number, label, isErrorState }) => (_jsxs(Box, { direction: "row", alignItems: "center", justifyContent: "center", style: styles.mobileContainer, children: [_jsx(Box, { alignItems: "center", justifyContent: "center", style: [styles.mobileNumber, isErrorState && styles.mobileNumberError], children: _jsx(LakeText, { variant: "smallMedium", color: isErrorState ? colors.negative[500] : colors.current[500], children: number }) }), _jsx(Space, { width: 8 }), _jsx(LakeHeading, { level: 4, variant: "h4", color: isErrorState ? colors.negative[500] : colors.current[500], children: label })] })),
147
- None: () => null,
148
- });
149
- };
150
111
  export const MobileStepper = ({ steps, activeStepId }) => {
151
112
  const total = steps.length;
152
113
  const currentStep = Option.fromNullable(steps
@@ -4,7 +4,6 @@ import { useEffect, useMemo, useRef, useState } from "react";
4
4
  import { Pressable, StyleSheet, Text, TextInput, View, } from "react-native";
5
5
  import { backgroundColor, colors, radii, shadows, texts } from "../constants/design";
6
6
  import { useBoolean } from "../hooks/useBoolean";
7
- import { useDisclosure } from "../hooks/useDisclosure";
8
7
  import { groupBy } from "../utils/array";
9
8
  import { isNotNullish, isNotNullishOrEmpty } from "../utils/nullish";
10
9
  import { safeSplitAround } from "../utils/string";
@@ -143,7 +142,7 @@ export const MultiSelect = ({ color = "gray", disabled = false, emptyResultText,
143
142
  const shouldScrollToBottomRef = useRef(false);
144
143
  const selectedTagListRef = useRef(null);
145
144
  const inputRef = useRef(null);
146
- const [visible, { open, close }] = useDisclosure(false);
145
+ const [visible, { on: open, off: close }] = useBoolean(false);
147
146
  const tint50 = colors[color][50];
148
147
  const tint100 = colors[color][100];
149
148
  useEffect(() => {
@@ -23,6 +23,5 @@ type Props = {
23
23
  safetyMargin?: number;
24
24
  forcedMode?: "Dropdown" | "BottomPanel";
25
25
  };
26
- export declare const VIEWPORT_WIDTH_THRESHOLD = 600;
27
26
  export declare const Popover: import("react").NamedExoticComponent<Props>;
28
27
  export {};
@@ -55,7 +55,7 @@ const animation = {
55
55
  { animationDuration: "100ms" },
56
56
  ],
57
57
  };
58
- export const VIEWPORT_WIDTH_THRESHOLD = 600;
58
+ const VIEWPORT_WIDTH_THRESHOLD = 600;
59
59
  export const Popover = memo(({ children, id, label, placement, verticalPlacement, role = "dialog", describedBy, matchReferenceWidth = false, matchReferenceMinWidth = false, onDismiss = noop, onEscapeKey = onDismiss, referenceRef, returnFocus = true, autoFocus = true, visible, underlay = true, forcedMode, }) => {
60
60
  const [rootElement, setRootElement] = useState(null);
61
61
  const underlayRef = useRef(null);
@@ -2,7 +2,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
2
2
  import { Children, cloneElement, Fragment, useCallback, useId, useState, } from "react";
3
3
  import { Pressable, StyleSheet, Text, View, } from "react-native";
4
4
  import { backgroundColor, colors, negativeSpacings, radii, shadows, spacings, texts, } from "../constants/design";
5
- import { useDisclosure } from "../hooks/useDisclosure";
5
+ import { useBoolean } from "../hooks/useBoolean";
6
6
  import { isNotNullish } from "../utils/nullish";
7
7
  import { Box } from "./Box";
8
8
  import { Icon } from "./Icon";
@@ -99,7 +99,7 @@ const styles = StyleSheet.create({
99
99
  });
100
100
  export const Tile = ({ icon, title, description, headerEnd, footer, flexBasis, children, numberOfLines, paddingVertical = 32, paddingHorizontal = 32, hovered = false, flexGrow = 0, flexShrink = 0, disabled = false, selected, collapsible = false, defaultExpanded = true, style, }) => {
101
101
  const contentId = useId();
102
- const [expanded, { toggle }] = useDisclosure(defaultExpanded);
102
+ const [expanded, { toggle }] = useBoolean(defaultExpanded);
103
103
  const hasHeader = isNotNullish(title) || isNotNullish(headerEnd) || isNotNullish(description) || collapsible;
104
104
  const header = (_jsxs(_Fragment, { children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTitleIcon, children: [isNotNullish(icon) && (_jsxs(_Fragment, { children: [icon, _jsx(Space, { height: 12 })] })), isNotNullish(title) && (_jsx(Text, { style: styles.title, role: "heading", "aria-level": 3, children: title }))] }), collapsible ? (_jsx(Icon, { name: "chevron-down-filled", size: 16, color: colors.gray[500], style: [styles.chevron, expanded && styles.chevronOpen] })) : isNotNullish(headerEnd) ? (_jsx(View, { children: headerEnd })) : null] }), isNotNullish(description) && (_jsxs(_Fragment, { children: [isNotNullish(title) && _jsx(Space, { height: 12 }), _jsx(View, { style: styles.descriptionContainer, children: _jsx(LakeText, { numberOfLines: numberOfLines, style: styles.description, children: description }) })] }))] }));
105
105
  const content = (_jsxs(_Fragment, { children: [children, isNotNullish(footer) && (_jsx(View, { style: [styles.footer, { marginTop: paddingVertical, marginBottom: -paddingVertical }], children: footer }))] }));
@@ -1,20 +0,0 @@
1
- type SortDirection = "Asc" | "Desc";
2
- export type SortField<T> = {
3
- name: T;
4
- ascLabel: string;
5
- descLabel: string;
6
- };
7
- export type SortValue<T extends string> = {
8
- field: T;
9
- direction: SortDirection;
10
- };
11
- type Props<T extends string> = {
12
- visible: boolean;
13
- title: string;
14
- fields: SortField<T>[];
15
- value: SortValue<T> | undefined;
16
- onChange: (value: SortValue<T>) => void;
17
- onClose: () => void;
18
- };
19
- export declare const SortBottomPanel: <T extends string>({ visible, title, value, fields, onChange, onClose, }: Props<T>) => import("react").JSX.Element;
20
- export {};
@@ -1,41 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Fragment } from "react";
3
- import { StyleSheet, View } from "react-native";
4
- import { commonStyles } from "../constants/commonStyles";
5
- import { colors, spacings } from "../constants/design";
6
- import { BottomPanel } from "./BottomPanel";
7
- import { Icon } from "./Icon";
8
- import { LakeHeading } from "./LakeHeading";
9
- import { LakeRadio } from "./LakeRadio";
10
- import { LakeText } from "./LakeText";
11
- import { Pressable } from "./Pressable";
12
- import { Separator } from "./Separator";
13
- import { Space } from "./Space";
14
- const styles = StyleSheet.create({
15
- head: {
16
- padding: 24,
17
- paddingBottom: 12,
18
- },
19
- option: {
20
- display: "flex",
21
- flexDirection: "row",
22
- alignItems: "center",
23
- paddingHorizontal: spacings[24],
24
- paddingVertical: spacings[12],
25
- },
26
- arrowDown: {
27
- transform: "rotate(180deg)",
28
- },
29
- text: {
30
- ...commonStyles.fill,
31
- },
32
- });
33
- export const SortBottomPanel = ({ visible, title, value, fields, onChange, onClose, }) => {
34
- return (_jsxs(BottomPanel, { visible: visible, onPressClose: onClose, children: [_jsx(View, { style: styles.head, children: _jsx(LakeHeading, { level: 3, variant: "h3", children: title }) }), fields.map((field, index) => (_jsxs(Fragment, { children: [_jsxs(Pressable, { style: styles.option, onPress: () => {
35
- onChange({ field: field.name, direction: "Asc" });
36
- onClose();
37
- }, children: [_jsx(Icon, { name: "arrow-up-filled", size: 12, color: colors.gray[600], style: styles.arrowDown }), _jsx(Space, { width: 12 }), _jsx(LakeText, { variant: "smallRegular", color: colors.gray[600], style: styles.text, children: field.ascLabel }), _jsx(Space, { width: 16 }), _jsx(LakeRadio, { value: (value === null || value === void 0 ? void 0 : value.field) === field.name && value.direction === "Asc" })] }), _jsxs(Pressable, { style: styles.option, onPress: () => {
38
- onChange({ field: field.name, direction: "Desc" });
39
- onClose();
40
- }, children: [_jsx(Icon, { name: "arrow-up-filled", size: 12, color: colors.gray[600] }), _jsx(Space, { width: 12 }), _jsx(LakeText, { variant: "smallRegular", color: colors.gray[600], style: styles.text, children: field.descLabel }), _jsx(Space, { width: 16 }), _jsx(LakeRadio, { value: (value === null || value === void 0 ? void 0 : value.field) === field.name && value.direction === "Desc" })] }), index < fields.length - 1 && _jsx(Separator, {})] }, field.name))), _jsx(Space, { height: 12 })] }));
41
- };
@@ -1,55 +0,0 @@
1
- import { Option } from "@swan-io/boxed";
2
- type Time = {
3
- hour: number;
4
- minute: number;
5
- };
6
- export declare const parseTime: (time: string) => Option<Time>;
7
- export declare const parseTimeRange: (value: {
8
- start?: string;
9
- end?: string;
10
- }) => {
11
- start: Option<Time>;
12
- end: Option<Time>;
13
- };
14
- export declare const validateTime: (time: Time) => boolean;
15
- export declare const validateTimeRange: (range: {
16
- start: Option<Time>;
17
- end: Option<Time>;
18
- }) => boolean;
19
- export type TimePickerProps = {
20
- value?: string;
21
- onChangeText: (value: string) => void;
22
- suggestionStart?: Time;
23
- suggestionEnd?: Time;
24
- intervalInMinutes?: number;
25
- nbMaxSuggestions?: number;
26
- suggestionSuffix?: (option: Time) => string;
27
- readOnly?: boolean;
28
- disabled?: boolean;
29
- error?: string;
30
- hideErrors?: boolean;
31
- noSuggestionLabel: string;
32
- };
33
- export declare const TimePicker: ({ value, onChangeText, suggestionStart, suggestionEnd, intervalInMinutes, nbMaxSuggestions, suggestionSuffix, readOnly, disabled, error, hideErrors, noSuggestionLabel, }: TimePickerProps) => import("react").JSX.Element;
34
- export type TimeRangePickerProps = {
35
- value: {
36
- start?: string;
37
- end?: string;
38
- };
39
- onChange: (value: {
40
- start?: string;
41
- end?: string;
42
- }) => void;
43
- intervalInMinutes?: number;
44
- nbMaxSuggestions?: number;
45
- readOnly?: boolean;
46
- disabled?: boolean;
47
- error?: string;
48
- startLabel: string;
49
- endLabel: string;
50
- minutesLabel: string;
51
- hoursLabel: string;
52
- noSuggestionLabel: string;
53
- };
54
- export declare const TimeRangePicker: ({ value, onChange, intervalInMinutes, nbMaxSuggestions, readOnly, disabled, error, startLabel, endLabel, minutesLabel, hoursLabel, noSuggestionLabel, }: TimeRangePickerProps) => import("react").JSX.Element;
55
- export {};
@@ -1,171 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AsyncData, Option, Result } from "@swan-io/boxed";
3
- import { useMemo } from "react";
4
- import { StyleSheet, View } from "react-native";
5
- import { Rifm } from "rifm";
6
- import { colors } from "../constants/design";
7
- import { noop } from "../utils/function";
8
- import { isNotNullishOrEmpty, isNullish } from "../utils/nullish";
9
- import { getRifmProps } from "../utils/rifm";
10
- import { Box } from "./Box";
11
- import { Fill } from "./Fill";
12
- import { Icon } from "./Icon";
13
- import { LakeCombobox } from "./LakeCombobox";
14
- import { LakeLabel } from "./LakeLabel";
15
- import { LakeText } from "./LakeText";
16
- import { Space } from "./Space";
17
- const styles = StyleSheet.create({
18
- label: {
19
- flex: 1,
20
- },
21
- arrowContainer: {
22
- height: 40, // input height
23
- },
24
- });
25
- const DEFAULT_START_TIME = { hour: 0, minute: 0 };
26
- const DEFAULT_END_TIME = { hour: 23, minute: 59 };
27
- const DEFAULT_INTERVAL_IN_MINUTES = 15;
28
- const rifmTimeProps = getRifmProps({
29
- accept: "numeric",
30
- charMap: { 2: ":" },
31
- maxLength: 4,
32
- });
33
- export const parseTime = (time) => {
34
- const [hours, minutes] = time.split(":").map(Number);
35
- if (isNullish(hours) || Number.isNaN(hours) || isNullish(minutes) || Number.isNaN(minutes)) {
36
- return Option.None();
37
- }
38
- return Option.Some({ hour: hours, minute: minutes });
39
- };
40
- export const parseTimeRange = (value) => {
41
- const start = isNotNullishOrEmpty(value.start) ? parseTime(value.start) : Option.None();
42
- const end = isNotNullishOrEmpty(value.end) ? parseTime(value.end) : Option.None();
43
- return { start, end };
44
- };
45
- const parseTypingHours = (time) => {
46
- const [hours, minutes] = time.split(":");
47
- if (isNullish(hours) || (hours === null || hours === void 0 ? void 0 : hours.length) !== 2 || isNotNullishOrEmpty(minutes)) {
48
- return Option.None();
49
- }
50
- const parsedHours = Number(hours);
51
- if (parsedHours < 0 || parsedHours > 23) {
52
- return Option.None();
53
- }
54
- if (Number.isNaN(parsedHours)) {
55
- return Option.None();
56
- }
57
- return Option.Some(parsedHours);
58
- };
59
- const stringifyTime = (time) => {
60
- return `${time.hour.toString().padStart(2, "0")}:${time.minute.toString().padStart(2, "0")}`;
61
- };
62
- const timeToMinutes = (time) => {
63
- return time.hour * 60 + time.minute;
64
- };
65
- const minutesToTime = (minutes) => {
66
- return {
67
- hour: Math.floor(minutes / 60),
68
- minute: minutes % 60,
69
- };
70
- };
71
- const addMinutes = (time, minutes) => {
72
- return minutesToTime(timeToMinutes(time) + minutes);
73
- };
74
- const floorTime = (time, intervalInMinutes) => {
75
- const minutes = timeToMinutes(time);
76
- const flooredMinutes = Math.floor(minutes / intervalInMinutes) * intervalInMinutes;
77
- return minutesToTime(flooredMinutes);
78
- };
79
- export const validateTime = (time) => {
80
- return time.hour >= 0 && time.hour <= 23 && time.minute >= 0 && time.minute <= 59;
81
- };
82
- const isTimeBefore = (time1, time2) => {
83
- return timeToMinutes(time1) < timeToMinutes(time2);
84
- };
85
- export const validateTimeRange = (range) => {
86
- if (range.start.isNone() || range.end.isNone()) {
87
- return true;
88
- }
89
- return isTimeBefore(range.start.value, range.end.value);
90
- };
91
- const minTime = (...times) => {
92
- const timesInMinutes = times.map(timeToMinutes);
93
- const minTimeInMinutes = Math.min(...timesInMinutes);
94
- return minutesToTime(minTimeInMinutes);
95
- };
96
- const maxTime = (...times) => {
97
- const timesInMinutes = times.map(timeToMinutes);
98
- const maxTimeInMinutes = Math.max(...timesInMinutes);
99
- return minutesToTime(maxTimeInMinutes);
100
- };
101
- const generateTimeList = (start, end, intervalInMinutes) => {
102
- const startMinutes = timeToMinutes(start);
103
- const endMinutes = timeToMinutes(end);
104
- const timeList = [];
105
- for (let i = startMinutes; i <= endMinutes; i += intervalInMinutes) {
106
- const time = minutesToTime(i);
107
- timeList.push(time);
108
- }
109
- return timeList;
110
- };
111
- const formatTimeDuration = (time, minutesLabel, hoursLabel) => {
112
- if (time.hour === 0) {
113
- return `${time.minute} ${minutesLabel}`;
114
- }
115
- if (time.minute === 0) {
116
- return `${time.hour} ${hoursLabel}`;
117
- }
118
- return `${time.hour} ${hoursLabel} ${time.minute} ${minutesLabel}`;
119
- };
120
- export const TimePicker = ({ value, onChangeText, suggestionStart = DEFAULT_START_TIME, suggestionEnd = DEFAULT_END_TIME, intervalInMinutes = DEFAULT_INTERVAL_IN_MINUTES, nbMaxSuggestions, suggestionSuffix, readOnly, disabled, error, hideErrors, noSuggestionLabel, }) => {
121
- const typingHour = parseTypingHours(value !== null && value !== void 0 ? value : "");
122
- const optionsInterval = useMemo(() => typingHour.match({
123
- Some: hour => [
124
- maxTime({ hour, minute: 0 }, suggestionStart),
125
- minTime({ hour, minute: 59 }, suggestionEnd),
126
- ],
127
- None: () => [suggestionStart, suggestionEnd],
128
- }), [typingHour, suggestionStart, suggestionEnd]);
129
- const options = useMemo(() => generateTimeList(optionsInterval[0], optionsInterval[1], intervalInMinutes).slice(0, nbMaxSuggestions), [optionsInterval, intervalInMinutes, nbMaxSuggestions]);
130
- const items = useMemo(() => AsyncData.Done(Result.Ok(options)), [options]);
131
- return (_jsx(Rifm, { value: value !== null && value !== void 0 ? value : "", onChange: onChangeText, ...rifmTimeProps, children: ({ value, onChange }) => (_jsx(LakeCombobox, { keyExtractor: stringifyTime, placeholder: "HH:MM", value: value, items: items, itemHeight: 40, nbItemsDisplayed: 4.5, renderItem: item => {
132
- const text = stringifyTime(item);
133
- const selected = text === value;
134
- const suffix = suggestionSuffix === null || suggestionSuffix === void 0 ? void 0 : suggestionSuffix(item);
135
- const label = isNotNullishOrEmpty(suffix) ? `${text} ${suffix}` : text;
136
- return (_jsxs(Box, { direction: "row", alignItems: "center", children: [_jsx(LakeText, { color: selected ? colors.gray[700] : colors.gray[900], variant: selected ? "smallRegular" : "medium", children: label }), _jsx(Fill, { minWidth: 8 }), selected && (_jsx(Icon, { name: "checkmark-filled", color: colors.positive[500], size: 16 }))] }));
137
- }, readOnly: readOnly, disabled: disabled, error: error, hideErrors: hideErrors, emptyResult: noSuggestionLabel, onChange: onChange, onValueChange: noop, onSelectItem: item => onChangeText(stringifyTime(item)) })) }));
138
- };
139
- export const TimeRangePicker = ({ value, onChange, intervalInMinutes = DEFAULT_INTERVAL_IN_MINUTES, nbMaxSuggestions, readOnly, disabled, error, startLabel, endLabel, minutesLabel, hoursLabel, noSuggestionLabel, }) => {
140
- var _a;
141
- const start = parseTime((_a = value.start) !== null && _a !== void 0 ? _a : "");
142
- const handleStartChange = (startValue) => {
143
- onChange({
144
- start: startValue,
145
- end: value.end,
146
- });
147
- };
148
- const handleEndChange = (endValue) => {
149
- onChange({
150
- start: value.start,
151
- end: endValue,
152
- });
153
- };
154
- return (_jsxs(View, { children: [_jsxs(Box, { direction: "row", alignItems: "end", children: [_jsx(LakeLabel, { label: startLabel, style: styles.label, render: () => (_jsx(TimePicker, { value: value.start, intervalInMinutes: intervalInMinutes, nbMaxSuggestions: nbMaxSuggestions, error: error, hideErrors: true, disabled: disabled, readOnly: readOnly, noSuggestionLabel: noSuggestionLabel, onChangeText: handleStartChange })) }), _jsx(Space, { width: 12 }), _jsx(Box, { style: styles.arrowContainer, justifyContent: "center", children: _jsx(Icon, { name: "arrow-right-filled", size: 20 }) }), _jsx(Space, { width: 12 }), _jsx(LakeLabel, { label: endLabel, style: styles.label, render: () => (_jsx(TimePicker, { value: value.end, intervalInMinutes: intervalInMinutes, nbMaxSuggestions: nbMaxSuggestions, suggestionStart: start
155
- .map(time => floorTime(time, intervalInMinutes))
156
- .map(time => addMinutes(time, intervalInMinutes))
157
- .match({
158
- Some: time => time,
159
- None: () => undefined,
160
- }), suggestionSuffix: time => {
161
- if (start.isNone()) {
162
- return "";
163
- }
164
- const durationInMinutes = timeToMinutes(time) - timeToMinutes(start.value);
165
- if (durationInMinutes > 0 && durationInMinutes <= 60) {
166
- const timeDuration = minutesToTime(durationInMinutes);
167
- return `(${formatTimeDuration(timeDuration, minutesLabel, hoursLabel)})`;
168
- }
169
- return "";
170
- }, error: error, hideErrors: true, disabled: disabled, readOnly: readOnly, noSuggestionLabel: noSuggestionLabel, onChangeText: handleEndChange })) })] }), _jsx(Space, { height: 4 }), _jsx(LakeText, { variant: "smallRegular", color: colors.negative[500], children: error !== null && error !== void 0 ? error : " " })] }));
171
- };
@@ -1,5 +0,0 @@
1
- export declare const useDisclosure: (initialValue: boolean) => [visible: boolean, fns: {
2
- open: () => void;
3
- close: () => void;
4
- toggle: () => void;
5
- }];
@@ -1,12 +0,0 @@
1
- import { useMemo, useState } from "react";
2
- export const useDisclosure = (initialValue) => {
3
- const [value, setValue] = useState(initialValue);
4
- return [
5
- value,
6
- useMemo(() => ({
7
- open: () => setValue(true),
8
- close: () => setValue(false),
9
- toggle: () => setValue(prevValue => !prevValue),
10
- }), []),
11
- ];
12
- };