@remit/ui 0.0.52 → 0.0.53

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": "@remit/ui",
3
- "version": "0.0.52",
3
+ "version": "0.0.53",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "src"
@@ -120,6 +120,36 @@ describe("SelectionSheet", () => {
120
120
  assert.doesNotMatch(html, /Select similar messages/);
121
121
  });
122
122
 
123
+ /**
124
+ * jsdom lays nothing out, so these assert the constraint itself rather than a
125
+ * measured height: the sheet must not carry a fixed pixel ceiling (which sat
126
+ * below its own content at 411×759 and clipped the last action), and its body
127
+ * must scroll rather than clip whatever the ceiling does cut off (#405).
128
+ */
129
+ it("caps the expanded height against the viewport, not a fixed pixel ceiling", () => {
130
+ const html = renderToString(
131
+ createElement(SelectionSheet, { ...base, startExpanded: true }),
132
+ );
133
+ assert.match(html, /max-height:70dvh/);
134
+ assert.doesNotMatch(html, /max-height:min\(/);
135
+ });
136
+
137
+ it("scrolls the expanded actions instead of clipping them", () => {
138
+ const html = renderToString(
139
+ createElement(SelectionSheet, {
140
+ ...base,
141
+ startExpanded: true,
142
+ onSelectSimilar: noop,
143
+ onSomethingElse: noop,
144
+ }),
145
+ );
146
+ assert.match(html, /class="flex min-h-0 flex-1 flex-col overflow-y-auto /);
147
+ assert.doesNotMatch(
148
+ html,
149
+ /class="flex min-h-0 flex-1 flex-col overflow-hidden /,
150
+ );
151
+ });
152
+
123
153
  it("renders a partial-failure notice with a Retry action", () => {
124
154
  const html = renderToString(
125
155
  createElement(SelectionSheet, {
@@ -66,6 +66,27 @@ export const Expanded: Story = {
66
66
  args: { startExpanded: true, moveSlot: <MoveSlot /> },
67
67
  };
68
68
 
69
+ /**
70
+ * The same expanded sheet on the shortest phone viewport a real device reports
71
+ * (411×759 — Android Chrome with its address bar and system nav showing). This
72
+ * is the tallest the content ever gets: select-all, all three quick actions and
73
+ * both smart-flow rows. A fixed height ceiling cut "Something else" off here
74
+ * with no way to reach it (#405); the sheet now takes the height its content
75
+ * needs, and scrolls only when the viewport genuinely can't hold it.
76
+ */
77
+ export const ExpandedShortViewport: Story = {
78
+ globals: { viewport: { value: "mobileShort" } },
79
+ args: {
80
+ startExpanded: true,
81
+ moveSlot: <MoveSlot />,
82
+ selectAll: {
83
+ checked: false,
84
+ indeterminate: true,
85
+ onChange: () => undefined,
86
+ },
87
+ },
88
+ };
89
+
69
90
  /** While a search result set pages to its total: the count isn't known, the
70
91
  * quick actions are replaced by the running total and an explicit Stop. */
71
92
  export const Counting: Story = {
@@ -24,9 +24,13 @@ const formatCount = (n: number): string => n.toLocaleString();
24
24
 
25
25
  /** The peek height of the collapsed teaser row (px). */
26
26
  export const SELECTION_SHEET_TEASER_HEIGHT = 56;
27
- /** Ceiling on the expanded sheet height (px); CSS clamps to a third of the
28
- * viewport below this. */
29
- const EXPANDED_MAX = 320;
27
+ /** Ceiling on the expanded sheet height, as a share of the dynamic viewport.
28
+ * The sheet is otherwise sized by its own content, so the ceiling binds only on
29
+ * a viewport too short to hold the actions — and there they scroll. */
30
+ const EXPANDED_MAX_DVH = 70;
31
+ /** Assumed expanded height until the sheet has been measured; only sets the
32
+ * collapsed translate for the first frame. */
33
+ const EXPANDED_HEIGHT_FALLBACK = 320;
30
34
 
31
35
  const SNAP_MS = 320;
32
36
  const SNAP_EASE = "cubic-bezier(0.32, 0.9, 0.3, 1)";
@@ -131,11 +135,16 @@ export interface SelectionSheetProps {
131
135
 
132
136
  /**
133
137
  * The mobile multi-select surface: a peeking bottom sheet that teases at ~56px
134
- * with the selection count, and expands by drag or tap to a third-height sheet
135
- * carrying the bulk verbs (Delete / Move / Junk), the select-similar → organize
136
- * entries, and every escalation state (counting, running progress,
137
- * partial-failure) the selection can be in. Drag or tap the grabber to collapse
138
- * back to the teaser; the selection is untouched.
138
+ * with the selection count, and expands by drag or tap to carry the bulk verbs
139
+ * (Delete / Move / Junk), the select-similar → organize entries, and every
140
+ * escalation state (counting, running progress, partial-failure) the selection
141
+ * can be in. Drag or tap the grabber to collapse back to the teaser; the
142
+ * selection is untouched.
143
+ *
144
+ * Expanded, it takes the height its own content needs, up to
145
+ * {@link EXPANDED_MAX_DVH}% of the dynamic viewport, past which the actions
146
+ * scroll. A fixed ceiling sat below the content on a short phone viewport and
147
+ * cut the last action off with no way to reach it (#405).
139
148
  *
140
149
  * Sits absolutely against the bottom of the nearest positioned ancestor, so the
141
150
  * list it belongs to must be a `relative` container and pad its own bottom by
@@ -160,7 +169,9 @@ export function SelectionSheet({
160
169
  }: SelectionSheetProps) {
161
170
  const [expanded, setExpanded] = useState(startExpanded);
162
171
  const containerRef = useRef<HTMLDivElement>(null);
163
- const [expandedHeight, setExpandedHeight] = useState(EXPANDED_MAX);
172
+ const [expandedHeight, setExpandedHeight] = useState(
173
+ EXPANDED_HEIGHT_FALLBACK,
174
+ );
164
175
 
165
176
  // A run or a live count owns the sheet: it stays open so the progress and
166
177
  // status can't be dragged out of sight mid-operation.
@@ -265,7 +276,7 @@ export function SelectionSheet({
265
276
  data-selection-sheet=""
266
277
  className="absolute inset-x-0 bottom-0 z-30 flex select-none flex-col rounded-t-2xl border-t border-line bg-surface shadow-2xl shadow-black/40"
267
278
  style={{
268
- maxHeight: `min(${EXPANDED_MAX}px, 38dvh)`,
279
+ maxHeight: `${EXPANDED_MAX_DVH}dvh`,
269
280
  minHeight: `${SELECTION_SHEET_TEASER_HEIGHT}px`,
270
281
  transform: `translateY(${translate}px)`,
271
282
  transition,
@@ -355,10 +366,12 @@ export function SelectionSheet({
355
366
 
356
367
  {/* Expanded content — clipped by the translate when collapsed. It stays
357
368
  in the DOM at the teaser, so `inert` when collapsed keeps its offscreen
358
- verbs out of the tab order and the a11y tree until the sheet opens. */}
369
+ verbs out of the tab order and the a11y tree until the sheet opens.
370
+ Scrolls rather than clips, so every verb stays reachable on a viewport
371
+ the actions don't fit in (#405). */}
359
372
  <div
360
373
  inert={!expanded ? true : undefined}
361
- className="flex min-h-0 flex-1 flex-col overflow-hidden px-4 pb-4"
374
+ className="flex min-h-0 flex-1 flex-col overflow-y-auto px-4 pb-4"
362
375
  >
363
376
  {progress && (
364
377
  <div className="mb-3">