@scripso-homepad/ui 0.4.28 → 0.4.30

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": "@scripso-homepad/ui",
3
- "version": "0.4.28",
3
+ "version": "0.4.30",
4
4
  "type": "module",
5
5
  "description": "Cross-platform UI components for Homepad (React Web + React Native)",
6
6
  "license": "MIT",
@@ -42,6 +42,10 @@ export interface CountryCodeSelectorProps {
42
42
  /** Country list (defaults to built-in list). */
43
43
  options?: Country[];
44
44
  disabled?: boolean;
45
+ /** Outer trigger height. Defaults to shared input height. */
46
+ height?: number;
47
+ /** Trigger border color. Defaults to shared input border token. */
48
+ borderColor?: string;
45
49
  style?: StyleProp<ViewStyle>;
46
50
  className?: string;
47
51
  }
@@ -70,6 +74,8 @@ export function CountryCodeSelector({
70
74
  onValueChange,
71
75
  options = countries,
72
76
  disabled = false,
77
+ height = TRIGGER_HEIGHT,
78
+ borderColor = colors.stormGray50,
73
79
  style,
74
80
  className,
75
81
  }: CountryCodeSelectorProps) {
@@ -157,6 +163,7 @@ export function CountryCodeSelector({
157
163
  onPress={handleOpen}
158
164
  style={[
159
165
  styles.trigger,
166
+ { height, borderColor },
160
167
  disabled ? styles.triggerDisabled : styles.triggerEnabled,
161
168
  Platform.OS === "web" ? styles.triggerWeb : null,
162
169
  ]}
@@ -207,15 +214,14 @@ const styles = StyleSheet.create({
207
214
  height: TRIGGER_HEIGHT,
208
215
  borderRadius: radii.lg,
209
216
  borderWidth: 1,
210
- padding: spacing.lg,
217
+ paddingHorizontal: spacing.lg,
218
+ paddingVertical: 0,
211
219
  gap: spacing.sm,
212
220
  },
213
221
  triggerEnabled: {
214
- borderColor: colors.stormGray50,
215
222
  backgroundColor: colors.white,
216
223
  },
217
224
  triggerDisabled: {
218
- borderColor: colors.stormGray50,
219
225
  backgroundColor: colors.stormGray50,
220
226
  },
221
227
  triggerWeb: {
@@ -16,6 +16,7 @@ import { Label } from "./Label";
16
16
  import {
17
17
  getInputFieldStyles,
18
18
  inputFieldMetrics,
19
+ INPUT_HEIGHT,
19
20
  INPUT_ICON_GAP,
20
21
  INPUT_OUTLINE_WIDTH,
21
22
  resolveInputVisualState,
@@ -38,6 +39,8 @@ export interface PhoneInputProps extends Omit<TextInputProps, "style"> {
38
39
  onCountryCodeChange?: (countryCode: string) => void;
39
40
  error?: string;
40
41
  hint?: string;
42
+ /** Outer field height for country trigger + national number. Defaults to shared input height. */
43
+ height?: number;
41
44
  containerStyle?: StyleProp<ViewStyle>;
42
45
  style?: StyleProp<TextStyle>;
43
46
  className?: string;
@@ -60,6 +63,7 @@ export function PhoneInput({
60
63
  onCountryCodeChange,
61
64
  error,
62
65
  hint,
66
+ height = INPUT_HEIGHT,
63
67
  containerStyle,
64
68
  style,
65
69
  className,
@@ -124,6 +128,7 @@ export function PhoneInput({
124
128
  value={countryCode}
125
129
  onValueChange={onCountryCodeChange}
126
130
  disabled={isDisabled}
131
+ height={height}
127
132
  style={styles.countrySelector}
128
133
  />
129
134
  </View>
@@ -132,6 +137,7 @@ export function PhoneInput({
132
137
  style={[
133
138
  inputFieldMetrics.containerBase,
134
139
  inputFieldMetrics.container,
140
+ { height },
135
141
  phoneFieldStyles.container,
136
142
  styles.phoneField,
137
143
  ]}
@@ -74,7 +74,7 @@ export function ConfirmModal({
74
74
  aria-labelledby="homepad-confirm-modal-title"
75
75
  aria-describedby="homepad-confirm-modal-description"
76
76
  className={cn(
77
- 'flex w-full max-w-md flex-col gap-8 rounded-2xl bg-white p-4 opacity-100 md:p-6',
77
+ 'flex w-full max-w-md flex-col gap-8 rounded-3xl bg-white p-6 opacity-100 md:p-8',
78
78
  containerClassName,
79
79
  )}
80
80
  onClick={(event) => {
@@ -71,10 +71,12 @@ export function Drawer({
71
71
  }: DrawerProps) {
72
72
  const [isPresent, setIsPresent] = useState(open);
73
73
  const [isAnimatedIn, setIsAnimatedIn] = useState(false);
74
+ const [isTransformSettled, setIsTransformSettled] = useState(open);
74
75
 
75
76
  useEffect(() => {
76
77
  if (open) {
77
78
  setIsPresent(true);
79
+ setIsTransformSettled(false);
78
80
 
79
81
  const frame = window.requestAnimationFrame(() => {
80
82
  window.requestAnimationFrame(() => {
@@ -82,18 +84,32 @@ export function Drawer({
82
84
  });
83
85
  });
84
86
 
87
+ const settleTimeout = window.setTimeout(() => {
88
+ setIsTransformSettled(true);
89
+ }, DRAWER_TRANSITION_MS);
90
+
85
91
  return () => {
86
92
  window.cancelAnimationFrame(frame);
93
+ window.clearTimeout(settleTimeout);
87
94
  };
88
95
  }
89
96
 
90
- setIsAnimatedIn(false);
97
+ // Re-enable transform at the open position, then slide out.
98
+ setIsTransformSettled(false);
99
+ setIsAnimatedIn(true);
100
+
101
+ const frame = window.requestAnimationFrame(() => {
102
+ window.requestAnimationFrame(() => {
103
+ setIsAnimatedIn(false);
104
+ });
105
+ });
91
106
 
92
107
  const timeout = window.setTimeout(() => {
93
108
  setIsPresent(false);
94
109
  }, DRAWER_TRANSITION_MS);
95
110
 
96
111
  return () => {
112
+ window.cancelAnimationFrame(frame);
97
113
  window.clearTimeout(timeout);
98
114
  };
99
115
  }, [open]);
@@ -144,8 +160,14 @@ export function Drawer({
144
160
  aria-labelledby="homepad-drawer-title"
145
161
  {...(subtitle ? { 'aria-describedby': 'homepad-drawer-subtitle' } : {})}
146
162
  className={cn(
147
- 'relative z-10 flex h-[calc(100dvh-16px)] min-h-0 flex-col overflow-hidden rounded-[16px] bg-white shadow-2xl transition-transform duration-300 ease-in-out will-change-transform max-md:h-dvh max-md:rounded-none',
148
- isAnimatedIn ? 'translate-x-0' : 'translate-x-full',
163
+ 'relative z-10 flex h-[calc(100dvh-16px)] min-h-0 flex-col overflow-hidden rounded-[16px] bg-white shadow-2xl transition-transform duration-300 ease-in-out max-md:h-dvh max-md:rounded-none',
164
+ // Keep GPU transform only while animating — settled translate-x-0 blurs text.
165
+ !isTransformSettled && 'will-change-transform',
166
+ isAnimatedIn
167
+ ? isTransformSettled
168
+ ? 'transform-none'
169
+ : 'translate-x-0'
170
+ : 'translate-x-full',
149
171
  widthClassName,
150
172
  className,
151
173
  )}
@@ -25,6 +25,8 @@ export type AppHeaderProps = {
25
25
  notificationsIcon?: ReactNode;
26
26
  searchIcon?: ReactNode;
27
27
  buildingChevronIcon?: ReactNode;
28
+ /** Rendered before the building select (e.g. month period control). */
29
+ actions?: ReactNode;
28
30
  buildingSelectDisabled?: boolean;
29
31
  searchDisabled?: boolean;
30
32
  notificationsDisabled?: boolean;
@@ -60,6 +62,7 @@ export function AppHeader({
60
62
  notificationsIcon,
61
63
  searchIcon,
62
64
  buildingChevronIcon,
65
+ actions,
63
66
  buildingSelectDisabled = false,
64
67
  searchDisabled = false,
65
68
  notificationsDisabled = false,
@@ -77,6 +80,8 @@ export function AppHeader({
77
80
  <h1 className="truncate typography-title text-slate-blue">{title}</h1>
78
81
 
79
82
  <div className="flex shrink-0 items-center gap-3">
83
+ {actions}
84
+
80
85
  {hasBuildingSelect ? (
81
86
  <BuildingSelect
82
87
  options={buildingOptions}
@@ -38,7 +38,9 @@ export function SidebarNavItem({ item, isOpen, isActive, onNavigate }: SidebarNa
38
38
  'relative flex items-center rounded-xl px-4 py-3 transition-[background-color,color,gap,padding] duration-300 ease-in-out',
39
39
  isOpen ? 'w-full gap-4' : 'w-full justify-center gap-0 px-3',
40
40
  item.disabled && 'pointer-events-none opacity-50',
41
- isActive ? 'bg-black/12 text-white shadow-none' : 'text-navy-100 shadow-none hover:bg-white/5',
41
+ isActive
42
+ ? 'bg-black/12 text-white shadow-none'
43
+ : 'text-navy-100 shadow-none hover:bg-white/5',
42
44
  )}
43
45
  >
44
46
  <span