@swan-io/lake 8.12.0 → 8.13.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": "8.12.0",
3
+ "version": "8.13.0",
4
4
  "engines": {
5
5
  "node": ">=20.9.0",
6
6
  "yarn": "^1.22.0"
@@ -21,6 +21,7 @@ export type ButtonProps = {
21
21
  href?: string;
22
22
  hrefAttrs?: HrefAttrs;
23
23
  pill?: boolean;
24
+ disabledUntil?: string;
24
25
  } & ({
25
26
  ariaLabel: string;
26
27
  children?: never;
@@ -1,7 +1,8 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Children, forwardRef, Fragment, memo } from "react";
2
+ import { Option } from "@swan-io/boxed";
3
+ import { Children, forwardRef, Fragment, memo, useEffect, useState } from "react";
3
4
  import { ActivityIndicator, StyleSheet, View, } from "react-native";
4
- import { match } from "ts-pattern";
5
+ import { match, P } from "ts-pattern";
5
6
  import { commonStyles } from "../constants/commonStyles";
6
7
  import { backgroundColor, colors, invariantColors, radii, spacings, texts, } from "../constants/design";
7
8
  import { isNotNullish, isNullish } from "../utils/nullish";
@@ -52,6 +53,12 @@ const styles = StyleSheet.create({
52
53
  backgroundColor: colors.gray[900],
53
54
  borderRadius: radii[6],
54
55
  },
56
+ loaderContainerProgress: {
57
+ opacity: 0.5,
58
+ backgroundColor: colors.gray[900],
59
+ transformOrigin: "0 0",
60
+ ...StyleSheet.absoluteFillObject,
61
+ },
55
62
  small: {
56
63
  height: 40,
57
64
  paddingLeft: 16,
@@ -83,6 +90,8 @@ const styles = StyleSheet.create({
83
90
  alignItems: "center",
84
91
  justifyContent: "center",
85
92
  transform: "translateZ(0px)",
93
+ borderRadius: radii[6],
94
+ overflow: "hidden",
86
95
  },
87
96
  group: {
88
97
  flexDirection: "row",
@@ -112,7 +121,8 @@ const styles = StyleSheet.create({
112
121
  visibility: "hidden",
113
122
  },
114
123
  });
115
- export const LakeButton = memo(forwardRef(({ ariaControls, ariaExpanded, ariaLabel, children, color = "gray", direction = "row", disabled = false, icon, grow = false, iconPosition = "start", loading = false, mode = "primary", onPress, size = "large", iconSize = size === "small" ? 18 : 20, style, forceBackground = false, href, hrefAttrs, pill = false, }, forwardedRef) => {
124
+ export const LakeButton = memo(forwardRef(({ ariaControls, ariaExpanded, ariaLabel, children, color = "gray", direction = "row", disabled = false, icon, grow = false, iconPosition = "start", loading = false, mode = "primary", onPress, size = "large", iconSize = size === "small" ? 18 : 20, style, forceBackground = false, href, hrefAttrs, pill = false, disabledUntil, }, forwardedRef) => {
125
+ const [progressAnimation, setProgressAnimation] = useState(() => Option.None());
116
126
  const isPrimary = mode === "primary";
117
127
  const isSmall = size === "small";
118
128
  const vertical = direction === "column";
@@ -122,14 +132,43 @@ export const LakeButton = memo(forwardRef(({ ariaControls, ariaExpanded, ariaLab
122
132
  const hasIconStart = hasIcon && iconPosition === "start";
123
133
  const hasIconEnd = hasIcon && iconPosition === "end";
124
134
  const hasOnlyIcon = hasIcon && isNullish(children);
125
- return (_jsx(Pressable, { href: href, hrefAttrs: hrefAttrs, role: href != null ? "link" : "button", "aria-busy": loading, "aria-disabled": disabled, "aria-controls": ariaControls, "aria-expanded": ariaExpanded, "aria-label": ariaLabel, disabled: loading || disabled, ref: forwardedRef, onPress: onPress, style: ({ hovered, pressed, focused }) => [
135
+ useEffect(() => {
136
+ if (disabledUntil == null) {
137
+ return;
138
+ }
139
+ const startTime = Date.now();
140
+ const endTime = new Date(disabledUntil).getTime();
141
+ const config = {
142
+ startTime,
143
+ duration: endTime - startTime,
144
+ endTime,
145
+ };
146
+ const tick = () => {
147
+ const now = Date.now();
148
+ if (now >= config.endTime) {
149
+ setProgressAnimation(Option.None());
150
+ }
151
+ else {
152
+ setProgressAnimation(Option.Some({
153
+ startTime: config.startTime,
154
+ duration: config.duration,
155
+ now: Date.now(),
156
+ }));
157
+ animationFrameId = requestAnimationFrame(tick);
158
+ }
159
+ };
160
+ let animationFrameId = requestAnimationFrame(tick);
161
+ return () => cancelAnimationFrame(animationFrameId);
162
+ }, [disabledUntil]);
163
+ const shouldHideContents = loading || progressAnimation.isSome();
164
+ return (_jsx(Pressable, { href: href, hrefAttrs: hrefAttrs, role: href != null ? "link" : "button", "aria-busy": loading, "aria-disabled": disabled, "aria-controls": ariaControls, "aria-expanded": ariaExpanded, "aria-label": ariaLabel, disabled: loading || disabled || progressAnimation.isSome(), ref: forwardedRef, onPress: onPress, style: ({ hovered, pressed, focused }) => [
126
165
  styles.base,
127
166
  isSmall && styles.small,
128
167
  vertical && [styles.vertical, isSmall && styles.verticalSmall],
129
168
  hasIconStart && isSmall ? styles.withIconStartSmall : styles.withIconStart,
130
169
  hasIconEnd && (isSmall ? styles.withIconEndSmall : styles.withIconEnd),
131
170
  hasOnlyIcon && (isSmall ? styles.iconSmallOnly : styles.iconOnly),
132
- disabled && commonStyles.disabled,
171
+ (disabled || progressAnimation.isSome()) && commonStyles.disabled,
133
172
  disabled && forceBackground && styles.resetOpacity,
134
173
  grow && styles.grow,
135
174
  match(mode)
@@ -174,11 +213,16 @@ export const LakeButton = memo(forwardRef(({ ariaControls, ariaExpanded, ariaLab
174
213
  : hovered || pressed
175
214
  ? colors[color][700]
176
215
  : colors[color][600];
177
- return (_jsxs(_Fragment, { children: [hasIconStart && (_jsxs(_Fragment, { children: [_jsx(Icon, { color: textColor, name: icon, size: iconSize, style: loading && styles.hidden }), isNotNullish(children) && _jsx(Space, { height: spaceHeight, width: spaceWidth })] })), typeof children === "string" || typeof children === "number" ? (_jsx(LakeText, { numberOfLines: 1, userSelect: "none", style: [
216
+ return (_jsxs(_Fragment, { children: [hasIconStart && (_jsxs(_Fragment, { children: [_jsx(Icon, { color: textColor, name: icon, size: iconSize, style: shouldHideContents && styles.hidden }), isNotNullish(children) && _jsx(Space, { height: spaceHeight, width: spaceWidth })] })), typeof children === "string" || typeof children === "number" ? (_jsx(LakeText, { numberOfLines: 1, userSelect: "none", style: [
178
217
  isSmall ? styles.textSmall : styles.text,
179
- loading && styles.hidden,
218
+ shouldHideContents && styles.hidden,
180
219
  { color: textColor },
181
- ], children: children })) : (_jsx(Box, { alignItems: "center", direction: "row", justifyContent: "center", style: [vertical && styles.vertical, loading && styles.hidden], children: children })), hasIconEnd && (_jsxs(_Fragment, { children: [isNotNullish(children) && _jsx(Space, { height: spaceHeight, width: spaceWidth }), _jsx(Icon, { color: textColor, name: icon, size: iconSize, style: loading && styles.hidden })] })), loading && (_jsx(View, { style: styles.loaderContainer, children: _jsx(ActivityIndicator, { color: isPrimary ? colors[color].contrast : colors[color].primary, size: iconSize }) })), isPrimary && _jsx(View, { style: [styles.hiddenView, pressed && styles.pressed] }), pill && _jsx(View, { style: styles.pill })] }));
220
+ ], children: children })) : (_jsx(Box, { alignItems: "center", direction: "row", justifyContent: "center", style: [vertical && styles.vertical, shouldHideContents && styles.hidden], children: children })), hasIconEnd && (_jsxs(_Fragment, { children: [isNotNullish(children) && _jsx(Space, { height: spaceHeight, width: spaceWidth }), _jsx(Icon, { color: textColor, name: icon, size: iconSize, style: shouldHideContents && styles.hidden })] })), loading && (_jsx(View, { style: styles.loaderContainer, children: _jsx(ActivityIndicator, { color: isPrimary ? colors[color].contrast : colors[color].primary, size: iconSize }) })), isPrimary && _jsx(View, { style: [styles.hiddenView, pressed && styles.pressed] }), match(progressAnimation)
221
+ .with(Option.P.Some(P.select()), ({ startTime, now, duration }) => (_jsxs(View, { style: styles.loaderContainer, children: [_jsx(View, { style: [
222
+ styles.loaderContainerProgress,
223
+ { transform: `scaleX(${((now - startTime) / duration) * 100}%)` },
224
+ ] }), _jsx(LakeText, { color: isPrimary ? colors[color].contrast : colors[color].primary, children: Math.ceil((duration - (now - startTime)) / 1000) })] })))
225
+ .otherwise(() => null), pill && _jsx(View, { style: styles.pill })] }));
182
226
  } }));
183
227
  }));
184
228
  LakeButton.displayName = "Button";