@butternutbox/pawprint-native 0.10.7 → 0.10.9

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": "@butternutbox/pawprint-native",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "type": "module",
5
5
  "description": "ButternutBox Pawprint Design System - React Native Components",
6
6
  "main": "./dist/index.cjs",
@@ -129,34 +129,36 @@ export type DatePickerProps = DatePickerOwnProps &
129
129
  Omit<ViewProps, keyof DatePickerOwnProps>
130
130
 
131
131
  const StyledContainer = styled(View)<{
132
- containerGap: number
133
132
  containerMaxWidth: number
134
- }>(({ containerGap, containerMaxWidth }) => ({
133
+ }>(({ containerMaxWidth }) => ({
135
134
  flexDirection: "column",
136
- gap: containerGap,
135
+ // Fill the parent by default (capped by maxWidth) so consumers don't need a
136
+ // wrapper. Overridable via the `style` prop.
137
+ width: "100%",
137
138
  maxWidth: containerMaxWidth
138
139
  }))
139
140
 
140
- const StyledMonthHeader = styled(View)<{ headerGap: number }>(
141
- ({ headerGap }) => ({
141
+ const StyledMonthHeader = styled(View)<{ headerGap: number; datesGap: number }>(
142
+ ({ headerGap, datesGap }) => ({
142
143
  flexDirection: "row",
143
144
  alignItems: "center",
144
- gap: headerGap
145
+ gap: headerGap,
146
+ // Gap between the month header and the dates grid.
147
+ marginBottom: datesGap
145
148
  })
146
149
  )
147
150
 
148
- const StyledWeekRow = styled(View)({
149
- flexDirection: "row"
150
- })
151
+ const StyledWeekRow = styled(View)<{ weekGap: number }>(({ weekGap }) => ({
152
+ flexDirection: "row",
153
+ // Gap between the week-day header and the month header.
154
+ marginBottom: weekGap
155
+ }))
151
156
 
152
- const StyledWeekCell = styled(View)<{ cellHeight: number }>(
153
- ({ cellHeight }) => ({
154
- flex: 1,
155
- alignItems: "center",
156
- justifyContent: "center",
157
- height: cellHeight
158
- })
159
- )
157
+ const StyledWeekCell = styled(View)({
158
+ flex: 1,
159
+ alignItems: "center",
160
+ justifyContent: "center"
161
+ })
160
162
 
161
163
  const StyledDatesGrid = styled(View)<{ rowGap: number }>(({ rowGap }) => ({
162
164
  flexDirection: "column",
@@ -280,7 +282,8 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
280
282
  const [slideDir, setSlideDir] = useState<"forward" | "backward">("forward")
281
283
 
282
284
  const {
283
- containerGap,
285
+ weekGap,
286
+ datesGap,
284
287
  containerMaxWidth,
285
288
  headerGap,
286
289
  cellHeight,
@@ -291,7 +294,10 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
291
294
  rowGap
292
295
  } = useMemo(
293
296
  () => ({
294
- containerGap: parseTokenValue(datePicker.spacing.gap),
297
+ // Gap between the week-day header and the month header.
298
+ weekGap: parseTokenValue(datePicker.spacing.gap),
299
+ // Gap between the month header and the dates grid.
300
+ datesGap: parseTokenValue(theme.tokens.semantics.dimensions.spacing.md),
295
301
  containerMaxWidth: parseTokenValue(dt.size.width) * 7,
296
302
  headerGap: parseTokenValue(datePicker.month.spacing.gap),
297
303
  cellHeight: parseTokenValue(dt.size.height),
@@ -301,7 +307,7 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
301
307
  disabledOpacity: parseTokenValue(dt.opacity.disabled),
302
308
  rowGap: parseTokenValue(datePicker.dates.spacing.gap)
303
309
  }),
304
- [datePicker, dt]
310
+ [datePicker, dt, theme]
305
311
  )
306
312
 
307
313
  const resolvedGetDateState = useCallback(
@@ -423,14 +429,13 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
423
429
  return (
424
430
  <StyledContainer
425
431
  ref={ref}
426
- containerGap={containerGap}
427
432
  containerMaxWidth={containerMaxWidth}
428
433
  {...rest}
429
434
  >
430
435
  {showWeekHeader && (
431
- <StyledWeekRow accessibilityElementsHidden>
436
+ <StyledWeekRow weekGap={weekGap} accessibilityElementsHidden>
432
437
  {weekDayLabels.map((label) => (
433
- <StyledWeekCell key={label} cellHeight={cellHeight}>
438
+ <StyledWeekCell key={label}>
434
439
  <Typography
435
440
  token={datePicker.week.typography}
436
441
  align="center"
@@ -443,12 +448,12 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
443
448
  </StyledWeekRow>
444
449
  )}
445
450
 
446
- <StyledMonthHeader headerGap={headerGap}>
451
+ <StyledMonthHeader headerGap={headerGap} datesGap={datesGap}>
447
452
  <View style={{ opacity: showPrevControl ? 1 : 0 }}>
448
453
  <IconButton
449
454
  icon={KeyboardArrowLeft}
450
455
  variant="filled"
451
- size="sm"
456
+ size="md"
452
457
  colour="primary"
453
458
  aria-label="Previous month"
454
459
  onPress={showPrevControl ? handlePrevMonth : undefined}
@@ -470,7 +475,7 @@ export const DatePicker = React.forwardRef<View, DatePickerProps>(
470
475
  <IconButton
471
476
  icon={KeyboardArrowRight}
472
477
  variant="filled"
473
- size="sm"
478
+ size="md"
474
479
  colour="primary"
475
480
  aria-label="Next month"
476
481
  onPress={showNextControl ? handleNextMonth : undefined}
@@ -205,6 +205,55 @@ export const LongContent = () => (
205
205
  </View>
206
206
  )
207
207
 
208
+ // Exercises the panel re-fit: toggling the extra paragraphs grows/shrinks the
209
+ // body after the drawer has opened. The panel should track the content —
210
+ // growing (up to its max, then scrolling) and shrinking back — never leaving a
211
+ // gap or clipping. Regression guard for the 0.10.8 re-measure feedback loop.
212
+ export const DynamicContent = () => {
213
+ const [expanded, setExpanded] = useState(false)
214
+ return (
215
+ <View style={styles.container}>
216
+ <Drawer.Root>
217
+ <Drawer.Trigger>
218
+ <Button variant="filled" colour="primary">
219
+ Open dynamic drawer
220
+ </Button>
221
+ </Drawer.Trigger>
222
+ <Drawer.Portal>
223
+ <Drawer.Overlay />
224
+ <Drawer.Content>
225
+ <Drawer.Header variant="titleAndText">
226
+ <Drawer.Title>Delivery details</Drawer.Title>
227
+ <Drawer.Close />
228
+ </Drawer.Header>
229
+ <Drawer.Body>
230
+ <Typography>{bodyText}</Typography>
231
+ <Button
232
+ variant="outlined"
233
+ colour="secondary"
234
+ onPress={() => setExpanded((prev) => !prev)}
235
+ >
236
+ {expanded ? "Show less" : "Show more"}
237
+ </Button>
238
+ {expanded &&
239
+ Array.from({ length: 12 }, (_, i) => (
240
+ <Typography
241
+ key={i}
242
+ >{`More detail ${i + 1}: ${bodyText}`}</Typography>
243
+ ))}
244
+ </Drawer.Body>
245
+ <Drawer.Footer>
246
+ <Button variant="filled" colour="primary">
247
+ Confirm
248
+ </Button>
249
+ </Drawer.Footer>
250
+ </Drawer.Content>
251
+ </Drawer.Portal>
252
+ </Drawer.Root>
253
+ </View>
254
+ )
255
+ }
256
+
208
257
  export const Controlled = () => {
209
258
  const [open, setOpen] = useState(false)
210
259
  return (
@@ -1,4 +1,4 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
1
+ import React, { useCallback, useEffect, useMemo, useRef } from "react"
2
2
  import {
3
3
  Animated,
4
4
  Easing,
@@ -35,7 +35,7 @@ const StyledPanel = styled(View)<{
35
35
  panelShadowColor: string
36
36
  panelShadowOffsetY: number
37
37
  panelShadowBlur: number
38
- panelFlex?: number
38
+ panelMaxHeight: number
39
39
  }>(
40
40
  ({
41
41
  panelBorderRadius,
@@ -46,12 +46,17 @@ const StyledPanel = styled(View)<{
46
46
  panelShadowColor,
47
47
  panelShadowOffsetY,
48
48
  panelShadowBlur,
49
- panelFlex
49
+ panelMaxHeight
50
50
  }) => ({
51
51
  width: "100%",
52
52
  maxWidth: panelMaxWidth,
53
+ // The panel wraps its content and caps at maxHeight. It is intentionally
54
+ // NOT given a fixed height: letting it size to content means it re-fits
55
+ // automatically whenever the body grows or shrinks (async load, expanding
56
+ // sections). The body's own maxHeight (see DrawerBody) bounds scrolling and
57
+ // keeps the ScrollView from collapsing, so no fixed panel height is needed.
58
+ maxHeight: panelMaxHeight,
53
59
  alignSelf: "center",
54
- ...(panelFlex != null ? { flex: panelFlex } : undefined),
55
60
  borderTopLeftRadius: panelBorderRadius,
56
61
  borderTopRightRadius: panelBorderRadius,
57
62
  borderTopWidth: panelBorderWidth,
@@ -114,12 +119,6 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
114
119
  const isOpenRef = useRef(isOpen)
115
120
  const activeAnim = useRef<Animated.CompositeAnimation | null>(null)
116
121
 
117
- // After the first onLayout we know the real rendered height (capped at
118
- // MAX_HEIGHT). We immediately kick off the entry animation from that height
119
- // and then set explicit height + flex: 1 on the panel so DrawerBody's
120
- // ScrollView gets a bounded container and can scroll correctly.
121
- const [panelHeight, setPanelHeight] = useState(0)
122
-
123
122
  useEffect(() => {
124
123
  const id = translateY.addListener(({ value }) => {
125
124
  translateYValue.current = value
@@ -127,15 +126,6 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
127
126
  return () => translateY.removeListener(id)
128
127
  }, [translateY])
129
128
 
130
- // Clamp the cached panel height when the window shrinks (e.g. orientation
131
- // change) so the entry animation never starts from below the screen.
132
- useEffect(() => {
133
- if (panelHeightRef.current > 0 && panelHeightRef.current > maxHeight) {
134
- setPanelHeight(maxHeight)
135
- panelHeightRef.current = maxHeight
136
- }
137
- }, [maxHeight])
138
-
139
129
  const runEntry = useCallback(() => {
140
130
  if (panelHeightRef.current === 0) return
141
131
  translateY.setValue(panelHeightRef.current)
@@ -187,21 +177,17 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
187
177
  ({ nativeEvent }: { nativeEvent: { layout: { height: number } } }) => {
188
178
  const h = nativeEvent.layout.height
189
179
  const capped = Math.min(h, maxHeight)
180
+ if (capped <= 0) return
190
181
 
191
- // Only update state when the effective height actually changes, to
192
- // avoid re-render loops. Compare against the ref (kept in sync with
193
- // state) so panelHeight doesn't need to be a dep of this callback.
194
- if (capped !== panelHeightRef.current && capped > 0) {
195
- setPanelHeight(capped)
196
- panelHeightRef.current = capped
197
- }
182
+ // The panel wraps its content, so this fires again whenever the body
183
+ // grows or shrinks. Keep the cached height current for the entry/exit
184
+ // and drag-dismiss animations; the panel re-fits itself via layout, so
185
+ // there is nothing else to do here (no state, no feedback loop).
186
+ panelHeightRef.current = capped
198
187
 
199
- // Run entry on the first measurement. The animation starts from the
200
- // capped height even though the panel may still be at natural size;
201
- // the re-render that sets the explicit height happens while the panel
202
- // is still off-screen, so there is no visible jump.
203
- if (isOpenRef.current && !entryRanRef.current && capped > 0) {
204
- panelHeightRef.current = capped
188
+ // Kick off the entry animation on the first measurement. translateY is
189
+ // still off-screen (9999) at this point, so there is no visible jump.
190
+ if (isOpenRef.current && !entryRanRef.current) {
205
191
  runEntry()
206
192
  }
207
193
  },
@@ -294,11 +280,7 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
294
280
  <DrawerFooterContext.Provider value={hasFooter}>
295
281
  <DrawerDragContext.Provider value={dragContextValue}>
296
282
  <Animated.View
297
- style={[
298
- styles.animatedWrapper,
299
- panelHeight > 0 ? { height: panelHeight } : undefined,
300
- { transform: [{ translateY }] }
301
- ]}
283
+ style={[styles.animatedWrapper, { transform: [{ translateY }] }]}
302
284
  >
303
285
  <StyledPanel
304
286
  ref={ref}
@@ -306,7 +288,7 @@ export const DrawerContent = React.forwardRef<View, DrawerContentProps>(
306
288
  panelBorderRadius={parseTokenValue(drawer.borderRadius.top)}
307
289
  panelBgColor={colour.background.container.default}
308
290
  panelMaxWidth={parseTokenValue(drawer.size.maxWidth)}
309
- panelFlex={panelHeight > 0 ? 1 : undefined}
291
+ panelMaxHeight={maxHeight}
310
292
  panelBorderWidth={parseTokenValue(dimensions.borderWidth.sm)}
311
293
  panelBorderColor={colour.border.default}
312
294
  panelShadowColor={modal.shadow.color}
@@ -96,6 +96,9 @@ const StyledIndicator = styled(View)<{
96
96
  const StyledTextContent = styled(View)<{
97
97
  textContentGap: number
98
98
  }>(({ textContentGap }) => ({
99
+ // Take the row's remaining width so long label/subText wraps within the tile
100
+ // instead of pushing the trailing asset past the tile's edge.
101
+ flex: 1,
99
102
  flexDirection: "column",
100
103
  gap: textContentGap
101
104
  }))