@scripso-homepad/ui 0.4.27 → 0.4.29

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.27",
3
+ "version": "0.4.29",
4
4
  "type": "module",
5
5
  "description": "Cross-platform UI components for Homepad (React Web + React Native)",
6
6
  "license": "MIT",
@@ -57,7 +57,10 @@ export function ConfirmModal({
57
57
  return null;
58
58
  }
59
59
 
60
- const sharedButtonClassName = cn('btn h-10 min-w-0 flex-1 justify-center!', buttonClassName);
60
+ const sharedButtonClassName = cn(
61
+ 'btn h-10! w-full! min-w-0! justify-center!',
62
+ buttonClassName,
63
+ );
61
64
 
62
65
  return (
63
66
  <div
@@ -71,7 +74,7 @@ export function ConfirmModal({
71
74
  aria-labelledby="homepad-confirm-modal-title"
72
75
  aria-describedby="homepad-confirm-modal-description"
73
76
  className={cn(
74
- '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',
75
78
  containerClassName,
76
79
  )}
77
80
  onClick={(event) => {
@@ -98,7 +101,7 @@ export function ConfirmModal({
98
101
  </div>
99
102
  </header>
100
103
 
101
- <footer className="flex w-full gap-4">
104
+ <footer className="grid w-full grid-cols-2 gap-4">
102
105
  <Button
103
106
  title={cancelText}
104
107
  onPress={onCancel}
@@ -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
  )}
@@ -14,6 +14,8 @@ export type ModalProps = {
14
14
  subtitle?: string;
15
15
  icon?: ReactNode;
16
16
  iconContainerClassName?: string;
17
+ titleClassName?: string;
18
+ subtitleClassName?: string;
17
19
  children: ReactNode;
18
20
  cancelText: string;
19
21
  confirmText: string;
@@ -42,6 +44,8 @@ export function Modal({
42
44
  subtitle,
43
45
  icon,
44
46
  iconContainerClassName,
47
+ titleClassName,
48
+ subtitleClassName,
45
49
  children,
46
50
  cancelText,
47
51
  confirmText,
@@ -84,9 +88,8 @@ export function Modal({
84
88
  }
85
89
 
86
90
  const sharedButtonClassName = cn(
87
- 'btn h-13 w-full justify-center!',
88
- footerLayout === 'split' && 'min-w-0',
89
- footerLayout !== 'split' && buttonClassName,
91
+ 'btn h-13 w-full! min-w-0! justify-center!',
92
+ buttonClassName,
90
93
  );
91
94
 
92
95
  const modal = (
@@ -132,14 +135,17 @@ export function Modal({
132
135
  <div className="flex min-w-0 flex-col gap-2">
133
136
  <h2
134
137
  id="homepad-modal-title"
135
- className="typography-title-sm tracking-normal text-black"
138
+ className={cn('typography-title-sm tracking-normal text-black', titleClassName)}
136
139
  >
137
140
  {title}
138
141
  </h2>
139
142
  {subtitle ? (
140
143
  <p
141
144
  id="homepad-modal-subtitle"
142
- className="typography-caption tracking-normal text-storm-gray-400"
145
+ className={cn(
146
+ 'typography-caption tracking-normal text-storm-gray-400',
147
+ subtitleClassName,
148
+ )}
143
149
  >
144
150
  {subtitle}
145
151
  </p>
@@ -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}