@central-ui/react 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +33 -2
- package/dist/components/accordion/index.d.ts +73 -0
- package/dist/components/accordion/index.d.ts.map +1 -0
- package/dist/components/accordion/index.js +92 -0
- package/dist/components/accordion/index.js.map +1 -0
- package/dist/components/autocomplete/index.d.ts +365 -0
- package/dist/components/autocomplete/index.d.ts.map +1 -0
- package/dist/components/autocomplete/index.js +369 -0
- package/dist/components/autocomplete/index.js.map +1 -0
- package/dist/components/avatar/index.d.ts +48 -0
- package/dist/components/avatar/index.d.ts.map +1 -0
- package/dist/components/avatar/index.js +60 -0
- package/dist/components/avatar/index.js.map +1 -0
- package/dist/components/badge/index.d.ts +29 -0
- package/dist/components/badge/index.d.ts.map +1 -0
- package/dist/components/badge/index.js +36 -0
- package/dist/components/badge/index.js.map +1 -0
- package/dist/components/banner/index.d.ts +71 -0
- package/dist/components/banner/index.d.ts.map +1 -0
- package/dist/components/banner/index.js +119 -0
- package/dist/components/banner/index.js.map +1 -0
- package/dist/components/breadcrumb/index.d.ts +31 -0
- package/dist/components/breadcrumb/index.d.ts.map +1 -0
- package/dist/components/breadcrumb/index.js +42 -0
- package/dist/components/breadcrumb/index.js.map +1 -0
- package/dist/components/button/index.d.ts +48 -0
- package/dist/components/button/index.d.ts.map +1 -0
- package/dist/components/button/index.js +60 -0
- package/dist/components/button/index.js.map +1 -0
- package/dist/components/card/index.d.ts +34 -0
- package/dist/components/card/index.d.ts.map +1 -0
- package/dist/components/card/index.js +45 -0
- package/dist/components/card/index.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +66 -0
- package/dist/components/checkbox/index.d.ts.map +1 -0
- package/dist/components/checkbox/index.js +146 -0
- package/dist/components/checkbox/index.js.map +1 -0
- package/dist/components/chip/index.d.ts +26 -0
- package/dist/components/chip/index.d.ts.map +1 -0
- package/dist/components/chip/index.js +40 -0
- package/dist/components/chip/index.js.map +1 -0
- package/dist/components/combobox/index.d.ts +427 -0
- package/dist/components/combobox/index.d.ts.map +1 -0
- package/dist/components/combobox/index.js +416 -0
- package/dist/components/combobox/index.js.map +1 -0
- package/dist/components/datepicker/index.d.ts +297 -0
- package/dist/components/datepicker/index.d.ts.map +1 -0
- package/dist/components/datepicker/index.js +507 -0
- package/dist/components/datepicker/index.js.map +1 -0
- package/dist/components/dialog/index.d.ts +88 -0
- package/dist/components/dialog/index.d.ts.map +1 -0
- package/dist/components/dialog/index.js +102 -0
- package/dist/components/dialog/index.js.map +1 -0
- package/dist/components/divider/index.d.ts +21 -0
- package/dist/components/divider/index.d.ts.map +1 -0
- package/dist/components/divider/index.js +24 -0
- package/dist/components/divider/index.js.map +1 -0
- package/dist/components/drawer/index.d.ts +99 -0
- package/dist/components/drawer/index.d.ts.map +1 -0
- package/dist/components/drawer/index.js +137 -0
- package/dist/components/drawer/index.js.map +1 -0
- package/dist/components/empty/index.d.ts +40 -0
- package/dist/components/empty/index.d.ts.map +1 -0
- package/dist/components/empty/index.js +61 -0
- package/dist/components/empty/index.js.map +1 -0
- package/dist/components/fileupload/index.d.ts +107 -0
- package/dist/components/fileupload/index.d.ts.map +1 -0
- package/dist/components/fileupload/index.js +185 -0
- package/dist/components/fileupload/index.js.map +1 -0
- package/dist/components/form/index.d.ts +34 -0
- package/dist/components/form/index.d.ts.map +1 -0
- package/dist/components/form/index.js +35 -0
- package/dist/components/form/index.js.map +1 -0
- package/dist/components/input/index.d.ts +85 -0
- package/dist/components/input/index.d.ts.map +1 -0
- package/dist/components/input/index.js +249 -0
- package/dist/components/input/index.js.map +1 -0
- package/dist/components/menu/index.d.ts +115 -0
- package/dist/components/menu/index.d.ts.map +1 -0
- package/dist/components/menu/index.js +188 -0
- package/dist/components/menu/index.js.map +1 -0
- package/dist/components/metric/index.d.ts +24 -0
- package/dist/components/metric/index.d.ts.map +1 -0
- package/dist/components/metric/index.js +26 -0
- package/dist/components/metric/index.js.map +1 -0
- package/dist/components/numberinput/index.d.ts +105 -0
- package/dist/components/numberinput/index.d.ts.map +1 -0
- package/dist/components/numberinput/index.js +240 -0
- package/dist/components/numberinput/index.js.map +1 -0
- package/dist/components/otpinput/index.d.ts +63 -0
- package/dist/components/otpinput/index.d.ts.map +1 -0
- package/dist/components/otpinput/index.js +133 -0
- package/dist/components/otpinput/index.js.map +1 -0
- package/dist/components/page/index.d.ts +54 -0
- package/dist/components/page/index.d.ts.map +1 -0
- package/dist/components/page/index.js +100 -0
- package/dist/components/page/index.js.map +1 -0
- package/dist/components/passwordinput/index.d.ts +88 -0
- package/dist/components/passwordinput/index.d.ts.map +1 -0
- package/dist/components/passwordinput/index.js +251 -0
- package/dist/components/passwordinput/index.js.map +1 -0
- package/dist/components/popover/index.d.ts +67 -0
- package/dist/components/popover/index.d.ts.map +1 -0
- package/dist/components/popover/index.js +76 -0
- package/dist/components/popover/index.js.map +1 -0
- package/dist/components/radio/index.d.ts +90 -0
- package/dist/components/radio/index.d.ts.map +1 -0
- package/dist/components/radio/index.js +232 -0
- package/dist/components/radio/index.js.map +1 -0
- package/dist/components/ratinginput/index.d.ts +59 -0
- package/dist/components/ratinginput/index.d.ts.map +1 -0
- package/dist/components/ratinginput/index.js +88 -0
- package/dist/components/ratinginput/index.js.map +1 -0
- package/dist/components/select/index.d.ts +205 -0
- package/dist/components/select/index.d.ts.map +1 -0
- package/dist/components/select/index.js +234 -0
- package/dist/components/select/index.js.map +1 -0
- package/dist/components/skeleton/index.d.ts +19 -0
- package/dist/components/skeleton/index.d.ts.map +1 -0
- package/dist/components/skeleton/index.js +20 -0
- package/dist/components/skeleton/index.js.map +1 -0
- package/dist/components/slider/index.d.ts +72 -0
- package/dist/components/slider/index.d.ts.map +1 -0
- package/dist/components/slider/index.js +159 -0
- package/dist/components/slider/index.js.map +1 -0
- package/dist/components/spinner/index.d.ts +14 -0
- package/dist/components/spinner/index.d.ts.map +1 -0
- package/dist/components/spinner/index.js +15 -0
- package/dist/components/spinner/index.js.map +1 -0
- package/dist/components/splitter/index.d.ts +49 -0
- package/dist/components/splitter/index.d.ts.map +1 -0
- package/dist/components/splitter/index.js +67 -0
- package/dist/components/splitter/index.js.map +1 -0
- package/dist/components/stepper/index.d.ts +82 -0
- package/dist/components/stepper/index.d.ts.map +1 -0
- package/dist/components/stepper/index.js +100 -0
- package/dist/components/stepper/index.js.map +1 -0
- package/dist/components/switch/index.d.ts +57 -0
- package/dist/components/switch/index.d.ts.map +1 -0
- package/dist/components/switch/index.js +101 -0
- package/dist/components/switch/index.js.map +1 -0
- package/dist/components/table/index.d.ts +110 -0
- package/dist/components/table/index.d.ts.map +1 -0
- package/dist/components/table/index.js +211 -0
- package/dist/components/table/index.js.map +1 -0
- package/dist/components/tabs/index.d.ts +67 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/components/tabs/index.js +78 -0
- package/dist/components/tabs/index.js.map +1 -0
- package/dist/components/textarea/index.d.ts +72 -0
- package/dist/components/textarea/index.d.ts.map +1 -0
- package/dist/components/textarea/index.js +205 -0
- package/dist/components/textarea/index.js.map +1 -0
- package/dist/components/textlink/index.d.ts +39 -0
- package/dist/components/textlink/index.d.ts.map +1 -0
- package/dist/components/textlink/index.js +42 -0
- package/dist/components/textlink/index.js.map +1 -0
- package/dist/components/toast/index.d.ts +111 -0
- package/dist/components/toast/index.d.ts.map +1 -0
- package/dist/components/toast/index.js +205 -0
- package/dist/components/toast/index.js.map +1 -0
- package/dist/components/toggle/index.d.ts +41 -0
- package/dist/components/toggle/index.d.ts.map +1 -0
- package/dist/components/toggle/index.js +78 -0
- package/dist/components/toggle/index.js.map +1 -0
- package/dist/components/tooltip/index.d.ts +44 -0
- package/dist/components/tooltip/index.d.ts.map +1 -0
- package/dist/components/tooltip/index.js +51 -0
- package/dist/components/tooltip/index.js.map +1 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +46 -0
- package/dist/index.js.map +1 -0
- package/dist/types/Style.d.ts +14 -0
- package/dist/types/Style.d.ts.map +1 -0
- package/dist/types/Style.js +2 -0
- package/dist/types/Style.js.map +1 -0
- package/dist/types/Tone.d.ts +4 -0
- package/dist/types/Tone.d.ts.map +1 -0
- package/dist/types/Tone.js +2 -0
- package/dist/types/Tone.js.map +1 -0
- package/dist/utils/ArkStateToDataAttributes.d.ts +4 -0
- package/dist/utils/ArkStateToDataAttributes.d.ts.map +1 -0
- package/dist/utils/ArkStateToDataAttributes.js +9 -0
- package/dist/utils/ArkStateToDataAttributes.js.map +1 -0
- package/dist/utils/Tone.d.ts +3 -0
- package/dist/utils/Tone.d.ts.map +1 -0
- package/dist/utils/Tone.js +7 -0
- package/dist/utils/Tone.js.map +1 -0
- package/dist/utils/useForwardClickToInput.d.ts +4 -0
- package/dist/utils/useForwardClickToInput.d.ts.map +1 -0
- package/dist/utils/useForwardClickToInput.js +27 -0
- package/dist/utils/useForwardClickToInput.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts +198 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.js +180 -0
- package/dist/utils/virtualization/useVirtualizedAutocomplete.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.d.ts +200 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.js +174 -0
- package/dist/utils/virtualization/useVirtualizedCombobox.js.map +1 -0
- package/dist/utils/virtualization/useVirtualizedTable.d.ts +169 -0
- package/dist/utils/virtualization/useVirtualizedTable.d.ts.map +1 -0
- package/dist/utils/virtualization/useVirtualizedTable.js +127 -0
- package/dist/utils/virtualization/useVirtualizedTable.js.map +1 -0
- package/package.json +64 -4
- package/src/presets/shopify-polaris/shopify-polaris-accordion.css +28 -0
- package/src/presets/shopify-polaris/shopify-polaris-autocomplete.css +155 -0
- package/src/presets/shopify-polaris/shopify-polaris-avatar.css +47 -0
- package/src/presets/shopify-polaris/shopify-polaris-badge.css +27 -0
- package/src/presets/shopify-polaris/shopify-polaris-banner.css +111 -0
- package/src/presets/shopify-polaris/shopify-polaris-breadcrumb.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-button.css +79 -0
- package/src/presets/shopify-polaris/shopify-polaris-card.css +28 -0
- package/src/presets/shopify-polaris/shopify-polaris-checkbox.css +52 -0
- package/src/presets/shopify-polaris/shopify-polaris-chip.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-combobox.css +179 -0
- package/src/presets/shopify-polaris/shopify-polaris-datepicker.css +183 -0
- package/src/presets/shopify-polaris/shopify-polaris-dialog.css +40 -0
- package/src/presets/shopify-polaris/shopify-polaris-divider.css +12 -0
- package/src/presets/shopify-polaris/shopify-polaris-drawer.css +40 -0
- package/src/presets/shopify-polaris/shopify-polaris-empty.css +32 -0
- package/src/presets/shopify-polaris/shopify-polaris-fileupload.css +78 -0
- package/src/presets/shopify-polaris/shopify-polaris-form.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-input.css +72 -0
- package/src/presets/shopify-polaris/shopify-polaris-menu.css +112 -0
- package/src/presets/shopify-polaris/shopify-polaris-metric.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-numberinput.css +88 -0
- package/src/presets/shopify-polaris/shopify-polaris-otpinput.css +42 -0
- package/src/presets/shopify-polaris/shopify-polaris-page.css +69 -0
- package/src/presets/shopify-polaris/shopify-polaris-passwordinput.css +76 -0
- package/src/presets/shopify-polaris/shopify-polaris-popover.css +47 -0
- package/src/presets/shopify-polaris/shopify-polaris-radio.css +54 -0
- package/src/presets/shopify-polaris/shopify-polaris-ratinginput.css +42 -0
- package/src/presets/shopify-polaris/shopify-polaris-select.css +119 -0
- package/src/presets/shopify-polaris/shopify-polaris-skeleton.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-slider.css +86 -0
- package/src/presets/shopify-polaris/shopify-polaris-spinner.css +16 -0
- package/src/presets/shopify-polaris/shopify-polaris-splitter.css +34 -0
- package/src/presets/shopify-polaris/shopify-polaris-stepper.css +55 -0
- package/src/presets/shopify-polaris/shopify-polaris-switch.css +38 -0
- package/src/presets/shopify-polaris/shopify-polaris-table.css +72 -0
- package/src/presets/shopify-polaris/shopify-polaris-tabs.css +33 -0
- package/src/presets/shopify-polaris/shopify-polaris-textarea.css +61 -0
- package/src/presets/shopify-polaris/shopify-polaris-textlink.css +8 -0
- package/src/presets/shopify-polaris/shopify-polaris-toggle.css +52 -0
- package/src/presets/shopify-polaris/shopify-polaris-tooltip.css +31 -0
- package/src/presets/shopify-polaris/shopify-polaris.css +380 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-accordion.css +28 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-autocomplete.css +155 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-avatar.css +47 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-badge.css +27 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-banner.css +96 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-breadcrumb.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-button.css +68 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-card.css +28 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-checkbox.css +52 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-chip.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-combobox.css +184 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-datepicker.css +187 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-dialog.css +40 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-divider.css +12 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-drawer.css +40 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-empty.css +32 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-fileupload.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-form.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-input.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-menu.css +112 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-metric.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-numberinput.css +88 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-otpinput.css +42 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-page.css +69 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-passwordinput.css +76 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-popover.css +47 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-radio.css +54 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-ratinginput.css +42 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-select.css +118 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-skeleton.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-slider.css +86 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-spinner.css +16 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-splitter.css +34 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-stepper.css +55 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-switch.css +38 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-table.css +72 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tabs.css +33 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textarea.css +61 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-textlink.css +8 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toast.css +43 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-toggle.css +52 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy-tooltip.css +31 -0
- package/src/presets/shopify-polaris-legacy/shopify-polaris-legacy.css +381 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { Combobox as BaseCombobox } from '@base-ui/react/combobox';
|
|
2
|
+
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
3
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
4
|
+
/**
|
|
5
|
+
* Virtualize a large Combobox while keeping native search and keyboard behavior.
|
|
6
|
+
* Call this hook in your component, outside Root. Spread each returned props object
|
|
7
|
+
* on its matching element, and keep ComboboxFilteredItemsSync mounted inside Popup
|
|
8
|
+
* so native filtering supplies the complete results. Keep rootProps.onItemHighlighted
|
|
9
|
+
* connected; it scrolls keyboard highlights into view. Merge it if adding your own callback.
|
|
10
|
+
*
|
|
11
|
+
* Filtered groups are flattened into one list: this hook does not virtualize separate
|
|
12
|
+
* group headings. Use one shared heading, as below, or omit GroupLabel for a flat list
|
|
13
|
+
* and adjust paddingStart. Preserve the returned refs and layout styles when customizing.
|
|
14
|
+
* Render only rows, but give Group all filteredItems and each Item its returned index.
|
|
15
|
+
* The mapped rows already preserve your custom item fields; no manual annotation is needed.
|
|
16
|
+
*
|
|
17
|
+
* @example Complete virtualized Combobox
|
|
18
|
+
* import {
|
|
19
|
+
* Combobox,
|
|
20
|
+
* ComboboxFilteredItemsSync,
|
|
21
|
+
* useVirtualizedCombobox,
|
|
22
|
+
* } from '@central-ui/react'
|
|
23
|
+
*
|
|
24
|
+
* // Replace this sample with your larger dataset; keep the source stable between renders.
|
|
25
|
+
* const cityGroups = [{
|
|
26
|
+
* label: 'Cities',
|
|
27
|
+
* items: [
|
|
28
|
+
* { value: 'ma-rabat', label: 'Rabat', country: 'Morocco', population: 580000 },
|
|
29
|
+
* { value: 'ma-casablanca', label: 'Casablanca', country: 'Morocco', population: 3359818 },
|
|
30
|
+
* { value: 'fr-paris', label: 'Paris', country: 'France', population: 2102650 },
|
|
31
|
+
* ],
|
|
32
|
+
* }]
|
|
33
|
+
*
|
|
34
|
+
* function CityCombobox() {
|
|
35
|
+
* const virtual = useVirtualizedCombobox({
|
|
36
|
+
* items: cityGroups, // rootProps supplies these complete groups to Root.
|
|
37
|
+
* estimateSize: 34, // Match the row height plus spacing in your layout.
|
|
38
|
+
* paddingStart: 32, // Match the heading and leading space; adjust if styled differently.
|
|
39
|
+
* paddingEnd: 4, // Match the trailing group padding.
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* return (
|
|
43
|
+
* <Combobox.Root {...virtual.rootProps}>
|
|
44
|
+
* <Combobox.Container>
|
|
45
|
+
* <Combobox.Label>City</Combobox.Label>
|
|
46
|
+
* <Combobox.InputGroup>
|
|
47
|
+
* <Combobox.Input placeholder="Search cities" />
|
|
48
|
+
* <Combobox.Addon>
|
|
49
|
+
* <Combobox.AddonOpenButton aria-label="Show cities">
|
|
50
|
+
* <Combobox.AddonOpenButtonIcon />
|
|
51
|
+
* </Combobox.AddonOpenButton>
|
|
52
|
+
* <Combobox.AddonClearButton aria-label="Clear city">
|
|
53
|
+
* <Combobox.AddonClearButtonIcon />
|
|
54
|
+
* </Combobox.AddonClearButton>
|
|
55
|
+
* </Combobox.Addon>
|
|
56
|
+
* </Combobox.InputGroup>
|
|
57
|
+
* <Combobox.Popup>
|
|
58
|
+
* <ComboboxFilteredItemsSync {...virtual.filteredItemsSyncProps} />
|
|
59
|
+
* <Combobox.Empty // Keep mounted; native filtering controls visibility.
|
|
60
|
+
* >
|
|
61
|
+
* <Combobox.EmptyText>No cities found.</Combobox.EmptyText>
|
|
62
|
+
* </Combobox.Empty>
|
|
63
|
+
* <Combobox.ScrollArea {...virtual.scrollAreaProps}>
|
|
64
|
+
* <Combobox.GroupsList>
|
|
65
|
+
* {virtual.filteredItems.length > 0 && (
|
|
66
|
+
* <Combobox.Group items={virtual.filteredItems}>
|
|
67
|
+
* <Combobox.GroupLabel>Cities</Combobox.GroupLabel>
|
|
68
|
+
* <Combobox.ItemsList {...virtual.itemsListProps}>
|
|
69
|
+
* {virtual.rows.map((row) => (
|
|
70
|
+
* <Combobox.Item
|
|
71
|
+
* key={row.key}
|
|
72
|
+
* value={row.item}
|
|
73
|
+
* index={row.index} // Preserve the index in the full filtered list.
|
|
74
|
+
* {...row.itemProps}
|
|
75
|
+
* >
|
|
76
|
+
* <Combobox.ItemText>{row.item.label}</Combobox.ItemText>
|
|
77
|
+
* <Combobox.ItemIndicator />
|
|
78
|
+
* </Combobox.Item>
|
|
79
|
+
* ))}
|
|
80
|
+
* </Combobox.ItemsList>
|
|
81
|
+
* </Combobox.Group>
|
|
82
|
+
* )}
|
|
83
|
+
* </Combobox.GroupsList>
|
|
84
|
+
* </Combobox.ScrollArea>
|
|
85
|
+
* </Combobox.Popup>
|
|
86
|
+
* </Combobox.Container>
|
|
87
|
+
* </Combobox.Root>
|
|
88
|
+
* )
|
|
89
|
+
* }
|
|
90
|
+
*/
|
|
91
|
+
export function useVirtualizedCombobox({ items: source, estimateSize = 34, overscan = 5, paddingStart = 0, paddingEnd = 0, maxHeight = 320, availableHeight = 'var(--available-height)', }) {
|
|
92
|
+
const [filteredItems, setFilteredItems] = useState([]);
|
|
93
|
+
const scrollRef = useRef(null);
|
|
94
|
+
const virtualizer = useVirtualizer({
|
|
95
|
+
count: filteredItems.length,
|
|
96
|
+
getScrollElement: () => scrollRef.current,
|
|
97
|
+
estimateSize: (index) => typeof estimateSize === 'function'
|
|
98
|
+
? estimateSize(filteredItems[index], index)
|
|
99
|
+
: estimateSize,
|
|
100
|
+
getItemKey: (index) => filteredItems[index].value,
|
|
101
|
+
overscan,
|
|
102
|
+
paddingStart,
|
|
103
|
+
paddingEnd,
|
|
104
|
+
});
|
|
105
|
+
// Filtering or sorting supplies a new array; scrolling alone must not reset it.
|
|
106
|
+
useLayoutEffect(() => {
|
|
107
|
+
virtualizer.scrollToOffset(0);
|
|
108
|
+
}, [filteredItems, virtualizer]);
|
|
109
|
+
const totalSize = virtualizer.getTotalSize();
|
|
110
|
+
const virtualRows = virtualizer.getVirtualItems();
|
|
111
|
+
const scrollAreaProps = {
|
|
112
|
+
ref: scrollRef,
|
|
113
|
+
style: {
|
|
114
|
+
height: Math.min(totalSize, maxHeight),
|
|
115
|
+
maxHeight: availableHeight,
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
// Exclude the space already occupied by the caller's heading and padding.
|
|
119
|
+
const itemsListProps = {
|
|
120
|
+
style: {
|
|
121
|
+
position: 'relative',
|
|
122
|
+
height: totalSize - paddingStart - paddingEnd,
|
|
123
|
+
flexShrink: 0,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
const rows = virtualRows.map((row) => ({
|
|
127
|
+
key: row.key,
|
|
128
|
+
index: row.index,
|
|
129
|
+
item: filteredItems[row.index],
|
|
130
|
+
start: row.start,
|
|
131
|
+
size: row.size,
|
|
132
|
+
itemProps: {
|
|
133
|
+
style: {
|
|
134
|
+
position: 'absolute',
|
|
135
|
+
top: 0,
|
|
136
|
+
left: 0,
|
|
137
|
+
width: '100%',
|
|
138
|
+
transform: `translateY(${row.start - paddingStart}px)`,
|
|
139
|
+
},
|
|
140
|
+
'aria-setsize': filteredItems.length,
|
|
141
|
+
'aria-posinset': row.index + 1,
|
|
142
|
+
},
|
|
143
|
+
}));
|
|
144
|
+
const scrollToIndex = useCallback((index, options) => virtualizer.scrollToIndex(index, { align: 'auto', ...options }), [virtualizer]);
|
|
145
|
+
const onItemHighlighted = useCallback((item, details) => {
|
|
146
|
+
if (!item || details.reason === 'pointer' || details.index < 0)
|
|
147
|
+
return;
|
|
148
|
+
queueMicrotask(() => scrollToIndex(details.index));
|
|
149
|
+
}, [scrollToIndex]);
|
|
150
|
+
const onFilteredItemsChange = useCallback((next) => setFilteredItems(next), []);
|
|
151
|
+
return {
|
|
152
|
+
rootProps: { items: source, virtualized: true, onItemHighlighted },
|
|
153
|
+
filteredItemsSyncProps: { onChange: onFilteredItemsChange },
|
|
154
|
+
filteredItems,
|
|
155
|
+
scrollAreaProps,
|
|
156
|
+
itemsListProps,
|
|
157
|
+
rows,
|
|
158
|
+
scrollToIndex,
|
|
159
|
+
virtualizer,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Renders nothing. Mount inside Root (normally inside Popup) to mirror the
|
|
164
|
+
* complete native search results into the external virtualization hook.
|
|
165
|
+
* The callback infers the item type, including extra fields, without a cast.
|
|
166
|
+
*/
|
|
167
|
+
export function FilteredItemsSync({ onChange, }) {
|
|
168
|
+
const groups = BaseCombobox.useFilteredItems();
|
|
169
|
+
useLayoutEffect(() => {
|
|
170
|
+
onChange(groups.flatMap((group) => group.items));
|
|
171
|
+
}, [groups, onChange]);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
//# sourceMappingURL=useVirtualizedCombobox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualizedCombobox.js","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedCombobox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { CSSProperties } from 'react';
|
|
2
|
+
export interface UseVirtualizedTableOptions<TItem> {
|
|
3
|
+
/**
|
|
4
|
+
* Pass the rows in their current display order after filtering or sorting.
|
|
5
|
+
* Keep this array stable between those changes: a new array scrolls back to the top.
|
|
6
|
+
* @example items: orders
|
|
7
|
+
*/
|
|
8
|
+
items: readonly TItem[];
|
|
9
|
+
/**
|
|
10
|
+
* Return a unique database ID or another stable key so rows keep their identity
|
|
11
|
+
* when sorted or filtered. Avoid the array index when row order can change.
|
|
12
|
+
* @example getItemKey: (order) => order.id
|
|
13
|
+
*/
|
|
14
|
+
getItemKey: (item: TItem, index: number) => string | number;
|
|
15
|
+
/**
|
|
16
|
+
* Match the rendered row height in pixels, including any spacing between rows.
|
|
17
|
+
* Use a callback when rows have known different heights. This hook does not
|
|
18
|
+
* automatically measure rendered rows; inaccurate sizes can leave gaps or overlaps.
|
|
19
|
+
* @example estimateSize: 34
|
|
20
|
+
* @example estimateSize: (order) => order.hasDetails ? 56 : 34
|
|
21
|
+
* @default 34
|
|
22
|
+
*/
|
|
23
|
+
estimateSize?: number | ((item: TItem, index: number) => number);
|
|
24
|
+
/**
|
|
25
|
+
* Mount extra rows above and below the visible area to reduce blanks during fast
|
|
26
|
+
* scrolling. Increase this if needed, at the cost of rendering more rows.
|
|
27
|
+
* @example overscan: 8
|
|
28
|
+
* @default 5
|
|
29
|
+
*/
|
|
30
|
+
overscan?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Reserve leading scroll space before the first data row. The returned before
|
|
33
|
+
* spacer includes it; do not add that space a second time in your markup.
|
|
34
|
+
* The table header is outside Body, so do not include its height here.
|
|
35
|
+
* @example paddingStart: 16
|
|
36
|
+
* @default 0
|
|
37
|
+
*/
|
|
38
|
+
paddingStart?: number;
|
|
39
|
+
/**
|
|
40
|
+
* Reserve trailing scroll space after the last data row. The returned after
|
|
41
|
+
* spacer includes it; do not add that space a second time in your markup.
|
|
42
|
+
* @example paddingEnd: 16
|
|
43
|
+
* @default 0
|
|
44
|
+
*/
|
|
45
|
+
paddingEnd?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Cap the scrolling viewport height while allowing short lists to shrink.
|
|
48
|
+
* The header stays outside this viewport and does not count toward this cap.
|
|
49
|
+
* @example maxHeight: 480
|
|
50
|
+
* @default 320
|
|
51
|
+
*/
|
|
52
|
+
maxHeight?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Apply an additional CSS max-height to the scrolling viewport.
|
|
55
|
+
* Use this when the surrounding layout leaves less space than maxHeight.
|
|
56
|
+
* It constrains Body only; allow separately for the header height.
|
|
57
|
+
* @example availableHeight: 'calc(100dvh - 160px)'
|
|
58
|
+
* @default maxHeight
|
|
59
|
+
*/
|
|
60
|
+
availableHeight?: CSSProperties['maxHeight'];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Virtualize data rows while keeping table markup and columns under your control.
|
|
64
|
+
* Call the hook in your component. Spread bodyProps on the scrolling Table.Body and
|
|
65
|
+
* rowProps on each visible Table.Row. Render both spacer rows to preserve the scroll
|
|
66
|
+
* height of unmounted rows. The header is outside Body; keep its column widths aligned
|
|
67
|
+
* with the body. Do not make Table.ScrollArea a second vertical scrolling viewport.
|
|
68
|
+
*
|
|
69
|
+
* This example follows the demo's block table/grid row layout, with explicit table
|
|
70
|
+
* roles and row indices so assistive technology knows each row's position in the full
|
|
71
|
+
* collection. Keep the same grid columns for the header and body, preserve bodyProps.ref
|
|
72
|
+
* and styles, and account for the header with aria-rowcount and aria-rowindex.
|
|
73
|
+
*
|
|
74
|
+
* @example Complete virtualized table
|
|
75
|
+
* import { Table, useVirtualizedTable } from '@central-ui/react'
|
|
76
|
+
*
|
|
77
|
+
* // Stable sample records; sorting/filtering should intentionally replace this array.
|
|
78
|
+
* const orders = Array.from({ length: 5000 }, (_, index) => ({
|
|
79
|
+
* id: 'order-' + (10000 + index),
|
|
80
|
+
* number: '#' + (10000 + index),
|
|
81
|
+
* customer: index % 2 === 0 ? 'Alex Morgan' : 'Sam Lee',
|
|
82
|
+
* }))
|
|
83
|
+
*
|
|
84
|
+
* function OrdersTable() {
|
|
85
|
+
* const virtual = useVirtualizedTable({
|
|
86
|
+
* items: orders,
|
|
87
|
+
* getItemKey: (order) => order.id, // Preserve identity after sorting/filtering.
|
|
88
|
+
* estimateSize: 40, // Sets the rendered data row height through rowProps.
|
|
89
|
+
* maxHeight: 480, // Body scrolls; the header stays outside this height.
|
|
90
|
+
* })
|
|
91
|
+
*
|
|
92
|
+
* return (
|
|
93
|
+
* <Table.Root>
|
|
94
|
+
* <Table.Surface>
|
|
95
|
+
* <Table.Table className="block w-full" role="table"
|
|
96
|
+
* aria-label="Orders" aria-rowcount={orders.length + 1}>
|
|
97
|
+
* <Table.Head role="rowgroup"
|
|
98
|
+
* className="static block overflow-hidden [scrollbar-gutter:stable]">
|
|
99
|
+
* <Table.Row role="row" aria-rowindex={1}
|
|
100
|
+
* className="grid grid-cols-2" style={{ height: 40 }}>
|
|
101
|
+
* <Table.HeadCell role="columnheader" className="flex items-center">
|
|
102
|
+
* Order
|
|
103
|
+
* </Table.HeadCell>
|
|
104
|
+
* <Table.HeadCell role="columnheader" className="flex items-center">
|
|
105
|
+
* Customer
|
|
106
|
+
* </Table.HeadCell>
|
|
107
|
+
* </Table.Row>
|
|
108
|
+
* </Table.Head>
|
|
109
|
+
* <Table.Body {...virtual.bodyProps} role="rowgroup"
|
|
110
|
+
* className="block overflow-y-auto [scrollbar-gutter:stable]">
|
|
111
|
+
* {virtual.spacers.before > 0 && (
|
|
112
|
+
* <tr aria-hidden="true" className="block">
|
|
113
|
+
* <td colSpan={2} className="block"
|
|
114
|
+
* style={{ height: virtual.spacers.before, padding: 0, border: 0 }} />
|
|
115
|
+
* </tr>
|
|
116
|
+
* )}
|
|
117
|
+
* {virtual.rows.map((row) => (
|
|
118
|
+
* <Table.Row key={row.key} {...row.rowProps} role="row"
|
|
119
|
+
* className="grid grid-cols-2" aria-rowindex={row.index + 2}>
|
|
120
|
+
* <Table.Cell role="cell" className="flex h-full items-center">
|
|
121
|
+
* {row.item.number}
|
|
122
|
+
* </Table.Cell>
|
|
123
|
+
* <Table.Cell role="cell" className="flex h-full items-center">
|
|
124
|
+
* {row.item.customer}
|
|
125
|
+
* </Table.Cell>
|
|
126
|
+
* </Table.Row>
|
|
127
|
+
* ))}
|
|
128
|
+
* {virtual.spacers.after > 0 && (
|
|
129
|
+
* <tr aria-hidden="true" className="block">
|
|
130
|
+
* <td colSpan={2} className="block"
|
|
131
|
+
* style={{ height: virtual.spacers.after, padding: 0, border: 0 }} />
|
|
132
|
+
* </tr>
|
|
133
|
+
* )}
|
|
134
|
+
* </Table.Body>
|
|
135
|
+
* </Table.Table>
|
|
136
|
+
* </Table.Surface>
|
|
137
|
+
* </Table.Root>
|
|
138
|
+
* )
|
|
139
|
+
* }
|
|
140
|
+
*/
|
|
141
|
+
export declare function useVirtualizedTable<TItem>({ items, getItemKey, estimateSize, overscan, paddingStart, paddingEnd, maxHeight, availableHeight, }: UseVirtualizedTableOptions<TItem>): {
|
|
142
|
+
bodyProps: {
|
|
143
|
+
ref: import("react").RefObject<HTMLTableSectionElement | null>;
|
|
144
|
+
style: {
|
|
145
|
+
height: number;
|
|
146
|
+
maxHeight: import("csstype").Property.MaxHeight<string | number>;
|
|
147
|
+
overflowY: "auto";
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
rows: {
|
|
151
|
+
key: string | number | bigint;
|
|
152
|
+
index: number;
|
|
153
|
+
item: TItem;
|
|
154
|
+
start: number;
|
|
155
|
+
size: number;
|
|
156
|
+
rowProps: {
|
|
157
|
+
style: {
|
|
158
|
+
height: number;
|
|
159
|
+
};
|
|
160
|
+
};
|
|
161
|
+
}[];
|
|
162
|
+
spacers: {
|
|
163
|
+
before: number;
|
|
164
|
+
after: number;
|
|
165
|
+
};
|
|
166
|
+
scrollToIndex: (index: number, options?: Parameters<(index: number, { align: initialAlign, behavior, }?: import("@tanstack/virtual-core").ScrollToOptions) => void>[1]) => void;
|
|
167
|
+
virtualizer: import("@tanstack/react-virtual").ReactVirtualizer<HTMLTableSectionElement, HTMLTableRowElement>;
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=useVirtualizedTable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualizedTable.d.ts","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedTable.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAG1C,MAAM,WAAW,0BAA0B,CAAC,KAAK;IAC/C;;;;OAIG;IACH,KAAK,EAAE,SAAS,KAAK,EAAE,CAAA;IACvB;;;;OAIG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,CAAA;IAC3D;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,CAAA;IAChE;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CAC7C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8EG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,EACzC,KAAK,EACL,UAAU,EACV,YAAiB,EACjB,QAAY,EACZ,YAAgB,EAChB,UAAc,EACd,SAAe,EACf,eAA2B,GAC5B,EAAE,0BAA0B,CAAC,KAAK,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;2BAmDvB,MAAM,YACH,UAAU,gHAAkC,CAAC,CAAC,CAAC;;EAM9D"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { useCallback, useLayoutEffect, useRef } from 'react';
|
|
2
|
+
import { useVirtualizer } from '@tanstack/react-virtual';
|
|
3
|
+
/**
|
|
4
|
+
* Virtualize data rows while keeping table markup and columns under your control.
|
|
5
|
+
* Call the hook in your component. Spread bodyProps on the scrolling Table.Body and
|
|
6
|
+
* rowProps on each visible Table.Row. Render both spacer rows to preserve the scroll
|
|
7
|
+
* height of unmounted rows. The header is outside Body; keep its column widths aligned
|
|
8
|
+
* with the body. Do not make Table.ScrollArea a second vertical scrolling viewport.
|
|
9
|
+
*
|
|
10
|
+
* This example follows the demo's block table/grid row layout, with explicit table
|
|
11
|
+
* roles and row indices so assistive technology knows each row's position in the full
|
|
12
|
+
* collection. Keep the same grid columns for the header and body, preserve bodyProps.ref
|
|
13
|
+
* and styles, and account for the header with aria-rowcount and aria-rowindex.
|
|
14
|
+
*
|
|
15
|
+
* @example Complete virtualized table
|
|
16
|
+
* import { Table, useVirtualizedTable } from '@central-ui/react'
|
|
17
|
+
*
|
|
18
|
+
* // Stable sample records; sorting/filtering should intentionally replace this array.
|
|
19
|
+
* const orders = Array.from({ length: 5000 }, (_, index) => ({
|
|
20
|
+
* id: 'order-' + (10000 + index),
|
|
21
|
+
* number: '#' + (10000 + index),
|
|
22
|
+
* customer: index % 2 === 0 ? 'Alex Morgan' : 'Sam Lee',
|
|
23
|
+
* }))
|
|
24
|
+
*
|
|
25
|
+
* function OrdersTable() {
|
|
26
|
+
* const virtual = useVirtualizedTable({
|
|
27
|
+
* items: orders,
|
|
28
|
+
* getItemKey: (order) => order.id, // Preserve identity after sorting/filtering.
|
|
29
|
+
* estimateSize: 40, // Sets the rendered data row height through rowProps.
|
|
30
|
+
* maxHeight: 480, // Body scrolls; the header stays outside this height.
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* return (
|
|
34
|
+
* <Table.Root>
|
|
35
|
+
* <Table.Surface>
|
|
36
|
+
* <Table.Table className="block w-full" role="table"
|
|
37
|
+
* aria-label="Orders" aria-rowcount={orders.length + 1}>
|
|
38
|
+
* <Table.Head role="rowgroup"
|
|
39
|
+
* className="static block overflow-hidden [scrollbar-gutter:stable]">
|
|
40
|
+
* <Table.Row role="row" aria-rowindex={1}
|
|
41
|
+
* className="grid grid-cols-2" style={{ height: 40 }}>
|
|
42
|
+
* <Table.HeadCell role="columnheader" className="flex items-center">
|
|
43
|
+
* Order
|
|
44
|
+
* </Table.HeadCell>
|
|
45
|
+
* <Table.HeadCell role="columnheader" className="flex items-center">
|
|
46
|
+
* Customer
|
|
47
|
+
* </Table.HeadCell>
|
|
48
|
+
* </Table.Row>
|
|
49
|
+
* </Table.Head>
|
|
50
|
+
* <Table.Body {...virtual.bodyProps} role="rowgroup"
|
|
51
|
+
* className="block overflow-y-auto [scrollbar-gutter:stable]">
|
|
52
|
+
* {virtual.spacers.before > 0 && (
|
|
53
|
+
* <tr aria-hidden="true" className="block">
|
|
54
|
+
* <td colSpan={2} className="block"
|
|
55
|
+
* style={{ height: virtual.spacers.before, padding: 0, border: 0 }} />
|
|
56
|
+
* </tr>
|
|
57
|
+
* )}
|
|
58
|
+
* {virtual.rows.map((row) => (
|
|
59
|
+
* <Table.Row key={row.key} {...row.rowProps} role="row"
|
|
60
|
+
* className="grid grid-cols-2" aria-rowindex={row.index + 2}>
|
|
61
|
+
* <Table.Cell role="cell" className="flex h-full items-center">
|
|
62
|
+
* {row.item.number}
|
|
63
|
+
* </Table.Cell>
|
|
64
|
+
* <Table.Cell role="cell" className="flex h-full items-center">
|
|
65
|
+
* {row.item.customer}
|
|
66
|
+
* </Table.Cell>
|
|
67
|
+
* </Table.Row>
|
|
68
|
+
* ))}
|
|
69
|
+
* {virtual.spacers.after > 0 && (
|
|
70
|
+
* <tr aria-hidden="true" className="block">
|
|
71
|
+
* <td colSpan={2} className="block"
|
|
72
|
+
* style={{ height: virtual.spacers.after, padding: 0, border: 0 }} />
|
|
73
|
+
* </tr>
|
|
74
|
+
* )}
|
|
75
|
+
* </Table.Body>
|
|
76
|
+
* </Table.Table>
|
|
77
|
+
* </Table.Surface>
|
|
78
|
+
* </Table.Root>
|
|
79
|
+
* )
|
|
80
|
+
* }
|
|
81
|
+
*/
|
|
82
|
+
export function useVirtualizedTable({ items, getItemKey, estimateSize = 34, overscan = 5, paddingStart = 0, paddingEnd = 0, maxHeight = 320, availableHeight = maxHeight, }) {
|
|
83
|
+
const bodyRef = useRef(null);
|
|
84
|
+
const virtualizer = useVirtualizer({
|
|
85
|
+
count: items.length,
|
|
86
|
+
getScrollElement: () => bodyRef.current,
|
|
87
|
+
estimateSize: (index) => typeof estimateSize === 'function'
|
|
88
|
+
? estimateSize(items[index], index)
|
|
89
|
+
: estimateSize,
|
|
90
|
+
getItemKey: (index) => getItemKey(items[index], index),
|
|
91
|
+
overscan,
|
|
92
|
+
paddingStart,
|
|
93
|
+
paddingEnd,
|
|
94
|
+
});
|
|
95
|
+
// Filtering or sorting supplies a new array; scrolling alone must not reset it.
|
|
96
|
+
useLayoutEffect(() => {
|
|
97
|
+
virtualizer.scrollToOffset(0);
|
|
98
|
+
}, [items, virtualizer]);
|
|
99
|
+
const totalSize = virtualizer.getTotalSize();
|
|
100
|
+
const virtualRows = virtualizer.getVirtualItems();
|
|
101
|
+
const bodyProps = {
|
|
102
|
+
ref: bodyRef,
|
|
103
|
+
style: {
|
|
104
|
+
height: Math.min(totalSize, maxHeight),
|
|
105
|
+
maxHeight: availableHeight,
|
|
106
|
+
overflowY: 'auto',
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
const rows = virtualRows.map((row) => ({
|
|
110
|
+
key: row.key,
|
|
111
|
+
index: row.index,
|
|
112
|
+
item: items[row.index],
|
|
113
|
+
start: row.start,
|
|
114
|
+
size: row.size,
|
|
115
|
+
rowProps: { style: { height: row.size } },
|
|
116
|
+
}));
|
|
117
|
+
// Table rows stay in normal flow; the caller renders these as spacer rows.
|
|
118
|
+
const spacers = {
|
|
119
|
+
before: virtualRows[0]?.start ?? 0,
|
|
120
|
+
after: virtualRows.length
|
|
121
|
+
? totalSize - virtualRows[virtualRows.length - 1].end
|
|
122
|
+
: 0,
|
|
123
|
+
};
|
|
124
|
+
const scrollToIndex = useCallback((index, options) => virtualizer.scrollToIndex(index, { align: 'auto', ...options }), [virtualizer]);
|
|
125
|
+
return { bodyProps, rows, spacers, scrollToIndex, virtualizer };
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=useVirtualizedTable.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useVirtualizedTable.js","sourceRoot":"","sources":["../../../src/utils/virtualization/useVirtualizedTable.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,66 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@central-ui/react",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Accessible React components with optional Tailwind CSS style presets",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"react",
|
|
8
|
+
"ui",
|
|
9
|
+
"components",
|
|
10
|
+
"ark-ui",
|
|
11
|
+
"unstyled"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"sideEffects": [
|
|
18
|
+
"./src/presets/**/*.css"
|
|
19
|
+
],
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"src/presets",
|
|
23
|
+
"README.md",
|
|
24
|
+
"LICENSE"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./presets/shopify-polaris-legacy.css": "./src/presets/shopify-polaris-legacy/shopify-polaris-legacy.css",
|
|
32
|
+
"./presets/shopify-polaris.css": "./src/presets/shopify-polaris/shopify-polaris.css"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "tsc --project tsconfig.json",
|
|
36
|
+
"prepack": "npm run build",
|
|
37
|
+
"typecheck": "tsc --project tsconfig.json --noEmit"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
41
|
+
"react-dom": "^18.0.0 || ^19.0.0",
|
|
42
|
+
"tailwindcss": "^4.2.1"
|
|
43
|
+
},
|
|
44
|
+
"peerDependenciesMeta": {
|
|
45
|
+
"tailwindcss": {
|
|
46
|
+
"optional": true
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/react": "19.3.0",
|
|
51
|
+
"@types/react-dom": "19.3.0",
|
|
52
|
+
"typescript": "6.0.3"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@ark-ui/react": "5.39.3",
|
|
56
|
+
"@base-ui/react": "1.9.0",
|
|
57
|
+
"@internationalized/date": "3.12.4",
|
|
58
|
+
"@radix-ui/react-password-toggle-field": "0.2.1",
|
|
59
|
+
"@tanstack/react-virtual": "3.14.20",
|
|
60
|
+
"lucide-react": "1.57.0",
|
|
61
|
+
"react-aria": "3.53.1",
|
|
62
|
+
"react-aria-components": "1.22.1",
|
|
63
|
+
"react-resizable-panels": "4.14.5",
|
|
64
|
+
"tailwind-merge": "3.7.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/* accordion */
|
|
2
|
+
@layer components {
|
|
3
|
+
:where(:not([data-style-disabled])) {
|
|
4
|
+
&:where([data-component='accordion-list']) {
|
|
5
|
+
@apply flex w-full flex-col overflow-hidden rounded-xl border border-neutral-200 bg-white;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
&:where([data-component='accordion-item']) {
|
|
9
|
+
@apply w-full border-b border-neutral-200 last:border-b-0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
&:where([data-component='accordion-openbutton']) {
|
|
13
|
+
@apply flex min-h-10 w-full cursor-pointer items-center justify-between gap-4 bg-white px-3 py-2 text-left text-sm font-medium text-neutral-950 outline-none hover:bg-neutral-50 focus-visible:relative focus-visible:z-1 focus-visible:outline-2 focus-visible:outline-blue-500 disabled:pointer-events-none disabled:bg-neutral-100 disabled:text-neutral-500 disabled:opacity-50;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
&:where([data-component='accordion-openertitle']) {
|
|
17
|
+
@apply min-w-0 flex-1;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&:where([data-component='accordion-openerindicator']) {
|
|
21
|
+
@apply h-4 w-fit shrink-0 text-inherit transition-transform data-panel-open:rotate-180;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
&:where([data-component='accordion-content']) {
|
|
25
|
+
@apply h-(--accordion-panel-height) overflow-hidden text-sm text-neutral-700 transition-[height] duration-200 data-starting-style:h-0 data-ending-style:h-0;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|