pixelize-design-library 2.4.2-beta.46 → 2.4.2-beta.48
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/CHANGELOG.md +76 -3
- package/COMPONENT-GOTCHAS.md +76 -7
- package/dist/Components/Common/chipTriggerSize.d.ts +10 -0
- package/dist/Components/Common/chipTriggerSize.js +15 -0
- package/dist/Components/Dropdown/DropDown.js +10 -8
- package/dist/Components/SearchSelect/SearchSelect.js +66 -20
- package/dist/Components/SearchSelect/SearchSelectProps.d.ts +5 -1
- package/dist/Components/SearchSelect/searchSelectSize.d.ts +0 -5
- package/dist/Components/SearchSelect/searchSelectSize.js +0 -12
- package/dist/Components/Select/SelectNative.d.ts +2 -0
- package/dist/Components/Select/SelectPopup.js +104 -43
- package/dist/Components/Select/SelectProps.d.ts +4 -0
- package/dist/Components/Select/optionRowInk.d.ts +4 -0
- package/dist/Components/Select/optionRowInk.js +12 -1
- package/dist/Components/SelectSearch/SelectSearch.js +8 -4
- package/dist/Components/SelectV2/SelectV2.d.ts +2 -9
- package/dist/Components/SelectV2/SelectV2.js +2 -10
- package/dist/Components/Switch/Switch.d.ts +1 -1
- package/dist/Components/Switch/Switch.js +4 -2
- package/dist/Components/Table/Table.js +6 -7
- package/dist/Components/Table/TableProps.d.ts +9 -1
- package/dist/Components/Table/components/Pagination.js +8 -5
- package/dist/Components/Table/components/TableBody.d.ts +2 -1
- package/dist/Components/Table/components/TableBody.js +82 -15
- package/dist/Components/Table/components/TableHeader.d.ts +1 -1
- package/dist/Components/Table/components/TableHeader.js +8 -9
- package/dist/Components/Table/components/rowMetrics.d.ts +0 -1
- package/dist/Components/Table/components/rowMetrics.js +1 -2
- package/dist/Components/Table/filters/CompactSelect.js +6 -3
- package/dist/Components/Table/hooks/useTable.d.ts +5 -1
- package/dist/Components/Table/hooks/useTable.js +43 -7
- package/dist/Theme/chakra/Menu.styles.d.ts +6 -0
- package/dist/Theme/chakra/Menu.styles.js +8 -0
- package/dist/Theme/chakra/componentStyles.d.ts +6 -0
- package/dist/Theme/tokens/builders/outlineRung.d.ts +2 -0
- package/dist/Theme/tokens/builders/outlineRung.js +14 -1
- package/dist/Theme/tokens/builders/rowStates.js +3 -4
- package/dist/Theme/tokens/primitives/optionRowSizes.d.ts +20 -0
- package/dist/Theme/tokens/primitives/optionRowSizes.js +29 -0
- package/dist/esm/Components/Common/chipTriggerSize.js +11 -0
- package/dist/esm/Components/Dropdown/DropDown.js +10 -8
- package/dist/esm/Components/SearchSelect/SearchSelect.js +66 -20
- package/dist/esm/Components/SearchSelect/searchSelectSize.js +0 -12
- package/dist/esm/Components/Select/SelectPopup.js +105 -44
- package/dist/esm/Components/Select/optionRowInk.js +9 -0
- package/dist/esm/Components/SelectSearch/SelectSearch.js +9 -5
- package/dist/esm/Components/SelectV2/SelectV2.js +2 -9
- package/dist/esm/Components/Switch/Switch.js +4 -2
- package/dist/esm/Components/Table/Table.js +6 -7
- package/dist/esm/Components/Table/components/Pagination.js +8 -5
- package/dist/esm/Components/Table/components/TableBody.js +81 -16
- package/dist/esm/Components/Table/components/TableHeader.js +8 -9
- package/dist/esm/Components/Table/components/rowMetrics.js +0 -1
- package/dist/esm/Components/Table/filters/CompactSelect.js +7 -4
- package/dist/esm/Components/Table/hooks/useTable.js +44 -8
- package/dist/esm/Theme/chakra/Menu.styles.js +8 -0
- package/dist/esm/Theme/tokens/builders/outlineRung.js +12 -0
- package/dist/esm/Theme/tokens/builders/rowStates.js +4 -5
- package/dist/esm/Theme/tokens/primitives/optionRowSizes.js +25 -0
- package/dist/esm/index.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +9 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -63,6 +63,59 @@ next major.
|
|
|
63
63
|
|
|
64
64
|
### Fixed
|
|
65
65
|
|
|
66
|
+
- **`Table` — a column search only ever matched the page you were looking at.** The per-column
|
|
67
|
+
search ran on `filteredData`, which `useTable` had already sliced to the current page, so a match
|
|
68
|
+
on page 3 was invisible from page 1 and the pager still counted every row. The search now runs
|
|
69
|
+
before the slice: page count, the `1 - 20 of N` readout and the rendered rows all come from the
|
|
70
|
+
matched set, and typing a new search returns to page 1. Grouped mode already searched the whole
|
|
71
|
+
source and is unchanged; server pagination still searches only the loaded page, since the client
|
|
72
|
+
cannot see the rest.
|
|
73
|
+
- **`Table` — an empty page was a dead end, and a page-size change left the body scrolled.** The
|
|
74
|
+
pager's step controls were hidden whenever the loaded rows were empty, so a server page that came
|
|
75
|
+
back with nothing stranded the user with no way back; stepping back now survives an empty result
|
|
76
|
+
and only stepping forward is disabled. A narrowing client-side result also clamps the current page
|
|
77
|
+
instead of leaving it past the end. Changing the page size at page 0 swaps the rows without moving
|
|
78
|
+
`startRow`, which used to leave the virtualized body scrolled into the old page — the scroll reset
|
|
79
|
+
now keys on the page size too.
|
|
80
|
+
- **`Table` — a full-bleed status cell ignored `columns[].align`.** `align` is documented as aligning
|
|
81
|
+
the header label and the cells together, but a bleed status cell hardcoded centre. It now follows
|
|
82
|
+
`align` when the column sets one, and still centres when it does not, so existing tables look the
|
|
83
|
+
same.
|
|
84
|
+
- **`Select` — the popup's filter field did not line up with the options under it.** Its wrapper
|
|
85
|
+
used a hardcoded `p={2}` while option rows inset by the size spec's `mx` (6px at `sm`, 4px at
|
|
86
|
+
`xs`), so the field's edges sat 2-4px inside the list's, and its text origin 2px further in. The
|
|
87
|
+
field was also fixed at `size="sm"` whatever the `Select`'s own size, so a `lg` picker got a small
|
|
88
|
+
search box. Both now follow the active size spec; measured edge- and text-aligned at every size.
|
|
89
|
+
- **`Select` — a multi-select with chips rendered 2px taller than every other field.** The chip row's
|
|
90
|
+
vertical padding was computed against the control height without allowing for the trigger's own
|
|
91
|
+
1px rule top and bottom, which a border-box trigger adds on top. The sizing now lives in one
|
|
92
|
+
shared helper (`Common/chipTriggerSize`) that `Select` and the deprecated `SelectV2` both read.
|
|
93
|
+
- **`SearchSelect` — infinite-scroll paging could duplicate a page, loop forever on an
|
|
94
|
+
exact-multiple total, leak a pending fetch into a closed/unmounted menu, and never scrolled the
|
|
95
|
+
keyboard-highlighted row into view.** `handleScroll` now reads the live option count at fire
|
|
96
|
+
time instead of the value captured when its 200ms debounce was scheduled, and won't re-request a
|
|
97
|
+
`startIndex` it already asked for. `hasMore` takes new optional `pagination.hasMore` /
|
|
98
|
+
`pagination.totalRecords` overrides, defaults `limit` to 10 (it used to never update when `limit`
|
|
99
|
+
was omitted), and stops after one page that doesn't grow `options`. The pending page timer is
|
|
100
|
+
cleared on close/unmount, and the active row now scrolls into view with `block: "nearest"`.
|
|
101
|
+
- **`Table` — frozen columns sat at the wrong offset, and the pagination bar was misaligned.** The
|
|
102
|
+
sticky `left` was passed as a bare number, which Chakra reads off the space scale: a 48px offset
|
|
103
|
+
rendered as `12rem` (192px), and any cumulative frozen-column width landing on the scale jumped
|
|
104
|
+
4x. It stayed hidden because the old `CHECKBOX_SLOT_OFFSET = 50` is not a scale value — but 50
|
|
105
|
+
also missed the real 48px select-all cell by 2px, leaving a slit that scrolled content bled
|
|
106
|
+
through, and was wrong outright for `variant="studio"`'s 56px slot. The slot is now measured with
|
|
107
|
+
a `ResizeObserver`, and both offsets interpolate explicit pixels. The pager is one 24px row: the
|
|
108
|
+
`mt={3}` that faked centring is gone, the steps are `size="xs"` with 16px icons instead of
|
|
109
|
+
Chakra's 40px default, and the page-size select no longer fights `justify="flex-end"` with
|
|
110
|
+
`w="100%"`.
|
|
111
|
+
- **`Table` — virtualized body jumped to the top on an appended page, and never scrolled on a real
|
|
112
|
+
page change below the virtualize threshold.** The scroll-reset effect now keys on `startRow`
|
|
113
|
+
actually changing instead of on the 30-row virtualize threshold flipping, so infinite-scroll and
|
|
114
|
+
grouped `groupLoadMore` appends no longer yank the user to the top, while a real page change
|
|
115
|
+
resets correctly at any size. A shrinking `data` set no longer strands the window past the end,
|
|
116
|
+
the offset-lookup binary-searches instead of scanning from 0, and measured row heights are
|
|
117
|
+
pruned per page instead of growing forever.
|
|
118
|
+
|
|
66
119
|
- **`SegmentedControl` / `Switch` guessed the backdrop it was drawn on.** `brandSlide` painted an
|
|
67
120
|
8% brand tint composited over `backgroundColor.main` and lifted its unselected-label ink against
|
|
68
121
|
that same assumed canvas, so on a brand-filled bar the tint sank into the bar and the label
|
|
@@ -70,9 +123,29 @@ next major.
|
|
|
70
123
|
on a `primary[700]` bar was unreadable. The track is now an opaque plate on every backdrop: rest
|
|
71
124
|
ink 7.58–18.39:1, plate-vs-bar 6.31–19.0:1, with no change required at the call site. `surface`
|
|
72
125
|
is now only a refinement for the plate's hairline edge, and a test gates the invariant.
|
|
73
|
-
- **A selected `Select` option stacked brand ink on a
|
|
74
|
-
`
|
|
75
|
-
|
|
126
|
+
- **A selected `Select`/`SearchSelect`/`SelectSearch`/`CompactSelect` option stacked brand ink on a
|
|
127
|
+
brand-tinted row** — `CompactSelect` had no selected styling at all. The label is now `text[900]`
|
|
128
|
+
at weight 500 with the brand carried by a trailing check, matching `OrgSwitcher`. The row tint
|
|
129
|
+
itself is also settled to one perceptible step off the panel instead of taking the raw token
|
|
130
|
+
(`selected` 1.16–1.29:1 → 1.151–1.191:1; the pressed-and-selected state above it 1.34–1.52:1 →
|
|
131
|
+
1.259–1.340:1), so a too-saturated tint is pulled down as reliably as a too-faint one is lifted.
|
|
132
|
+
- **Every dropdown row is now one size.** `Select`, `SearchSelect`, `SelectSearch`,
|
|
133
|
+
`CompactSelect`'s filter, `DropDown`'s menu items and every themed `Menu` item shared six
|
|
134
|
+
different paddings for the same row; `sm` is now 30px tall everywhere (`padding: 4px 10px`,
|
|
135
|
+
`font-size: 13px`) instead of `SearchSelect` alone rendering ~34px with a 16px font. Row
|
|
136
|
+
content — including a `SearchSelect` `option.view` or `Select` `renderOption` node — now inherits
|
|
137
|
+
its font size from the row, so consumer-rendered row content is no longer stuck at the page's
|
|
138
|
+
16px body font.
|
|
139
|
+
- **A dropdown row's description sub-line fell under AA in dark mode.** Settling the selected rung
|
|
140
|
+
left `textMuted` at **4.00–4.22:1** on the hovered-selected row across 6 of 9 brands (dark
|
|
141
|
+
`surfaceRaised` carries only ~0.86 of headroom, so any lighter rung eats it). The sub-line ink is
|
|
142
|
+
now derived once per palette against that busiest rung, clearing 4.5:1 everywhere; light mode is
|
|
143
|
+
unchanged because it already cleared.
|
|
144
|
+
- **`Switch` no longer forces a 200px-wide track when it has no labels.** The width floor now only
|
|
145
|
+
applies once a label is set, so an unlabelled instance — the wrong component for a boolean field,
|
|
146
|
+
but one that shipped live in a 120px table cell — shrinks to fit instead of overflowing by 80px.
|
|
147
|
+
Use `ToggleSwitch` for an on/off field; swapping it in trades `role="radiogroup"` for
|
|
148
|
+
`role="switch"`, the same `getByRole` break the `InputSwitch` → `ToggleSwitch` migration caused.
|
|
76
149
|
|
|
77
150
|
- **`SegmentedControl`'s pill bulged past the plate's corners.** The pill and the segments carried
|
|
78
151
|
the track's own `lg` radius while sitting 2px inside it, so the plate's band stayed even along the
|
package/COMPONENT-GOTCHAS.md
CHANGED
|
@@ -127,7 +127,7 @@ is now the library's own themed component, with a real dual-thumb mode and a `ma
|
|
|
127
127
|
| One of 2-5 named options | `SegmentedControl` | `role="radiogroup"`, keyboard-navigable, supports per-option disabling |
|
|
128
128
|
| Switching between views or panels | `Tabs` | `role="tablist"` + `aria-controls`, count badges, lazy panels, vertical orientation |
|
|
129
129
|
| *(avoid)* | `InputSwitch` | `@deprecated` — now a thin wrapper that renders `ToggleSwitch` |
|
|
130
|
-
| *(avoid)* | `Switch` | `@deprecated` — a
|
|
130
|
+
| *(avoid)* | `Switch` | `@deprecated` — a two-label segmented control (`leftText`/`rightText`), not a boolean; for a single on/off field use `ToggleSwitch` instead |
|
|
131
131
|
|
|
132
132
|
**Apply:** "on vs off" is always `ToggleSwitch`; 2+ named options is always `SegmentedControl`.
|
|
133
133
|
|
|
@@ -434,6 +434,37 @@ set the required state on your own wrapping `FormControl`. Note the obvious one-
|
|
|
434
434
|
component is not neutral: Chakra's `useFormControl` also emits native `required`, which would add
|
|
435
435
|
browser validation to a *filter* input that was never submitted before.
|
|
436
436
|
|
|
437
|
+
### `Select` with `onSearchChange` no longer filters locally (beta.48)
|
|
438
|
+
|
|
439
|
+
Passing `onSearchChange` means the server owns matching, so `Select` stops applying its own
|
|
440
|
+
substring filter to `options`. Before this, a fuzzy or ranked server result whose label did not
|
|
441
|
+
literally contain the typed text was fetched and then hidden. Pass `filterOptions={true}` to keep
|
|
442
|
+
the local pass on top, or `filterOptions={false}` to drop it even without `onSearchChange`.
|
|
443
|
+
|
|
444
|
+
**Apply:** if a server-backed `Select` suddenly shows more rows than it used to, that is this fix,
|
|
445
|
+
not a regression. Paged `Select`s should also pass `hasMore={false}` once the last page lands —
|
|
446
|
+
without it, `onLoadMore` fires again every time the user reaches the bottom.
|
|
447
|
+
|
|
448
|
+
### `Select` with `isMulti` keeps picks that leave `options` (beta.48)
|
|
449
|
+
|
|
450
|
+
`onChange` used to be built from the options currently in the list, so replacing `options` with a
|
|
451
|
+
server-search result silently dropped every earlier pick the new page did not contain — the chips
|
|
452
|
+
vanished and the next selection emitted a truncated array. Selected options are now remembered
|
|
453
|
+
independently of `options`.
|
|
454
|
+
|
|
455
|
+
**Apply:** nothing to change. If you worked around this by merging the old selection back in
|
|
456
|
+
yourself, that workaround is now redundant.
|
|
457
|
+
|
|
458
|
+
### `Table`'s column search covers every page (beta.48)
|
|
459
|
+
|
|
460
|
+
The per-column search box used to run after the page slice, so it matched only the rows on screen
|
|
461
|
+
while the pager still counted the whole data set. It now runs first: the page count, the
|
|
462
|
+
`1 - 20 of N` readout and the rows all come from the matched set, and a new search returns to page
|
|
463
|
+
1. Under `paginationMode="server"` the client still only sees the loaded page — filter on the
|
|
464
|
+
server there.
|
|
465
|
+
|
|
466
|
+
**Apply:** a client-side table that looked like it had "no matches" past page 1 now finds them.
|
|
467
|
+
|
|
437
468
|
### `Select`'s `onChange` never fires for the built-in placeholder
|
|
438
469
|
|
|
439
470
|
`Select` resolves the changed value against its `options` list and calls `onChange` only on a hit.
|
|
@@ -1219,14 +1250,16 @@ configured has no visible effect at all.
|
|
|
1219
1250
|
**Apply:** to change the main field's height, change `size`; `height` only matters when
|
|
1220
1251
|
`insideSelect` is also passed.
|
|
1221
1252
|
|
|
1222
|
-
### `SearchSelect`'s `pagination
|
|
1253
|
+
### `SearchSelect`'s `pagination` now takes an explicit "has more" signal — prefer it at an exact-multiple total
|
|
1223
1254
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
looks
|
|
1255
|
+
Without `pagination.hasMore`/`pagination.totalRecords`, `hasMore` still falls back to guessing from
|
|
1256
|
+
`options.length % (pagination.limit ?? 10) === 0`. A total that's an exact multiple of `limit`
|
|
1257
|
+
(30 items, limit 10) still looks like "more pages exist" for the *first* extra scroll, firing one
|
|
1258
|
+
empty `onScroll` call — after that empty response, `SearchSelect` stops asking (it no longer loops
|
|
1259
|
+
forever the way earlier versions did).
|
|
1227
1260
|
|
|
1228
|
-
**Apply:**
|
|
1229
|
-
|
|
1261
|
+
**Apply:** pass `pagination.hasMore` (a boolean) or `pagination.totalRecords` (a count) so the field
|
|
1262
|
+
never fires that one avoidable empty request; both take precedence over the `limit` guess.
|
|
1230
1263
|
|
|
1231
1264
|
### `RolesPermission`'s `RoleData.permission`/`designation`/`makeSuperAdmin`/`automation`/`trackLocation`/`show_records` are dead weight in this component
|
|
1232
1265
|
|
|
@@ -1343,3 +1376,39 @@ Not just the columns — the select/edit/create header bar disappears too, since
|
|
|
1343
1376
|
returns `null` before rendering anything.
|
|
1344
1377
|
|
|
1345
1378
|
**Apply:** there's no prop for hiding only the board body while keeping header actions visible.
|
|
1379
|
+
|
|
1380
|
+
### `Switch` was forced to 200px even with no labels — it overflowed a narrow cell
|
|
1381
|
+
|
|
1382
|
+
`Switch` (`leftText`/`rightText`) kept a 12.5rem minimum width regardless of whether any label was
|
|
1383
|
+
set. Used as a boolean toggle — the wrong component for that, but the mistake shipped live in
|
|
1384
|
+
tickets' project custom-fields page — an unlabelled instance rendered as two unlabelled brand-filled
|
|
1385
|
+
pills at 200px inside a 120px table cell, overflowing by 80px. The width floor now only applies once
|
|
1386
|
+
`leftText`/`rightText` is actually set; an unlabelled instance shrinks to fit (~58px measured).
|
|
1387
|
+
|
|
1388
|
+
**Apply:** for a single on/off field use `ToggleSwitch`, not `Switch`, even now that it no longer
|
|
1389
|
+
overflows — a two-option segmented control still announces as `role="radiogroup"`, not
|
|
1390
|
+
`role="switch"`. Swapping `Switch` for `ToggleSwitch` changes `getByRole("radio", …)` queries to
|
|
1391
|
+
`getByRole("switch", …)` — the same test break the `InputSwitch` → `ToggleSwitch` migration caused.
|
|
1392
|
+
|
|
1393
|
+
### A `SearchSelect`/`Select` custom row node inherits the row's font size and padding — don't restate them
|
|
1394
|
+
|
|
1395
|
+
`SearchSelect`'s `option.view` and `Select`'s `renderOption` render straight inside the option row,
|
|
1396
|
+
which now carries its own `fontSize`/`px`/`py` so a plain-text row is legible with no extra markup.
|
|
1397
|
+
A custom node that also sets its own `px`/`py`/`fontSize` stacks on top of the row's rather than
|
|
1398
|
+
replacing it — this is what made rows in crm-frontend render 57–75px tall instead of the ~30px every
|
|
1399
|
+
other row uses.
|
|
1400
|
+
|
|
1401
|
+
**Apply:** size a custom `option.view`/`renderOption` node yourself only when it needs something
|
|
1402
|
+
*different* from the row default — inherit by default, don't restate it.
|
|
1403
|
+
|
|
1404
|
+
### A custom option row's sub-line must not use `colors.textMuted` directly
|
|
1405
|
+
|
|
1406
|
+
A selected row sits on a tinted fill, and the hovered-selected rung is the busiest one in the
|
|
1407
|
+
ladder. In dark mode `surfaceRaised` carries only ~0.86 of contrast headroom above AA, so raw
|
|
1408
|
+
`textMuted` on that rung measures **4.00–4.22:1** in 6 of 9 brands — under the 4.5:1 a 12px
|
|
1409
|
+
sub-line needs. The library's own rows no longer use it.
|
|
1410
|
+
|
|
1411
|
+
**Apply:** in a `SearchSelect` `option.view` or `Select` `renderOption` node, read the sub-line ink
|
|
1412
|
+
from `optionRowDescriptionInk(theme.colors)` (exported from the package root) rather than
|
|
1413
|
+
`theme.colors.textMuted`. It is a no-op in light mode. The row's main label is
|
|
1414
|
+
`optionRowInk(theme.colors, isSelected)` with `optionRowWeight(isSelected)` — never brand ink.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type ControlSize } from "../../Theme/tokens/primitives/controlSizes";
|
|
2
|
+
export type ChipSize = "xs" | "sm";
|
|
3
|
+
/** Sizes a select trigger so a chip row lands on the same height as a plain one. */
|
|
4
|
+
export declare const chipTriggerSizeStyles: (size: ControlSize, hasChips: boolean, chipSize?: ChipSize) => {
|
|
5
|
+
minH: string;
|
|
6
|
+
fontSize: string;
|
|
7
|
+
px: string;
|
|
8
|
+
h?: string;
|
|
9
|
+
py?: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.chipTriggerSizeStyles = void 0;
|
|
4
|
+
const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
|
|
5
|
+
const CHIP_TAG_HEIGHT_PX = { xs: 20, sm: 24 };
|
|
6
|
+
const CONTROL_BORDER_PX = 2;
|
|
7
|
+
const controlHeightPx = (size) => parseFloat(controlSizes_1.CONTROL_SIZES[size].height) * 16;
|
|
8
|
+
const chipRowPaddingY = (size, chipSize) => `${(controlHeightPx(size) - CHIP_TAG_HEIGHT_PX[chipSize] - CONTROL_BORDER_PX) / 2 / 16}rem`;
|
|
9
|
+
/** Sizes a select trigger so a chip row lands on the same height as a plain one. */
|
|
10
|
+
const chipTriggerSizeStyles = (size, hasChips, chipSize = "sm") => {
|
|
11
|
+
const spec = controlSizes_1.CONTROL_SIZES[size];
|
|
12
|
+
const base = { minH: spec.height, fontSize: spec.fontSize, px: spec.paddingX };
|
|
13
|
+
return hasChips ? { ...base, py: chipRowPaddingY(size, chipSize) } : { ...base, h: spec.height };
|
|
14
|
+
};
|
|
15
|
+
exports.chipTriggerSizeStyles = chipTriggerSizeStyles;
|
|
@@ -11,16 +11,17 @@ const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
|
|
|
11
11
|
const lucide_react_1 = require("lucide-react");
|
|
12
12
|
const rafThrottle_1 = require("../../Utils/rafThrottle");
|
|
13
13
|
const controlSizes_1 = require("../../Theme/tokens/primitives/controlSizes");
|
|
14
|
+
const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
|
|
14
15
|
const motion_1 = require("../../Theme/tokens/primitives/motion");
|
|
15
16
|
const motion_2 = require("../../Theme/chakra/motion");
|
|
16
17
|
const focusRing_styles_1 = require("../../Theme/chakra/focusRing.styles");
|
|
17
18
|
const MotionBox = (0, framer_motion_1.motion)((0, react_1.forwardRef)((props, ref) => ((0, jsx_runtime_1.jsx)(react_2.Box, { ref: ref, ...props }))));
|
|
18
19
|
const SIZE_MAP = {
|
|
19
|
-
xs: { btn: "xs",
|
|
20
|
-
sm: { btn: "sm",
|
|
21
|
-
md: { btn: "md",
|
|
22
|
-
lg: { btn: "lg",
|
|
23
|
-
xl: { btn: "lg",
|
|
20
|
+
xs: { btn: "xs", img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.xs.iconSize, minW: "8rem", gap: 1.5, rowSize: "xs" },
|
|
21
|
+
sm: { btn: "sm", img: "1rem", chevron: controlSizes_1.CONTROL_SIZES.sm.iconSize, minW: "9rem", gap: 2, rowSize: "sm" },
|
|
22
|
+
md: { btn: "md", img: "1.125rem", chevron: controlSizes_1.CONTROL_SIZES.md.iconSize, minW: "11rem", gap: 2, rowSize: "md" },
|
|
23
|
+
lg: { btn: "lg", img: "1.25rem", chevron: controlSizes_1.CONTROL_SIZES.lg.iconSize, minW: "12rem", gap: 2.5, rowSize: "lg" },
|
|
24
|
+
xl: { btn: "lg", img: "1.4rem", chevron: 20, minW: "13rem", gap: 3, rowSize: "lg" },
|
|
24
25
|
};
|
|
25
26
|
const TRIGGER_TRANSITION = (0, motion_1.transitionFor)(["background-color", "border-color", "color", "box-shadow"]);
|
|
26
27
|
const ROW_TRANSITION = (0, motion_1.transitionFor)(["background-color", "color"]);
|
|
@@ -37,6 +38,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
|
|
|
37
38
|
const menuRef = (0, react_1.useRef)(null);
|
|
38
39
|
const [menuPos, setMenuPos] = (0, react_1.useState)({ top: 0, left: 0, width: 0 });
|
|
39
40
|
const s = SIZE_MAP[(_a = size !== null && size !== void 0 ? size : optionsSize) !== null && _a !== void 0 ? _a : "sm"];
|
|
41
|
+
const rowSpec = (0, optionRowSizes_1.optionRowSpec)(s.rowSize);
|
|
40
42
|
const prefersReducedMotion = (0, framer_motion_1.useReducedMotion)();
|
|
41
43
|
const menuId = (0, react_1.useId)();
|
|
42
44
|
// Roving-tabindex keyboard nav for the menu (ARIA APG menu-button pattern):
|
|
@@ -232,7 +234,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
|
|
|
232
234
|
const Chevron = isVisibleIconShow ? ((0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", transition: CHEVRON_TRANSITION, transform: isOpen ? "rotate(180deg)" : "rotate(0deg)", sx: (0, motion_2.reducedMotionSx)(), children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevron }) })) : undefined;
|
|
233
235
|
const dropDownToggleOption = () => {
|
|
234
236
|
const ButtonToggle = ((0, jsx_runtime_1.jsx)(react_2.Button, { ref: buttonTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, size: s.btn, colorScheme: "primary", borderRadius: "lg", fontWeight: 600, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (ButtonText ? undefined : "Open menu"), rightIcon: Chevron, sx: { transition: TRIGGER_TRANSITION, ...(0, motion_2.reducedMotionSx)(), ...dropDownButtonStyle }, ...buttonProps, children: (0, jsx_runtime_1.jsxs)(react_2.Box, { as: "span", display: "inline-flex", alignItems: "center", gap: DropdownIcon && ButtonText ? s.gap : 0, children: [DropdownIcon, ButtonText] }) }));
|
|
235
|
-
const TextToggle = ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: textTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, align: "center", gap: 1.5, cursor: "pointer", fontSize:
|
|
237
|
+
const TextToggle = ((0, jsx_runtime_1.jsxs)(react_2.Flex, { ref: textTriggerRef, onClick: handleToggle, onKeyDown: handleTriggerKeyDown, align: "center", gap: 1.5, cursor: "pointer", fontSize: rowSpec.fontSize, fontWeight: 500, color: theme.colors.text[700], borderRadius: "md", transition: ROW_TRANSITION, _hover: { color: theme.colors.accentText }, sx: { ...(0, focusRing_styles_1.focusVisibleStyles)(theme.colors), ...(0, motion_2.reducedMotionSx)() }, style: dropDownButtonStyle, role: "button", tabIndex: 0, "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": menuId, "aria-label": ariaLabel !== null && ariaLabel !== void 0 ? ariaLabel : (text ? undefined : "Open menu"), children: [DropdownIcon, text, Chevron] }));
|
|
236
238
|
return dropdownType === "button" ? ButtonToggle : TextToggle;
|
|
237
239
|
};
|
|
238
240
|
const DropdownContentRender = () => {
|
|
@@ -242,7 +244,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
|
|
|
242
244
|
}, role: "menuitem", tabIndex: index === activeIndex ? 0 : -1, onClick: () => {
|
|
243
245
|
handleListItemClick(option.id, option.label);
|
|
244
246
|
focusTrigger();
|
|
245
|
-
}, onKeyDown: (e) => handleItemKeyDown(e, option.id, option.label), mx:
|
|
247
|
+
}, onKeyDown: (e) => handleItemKeyDown(e, option.id, option.label), mx: rowSpec.mx, px: rowSpec.px, py: rowSpec.py, minH: rowSpec.minH, borderRadius: optionRowSizes_1.OPTION_ROW_RADIUS, display: "flex", alignItems: "center", gap: rowSpec.gap, fontSize: rowSpec.fontSize, cursor: "pointer", color: theme.colors.text[700], transition: ROW_TRANSITION, _hover: {
|
|
246
248
|
bg: hoverTint,
|
|
247
249
|
color: theme.colors.accentText,
|
|
248
250
|
}, _focus: {
|
|
@@ -255,7 +257,7 @@ const Dropdown = (0, react_1.forwardRef)(({ dropDownButtonStyle, ButtonText, opt
|
|
|
255
257
|
boxShadow: `inset 0 0 0 2px ${theme.colors.focusRing.ring}`,
|
|
256
258
|
}, borderBottom: divider
|
|
257
259
|
? `0.063rem solid ${theme.colors.boxborder[200]}`
|
|
258
|
-
: undefined, sx: { "&:last-child": { borderBottom: "none" }, ...(0, motion_2.reducedMotionSx)() }, style: ItemStyle, fontWeight: 500, children: [option.image && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", fontSize:
|
|
260
|
+
: undefined, sx: { "&:last-child": { borderBottom: "none" }, ...(0, motion_2.reducedMotionSx)() }, style: ItemStyle, fontWeight: 500, children: [option.image && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: option.image, boxSize: s.img, flexShrink: 0, color: theme.colors.textMuted, style: ImageStyle })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", fontSize: rowSpec.fontSize, isTruncated: true, style: LabelStyle, children: option.label })] }, option.id))) }));
|
|
259
261
|
};
|
|
260
262
|
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { ref: dropdownRef, position: "relative", display: "inline-block", children: [dropDownToggleOption(), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsx)(MotionBox, { ref: setMenuRef, position: "fixed", top: `${menuPos.top}px`, left: `${menuPos.left}px`, minW: `${menuPos.width}px`, zIndex: 1500, initial: prefersReducedMotion ? false : { opacity: 0, y: -6 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -6 }, style: headStyle, children: DropdownContentRender() }) }))] }));
|
|
261
263
|
});
|
|
@@ -44,6 +44,8 @@ const FormLabel_1 = require("../Common/FormLabel");
|
|
|
44
44
|
const Tag_1 = __importDefault(require("../Tag/Tag"));
|
|
45
45
|
const useCustomTheme_1 = require("../../Theme/useCustomTheme");
|
|
46
46
|
const rowStates_1 = require("../../Theme/tokens/builders/rowStates");
|
|
47
|
+
const optionRowSizes_1 = require("../../Theme/tokens/primitives/optionRowSizes");
|
|
48
|
+
const optionRowInk_1 = require("../Select/optionRowInk");
|
|
47
49
|
const useExclusiveOverlay_1 = require("../../Hooks/useExclusiveOverlay");
|
|
48
50
|
const FieldMessage_1 = __importDefault(require("../Common/FieldMessage"));
|
|
49
51
|
const useFieldIds_1 = require("../Common/useFieldIds");
|
|
@@ -75,6 +77,7 @@ const normalizeMultiValue = (val) => {
|
|
|
75
77
|
const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOptionLoading = false, isLoading = false, loadingText = "Loading...", placeholder = "Select", value, searchQuery = "", isMultiple = false, isRequired = false, error = false, errorMessage, helperText, width = "100%", height, size = "sm", isDisabled = false, chip, information, isInformation, informationMessage, rightIcon, addNew, isSelectAll = false, pagination, insideSelect, isRemoveAllIcon, "aria-label": ariaLabel, }) => {
|
|
76
78
|
var _a, _b, _c, _d, _e;
|
|
77
79
|
const s = (0, searchSelectSize_1.getSearchSelectSizeStyles)(size);
|
|
80
|
+
const spec = (0, optionRowSizes_1.optionRowSpec)(size);
|
|
78
81
|
const emitChange = onChange !== null && onChange !== void 0 ? onChange : onSelect;
|
|
79
82
|
const insideSelectBoxHeight = height !== null && height !== void 0 ? height : s.insideSelectDefaultHeight;
|
|
80
83
|
const [inputValue, setInputValue] = (0, react_1.useState)(searchQuery);
|
|
@@ -88,6 +91,9 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
88
91
|
const dropdownMenuRef = (0, react_1.useRef)(null);
|
|
89
92
|
const [dropdownPos, setDropdownPos] = (0, react_1.useState)({ top: 0, left: 0, width: 0 });
|
|
90
93
|
const scrollTimeoutRef = (0, react_1.useRef)(null);
|
|
94
|
+
const optionsLengthRef = (0, react_1.useRef)(options.length);
|
|
95
|
+
const lastPageStartRef = (0, react_1.useRef)(null);
|
|
96
|
+
const optionsListRef = (0, react_1.useRef)(null);
|
|
91
97
|
const [focused, setFocused] = (0, react_1.useState)(false);
|
|
92
98
|
const [customSelectOpen, setCustomSelectOpen] = (0, react_1.useState)(false);
|
|
93
99
|
const customSelectRef = (0, react_1.useRef)(null);
|
|
@@ -138,10 +144,22 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
138
144
|
return () => clearTimeout(delayDebounce);
|
|
139
145
|
}, [inputValue]);
|
|
140
146
|
(0, react_1.useEffect)(() => {
|
|
141
|
-
|
|
142
|
-
|
|
147
|
+
optionsLengthRef.current = options.length;
|
|
148
|
+
}, [options.length]);
|
|
149
|
+
(0, react_1.useEffect)(() => {
|
|
150
|
+
if ((pagination === null || pagination === void 0 ? void 0 : pagination.hasMore) !== undefined) {
|
|
151
|
+
setHasMore(pagination.hasMore);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if ((pagination === null || pagination === void 0 ? void 0 : pagination.totalRecords) !== undefined) {
|
|
155
|
+
setHasMore(options.length < pagination.totalRecords);
|
|
156
|
+
return;
|
|
143
157
|
}
|
|
144
|
-
|
|
158
|
+
if (!(pagination === null || pagination === void 0 ? void 0 : pagination.onScroll))
|
|
159
|
+
return;
|
|
160
|
+
const limit = pagination.limit || 10;
|
|
161
|
+
setHasMore(options.length % limit === 0);
|
|
162
|
+
}, [options.length, pagination === null || pagination === void 0 ? void 0 : pagination.hasMore, pagination === null || pagination === void 0 ? void 0 : pagination.totalRecords, pagination === null || pagination === void 0 ? void 0 : pagination.limit, pagination === null || pagination === void 0 ? void 0 : pagination.onScroll]);
|
|
145
163
|
const handleSelect = (option) => {
|
|
146
164
|
var _a;
|
|
147
165
|
if (isMultiple && insideSelect) {
|
|
@@ -321,6 +339,13 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
321
339
|
}
|
|
322
340
|
setActiveIndex((prev) => (prev >= 0 && prev < filteredOptions.length ? prev : -1));
|
|
323
341
|
}, [isOpen, filteredOptions.length]);
|
|
342
|
+
(0, react_1.useEffect)(() => {
|
|
343
|
+
var _a, _b;
|
|
344
|
+
if (!isOpen || activeIndex < 0 || !optionsListRef.current)
|
|
345
|
+
return;
|
|
346
|
+
(_b = (_a = optionsListRef.current
|
|
347
|
+
.querySelector(`[data-index="${activeIndex}"]`)) === null || _a === void 0 ? void 0 : _a.scrollIntoView) === null || _b === void 0 ? void 0 : _b.call(_a, { block: "nearest" });
|
|
348
|
+
}, [isOpen, activeIndex]);
|
|
324
349
|
// NOTE: only restores focus when a keyboard close actually unmounted Input and dropped focus to body.
|
|
325
350
|
(0, react_1.useEffect)(() => {
|
|
326
351
|
var _a, _b, _c, _d;
|
|
@@ -351,11 +376,30 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
351
376
|
clearTimeout(scrollTimeoutRef.current);
|
|
352
377
|
scrollTimeoutRef.current = setTimeout(() => {
|
|
353
378
|
var _a;
|
|
354
|
-
|
|
379
|
+
scrollTimeoutRef.current = null;
|
|
380
|
+
const startIndex = optionsLengthRef.current + 1;
|
|
381
|
+
if (lastPageStartRef.current === startIndex) {
|
|
382
|
+
setHasMore(false);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
lastPageStartRef.current = startIndex;
|
|
355
386
|
(_a = pagination.onScroll) === null || _a === void 0 ? void 0 : _a.call(pagination, startIndex, pagination.limit || 10, inputValue);
|
|
356
387
|
}, 200);
|
|
357
388
|
}
|
|
358
|
-
}, [pagination,
|
|
389
|
+
}, [pagination, inputValue, hasMore]);
|
|
390
|
+
(0, react_1.useEffect)(() => {
|
|
391
|
+
if (isOpen)
|
|
392
|
+
return;
|
|
393
|
+
if (scrollTimeoutRef.current) {
|
|
394
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
395
|
+
scrollTimeoutRef.current = null;
|
|
396
|
+
}
|
|
397
|
+
lastPageStartRef.current = null;
|
|
398
|
+
}, [isOpen]);
|
|
399
|
+
(0, react_1.useEffect)(() => () => {
|
|
400
|
+
if (scrollTimeoutRef.current)
|
|
401
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
402
|
+
}, []);
|
|
359
403
|
(0, react_2.useOutsideClick)({
|
|
360
404
|
ref: customSelectRef,
|
|
361
405
|
handler: (e) => {
|
|
@@ -494,16 +538,14 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
494
538
|
}, children: [(0, jsx_runtime_1.jsxs)(react_2.Box, { minW: "4.5rem", cursor: "pointer", display: "flex", justifyContent: "space-between", alignItems: "center", gap: 1, width: insideSelect.width, height: insideSelectBoxHeight, pl: "0.5rem", pr: "0.375rem", children: [insideLabel ? ((0, jsx_runtime_1.jsx)(SelectTruncatedLabel_1.default, { label: insideLabel, maxWidth: insideSelect.width || "6rem", fontSize: s.fieldFontSize })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: s.fieldFontSize, fontWeight: 500, color: theme.colors.textMuted, isTruncated: true, maxW: insideSelect.width || "6rem", children: insidePlaceholder })), (0, jsx_runtime_1.jsx)(react_2.Box, { as: "span", display: "inline-flex", flexShrink: 0, color: customSelectOpen ? theme.colors.accentText : theme.colors.textMuted, transition: CHEVRON_TRANSITION, transform: customSelectOpen ? "rotate(180deg)" : "rotate(0deg)", children: (0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize }) })] }), customSelectOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsx)(react_2.Box, { ref: setInsideSelectMenuRef, id: insideSelectPortalId, position: "fixed", top: `${customSelectPos.top}px`, left: `${customSelectPos.left}px`, zIndex: 1510, bg: theme.colors.surfaceRaised, border: "0.063rem solid", borderColor: theme.colors.boxborder[200], borderRadius: "xl", boxShadow: theme.shadows.lg, minW: `${customSelectPos.width}px`, maxW: "20rem", py: 1, overflow: "hidden", children: (_c = insideSelect.option) === null || _c === void 0 ? void 0 : _c.map((item) => {
|
|
495
539
|
var _a;
|
|
496
540
|
const selected = ((_a = insideSelect.value) === null || _a === void 0 ? void 0 : _a.id) === item.id;
|
|
497
|
-
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap:
|
|
498
|
-
? theme.colors.accentText
|
|
499
|
-
: theme.colors.text[700], bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
|
|
541
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { display: "flex", alignItems: "center", justifyContent: "space-between", gap: spec.gap, minH: spec.minH, py: spec.py, px: spec.px, fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(selected), color: (0, optionRowInk_1.optionRowInk)(theme.colors, selected), bg: selected ? rowBackgrounds.selected : "transparent", _hover: {
|
|
500
542
|
bg: selected
|
|
501
543
|
? rowBackgrounds.selectedActive
|
|
502
544
|
: rowBackgrounds.active,
|
|
503
545
|
}, cursor: "pointer", onClick: (e) => {
|
|
504
546
|
e.stopPropagation();
|
|
505
547
|
handleCustomSelect(item);
|
|
506
|
-
}, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: item.label }), selected && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size:
|
|
548
|
+
}, children: [(0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: item.label }), selected && ((0, jsx_runtime_1.jsx)(lucide_react_1.Check, { size: spec.iconSize, color: theme.colors.accentText }))] }, item.id));
|
|
507
549
|
}) }) }))] }));
|
|
508
550
|
})(), isMultiple
|
|
509
551
|
? selectedValues
|
|
@@ -535,16 +577,20 @@ const SearchSelect = ({ id, label, options, onChange, onSelect, onSearch, isOpti
|
|
|
535
577
|
else {
|
|
536
578
|
openMenu();
|
|
537
579
|
}
|
|
538
|
-
}, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: (0, focusRing_styles_1.focusVisibleStyles)(theme.colors), children: isOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: s.chevronSize })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py:
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
580
|
+
}, "aria-label": "toggle-dropdown", display: "flex", alignItems: "center", borderRadius: "sm", color: theme.colors.textMuted, cursor: isDisabled ? "not-allowed" : "pointer", sx: (0, focusRing_styles_1.focusVisibleStyles)(theme.colors), children: isOpen ? ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronUp, { size: s.chevronSize })) : ((0, jsx_runtime_1.jsx)(lucide_react_1.ChevronDown, { size: s.chevronSize })) }))] })] }) }), isOpen && ((0, jsx_runtime_1.jsx)(react_2.Portal, { children: (0, jsx_runtime_1.jsxs)(react_2.Box, { ref: setDropdownMenuRef, id: listboxId, role: "listbox", "aria-multiselectable": isMultiple || undefined, position: "fixed", top: `${dropdownPos.top}px`, left: `${dropdownPos.left}px`, width: `${dropdownPos.width}px`, zIndex: 1500, maxH: "20rem", borderWidth: 1, borderColor: theme.colors.boxborder[200], borderRadius: "xl", overflow: "hidden", bg: theme.colors.surfaceRaised, boxShadow: theme.shadows.lg, display: "flex", flexDirection: "column", children: [isMultiple && isSelectAll && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", onClick: handleSelectAll, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.text[700], borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [(0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: allFilteredSelected, sx: { pointerEvents: "none" }, size: s.checkboxSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: allFilteredSelected ? "Unselect All" : "Select All" })] })), (0, jsx_runtime_1.jsx)(react_2.Box, { ref: optionsListRef, flex: "1", overflowY: "auto", onScroll: handleScroll, maxH: "15rem", borderBottom: "0.063rem solid", borderColor: theme.colors.boxborder[200], children: isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: loadingText })] })) : !isOptionLoading && filteredOptions.length === 0 ? ((0, jsx_runtime_1.jsx)(react_2.Text, { p: 3, fontSize: spec.fontSize, color: theme.colors.textMuted, children: "No results found" })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [filteredOptions.map((option, index) => {
|
|
581
|
+
const selected = isSelected(option.id);
|
|
582
|
+
const singleSelected = selected && !isMultiple;
|
|
583
|
+
return ((0, jsx_runtime_1.jsxs)(react_2.Box, { id: getOptionId(index), "data-index": index, role: "option", "aria-selected": selected, py: spec.py, px: spec.px, minH: spec.minH, cursor: "pointer", bg: selected
|
|
584
|
+
? index === activeIndex
|
|
585
|
+
? rowBackgrounds.selectedActive
|
|
586
|
+
: rowBackgrounds.selected
|
|
587
|
+
: index === activeIndex
|
|
588
|
+
? rowBackgrounds.active
|
|
589
|
+
: "transparent", _hover: {
|
|
590
|
+
bg: selected ? rowBackgrounds.selectedActive : rowBackgrounds.active,
|
|
591
|
+
}, onClick: () => handleSelect(option), onMouseEnter: () => setActiveIndex(index), display: "flex", alignItems: "center", gap: spec.gap, color: (0, optionRowInk_1.optionRowInk)(theme.colors, singleSelected),
|
|
592
|
+
// `view` is consumer JSX with no size of its own; it inherited the 16px body font.
|
|
593
|
+
fontSize: spec.fontSize, fontWeight: (0, optionRowInk_1.optionRowWeight)(singleSelected), borderBottom: `0.063rem solid ${theme.colors.boxborder[200]}`, children: [isMultiple && ((0, jsx_runtime_1.jsx)(Checkbox_1.default, { isChecked: selected, sx: { pointerEvents: "none" }, size: s.checkboxSize })), option.view ? ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: option.view })) : ((0, jsx_runtime_1.jsx)(react_2.Text, { isTruncated: true, children: option.label })), singleSelected && ((0, jsx_runtime_1.jsx)(react_2.Box, { as: lucide_react_1.Check, size: spec.iconSize, color: theme.colors.accentText, flexShrink: 0 }))] }, option.id));
|
|
594
|
+
}), ((pagination === null || pagination === void 0 ? void 0 : pagination.scrollLoading) || (isOptionLoading && filteredOptions.length > 0)) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, display: "flex", alignItems: "center", justifyContent: "center", gap: spec.gap, minH: spec.minH, color: theme.colors.textMuted, children: [(0, jsx_runtime_1.jsx)(react_2.Spinner, { size: s.spinnerSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: loadingText })] }))] })) }), (addNew === null || addNew === void 0 ? void 0 : addNew.enabled) && ((0, jsx_runtime_1.jsxs)(react_2.Box, { py: spec.py, px: spec.px, borderTop: "0.063rem solid", borderColor: theme.colors.boxborder[200], bg: theme.colors.surfaceRaised, display: "flex", alignItems: "center", gap: spec.gap, color: theme.colors.accentText, fontWeight: 500, minH: spec.minH, cursor: "pointer", onClick: addNew.onClick, _hover: { bg: rowBackgrounds.active }, children: [addNew.icon || (0, jsx_runtime_1.jsx)(lucide_react_1.Plus, { size: s.addNewIconSize }), (0, jsx_runtime_1.jsx)(react_2.Text, { fontSize: spec.fontSize, children: addNew.text || "Add New" })] }))] }) })), (0, jsx_runtime_1.jsx)(FieldMessage_1.default, { error: error, errorMessage: errorMessage, helperText: helperText, helperId: ids.helperId, errorId: ids.errorId })] }));
|
|
549
595
|
};
|
|
550
596
|
exports.default = SearchSelect;
|
|
@@ -81,10 +81,14 @@ export type SearchSelectProps = {
|
|
|
81
81
|
pagination?: {
|
|
82
82
|
/** startIndex is 1-based (current options.length + 1), not a 0-based offset. */
|
|
83
83
|
onScroll?: (startIndex: number, limit: number, query: string) => void;
|
|
84
|
-
/** Also used to guess whether more pages exist (options.length % limit === 0
|
|
84
|
+
/** Also used to guess whether more pages exist (options.length % limit === 0, refined once a page returns no new rows). Defaults to 10 when omitted. */
|
|
85
85
|
limit?: number;
|
|
86
86
|
/** Set this synchronously while a page request is in flight, or fast scrolling can re-fire onScroll for the same page. */
|
|
87
87
|
scrollLoading?: boolean;
|
|
88
|
+
/** Explicit stop signal; overrides the limit-based heuristic when set. */
|
|
89
|
+
hasMore?: boolean;
|
|
90
|
+
/** Total row count; when set, hasMore is derived as options.length < totalRecords instead of the limit heuristic. */
|
|
91
|
+
totalRecords?: number;
|
|
88
92
|
};
|
|
89
93
|
/** Not implemented — the option list always renders unvirtualized; use `pagination` to page in large datasets instead. */
|
|
90
94
|
virtualization?: boolean;
|
|
@@ -19,11 +19,6 @@ export type SearchSelectSizeStyles = {
|
|
|
19
19
|
addNewIconSize: number;
|
|
20
20
|
/** Offset for the nested select segment (aligns with field padding). */
|
|
21
21
|
insideSelectMarginLeft: string;
|
|
22
|
-
/** “Select all” / option row text in the dropdown. */
|
|
23
|
-
dropdownTextFontSize: "xs" | "sm" | "md";
|
|
24
|
-
/** Min row height for main option list items. */
|
|
25
|
-
optionRowMinH: string;
|
|
26
|
-
optionRowPy: number;
|
|
27
22
|
/** Chakra `Checkbox` only supports sm | md | lg. */
|
|
28
23
|
checkboxSize: "sm" | "md" | "lg";
|
|
29
24
|
/** Chakra `Input` font token per `size` (unstyled still needs explicit fontSize). */
|
|
@@ -21,9 +21,6 @@ const SIZE_STYLES = {
|
|
|
21
21
|
clearIconSize: 10,
|
|
22
22
|
addNewIconSize: 14,
|
|
23
23
|
insideSelectMarginLeft: "-0.5rem",
|
|
24
|
-
dropdownTextFontSize: "xs",
|
|
25
|
-
optionRowMinH: "1.5rem",
|
|
26
|
-
optionRowPy: 1.5,
|
|
27
24
|
checkboxSize: "sm",
|
|
28
25
|
},
|
|
29
26
|
sm: {
|
|
@@ -42,9 +39,6 @@ const SIZE_STYLES = {
|
|
|
42
39
|
clearIconSize: 12,
|
|
43
40
|
addNewIconSize: 16,
|
|
44
41
|
insideSelectMarginLeft: "-0.625rem",
|
|
45
|
-
dropdownTextFontSize: "sm",
|
|
46
|
-
optionRowMinH: "2rem",
|
|
47
|
-
optionRowPy: 2,
|
|
48
42
|
checkboxSize: "sm",
|
|
49
43
|
},
|
|
50
44
|
md: {
|
|
@@ -63,9 +57,6 @@ const SIZE_STYLES = {
|
|
|
63
57
|
clearIconSize: 12,
|
|
64
58
|
addNewIconSize: 16,
|
|
65
59
|
insideSelectMarginLeft: "-0.75rem",
|
|
66
|
-
dropdownTextFontSize: "sm",
|
|
67
|
-
optionRowMinH: "2.5rem",
|
|
68
|
-
optionRowPy: 2,
|
|
69
60
|
checkboxSize: "md",
|
|
70
61
|
},
|
|
71
62
|
lg: {
|
|
@@ -84,9 +75,6 @@ const SIZE_STYLES = {
|
|
|
84
75
|
clearIconSize: 14,
|
|
85
76
|
addNewIconSize: 18,
|
|
86
77
|
insideSelectMarginLeft: "-0.875rem",
|
|
87
|
-
dropdownTextFontSize: "md",
|
|
88
|
-
optionRowMinH: "2.75rem",
|
|
89
|
-
optionRowPy: 2.5,
|
|
90
78
|
checkboxSize: "lg",
|
|
91
79
|
},
|
|
92
80
|
};
|
|
@@ -38,12 +38,14 @@ declare const SelectNative: React.ForwardRefExoticComponent<{
|
|
|
38
38
|
defaultSearchValue?: string;
|
|
39
39
|
onSearchChange?: (query: string) => void;
|
|
40
40
|
searchDebounceMs?: number;
|
|
41
|
+
filterOptions?: boolean;
|
|
41
42
|
isClearable?: boolean;
|
|
42
43
|
onClear?: () => void;
|
|
43
44
|
isLoading?: boolean;
|
|
44
45
|
loadingText?: string;
|
|
45
46
|
noOptionsMessage?: React.ReactNode;
|
|
46
47
|
onLoadMore?: () => void;
|
|
48
|
+
hasMore?: boolean;
|
|
47
49
|
renderOption?: (option: OptionProp, state: {
|
|
48
50
|
isSelected: boolean;
|
|
49
51
|
isActive: boolean;
|