@dashforge/tw 1.1.0 → 1.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/CHANGELOG.md +21 -1
- package/dist/index.esm.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/package.json +8 -3
- package/.swcrc +0 -29
- package/A11Y.md +0 -130
- package/CONSUMER-VALIDATION.md +0 -130
- package/COVERAGE.md +0 -63
- package/PARITY.md +0 -481
- package/PERFORMANCE.md +0 -227
- package/REFINEMENT-NOTES.md +0 -50
- package/THEME-AUDIT.md +0 -174
- package/dashforge-tw-1.0.0.tgz +0 -0
- package/eslint.config.mjs +0 -62
- package/project.json +0 -15
- package/rollup.config.cjs +0 -16
- package/src/components/Accordion/Accordion.test.tsx +0 -95
- package/src/components/Accordion/Accordion.tsx +0 -97
- package/src/components/Accordion/accordion.types.ts +0 -66
- package/src/components/Accordion/accordion.variants.ts +0 -32
- package/src/components/Alert/Alert.test.tsx +0 -302
- package/src/components/Alert/Alert.tsx +0 -186
- package/src/components/Alert/alert.types.ts +0 -144
- package/src/components/Alert/alert.variants.ts +0 -71
- package/src/components/AppShell/AppShell.test.tsx +0 -223
- package/src/components/AppShell/AppShell.tsx +0 -239
- package/src/components/AppShell/appShell.types.ts +0 -49
- package/src/components/AppShell/appShell.variants.ts +0 -54
- package/src/components/AspectRatio/AspectRatio.test.tsx +0 -173
- package/src/components/AspectRatio/AspectRatio.tsx +0 -73
- package/src/components/AspectRatio/aspectRatio.types.ts +0 -54
- package/src/components/Autocomplete/Autocomplete.perf.test.tsx +0 -225
- package/src/components/Autocomplete/Autocomplete.test.tsx +0 -556
- package/src/components/Autocomplete/Autocomplete.tsx +0 -1069
- package/src/components/Autocomplete/autocomplete.types.ts +0 -198
- package/src/components/Autocomplete/autocomplete.variants.ts +0 -141
- package/src/components/Avatar/Avatar.test.tsx +0 -287
- package/src/components/Avatar/Avatar.tsx +0 -304
- package/src/components/Avatar/avatar.types.ts +0 -205
- package/src/components/Avatar/avatar.variants.ts +0 -194
- package/src/components/Badge/Badge.test.tsx +0 -385
- package/src/components/Badge/Badge.tsx +0 -174
- package/src/components/Badge/badge.types.ts +0 -154
- package/src/components/Badge/badge.variants.ts +0 -161
- package/src/components/Box/Box.test.tsx +0 -391
- package/src/components/Box/Box.tsx +0 -112
- package/src/components/Box/box.types.ts +0 -116
- package/src/components/Box/box.variants.ts +0 -230
- package/src/components/Breadcrumbs/Breadcrumbs.perf.test.tsx +0 -128
- package/src/components/Breadcrumbs/Breadcrumbs.test.tsx +0 -231
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +0 -203
- package/src/components/Breadcrumbs/breadcrumbs.types.ts +0 -87
- package/src/components/Breadcrumbs/breadcrumbs.variants.ts +0 -54
- package/src/components/Button/Button.test.tsx +0 -204
- package/src/components/Button/Button.tsx +0 -151
- package/src/components/Button/button.types.ts +0 -110
- package/src/components/Button/button.variants.ts +0 -159
- package/src/components/Calendar/Calendar.test.tsx +0 -75
- package/src/components/Calendar/Calendar.tsx +0 -264
- package/src/components/Calendar/calendar.types.ts +0 -65
- package/src/components/Calendar/calendar.variants.ts +0 -64
- package/src/components/Card/Card.test.tsx +0 -291
- package/src/components/Card/Card.tsx +0 -241
- package/src/components/Card/card.types.ts +0 -132
- package/src/components/Checkbox/Checkbox.test.tsx +0 -146
- package/src/components/Checkbox/Checkbox.tsx +0 -286
- package/src/components/Checkbox/checkbox.types.ts +0 -88
- package/src/components/Checkbox/checkbox.variants.ts +0 -68
- package/src/components/Chip/Chip.test.tsx +0 -316
- package/src/components/Chip/Chip.tsx +0 -224
- package/src/components/Chip/chip.types.ts +0 -128
- package/src/components/Chip/chip.variants.ts +0 -173
- package/src/components/ConfirmDialog/ConfirmDialog.perf.test.tsx +0 -172
- package/src/components/ConfirmDialog/ConfirmDialog.test.tsx +0 -311
- package/src/components/ConfirmDialog/ConfirmDialog.tsx +0 -252
- package/src/components/ConfirmDialog/confirmDialog.types.ts +0 -70
- package/src/components/ConfirmDialog/confirmDialog.variants.ts +0 -76
- package/src/components/Container/Container.test.tsx +0 -209
- package/src/components/Container/Container.tsx +0 -74
- package/src/components/Container/container.types.ts +0 -45
- package/src/components/Container/container.variants.ts +0 -81
- package/src/components/DataGrid/DataGrid.perf.test.tsx +0 -114
- package/src/components/DataGrid/DataGrid.test.tsx +0 -1153
- package/src/components/DataGrid/DataGrid.tsx +0 -1282
- package/src/components/DataGrid/dataGrid.types.ts +0 -238
- package/src/components/DataGrid/dataGrid.variants.ts +0 -152
- package/src/components/DataGrid/filters/ColumnFilters.test.tsx +0 -330
- package/src/components/DataGrid/filters/ColumnFilters.tsx +0 -456
- package/src/components/DataGrid/reorder/useColumnReorder.test.ts +0 -130
- package/src/components/DataGrid/reorder/useColumnReorder.ts +0 -144
- package/src/components/DataGrid/resize/useColumnResize.test.ts +0 -144
- package/src/components/DataGrid/resize/useColumnResize.ts +0 -135
- package/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx +0 -186
- package/src/components/DatePicker/DatePicker.test.tsx +0 -76
- package/src/components/DatePicker/DatePicker.tsx +0 -305
- package/src/components/DatePicker/datePicker.types.ts +0 -77
- package/src/components/DatePicker/datePicker.variants.ts +0 -50
- package/src/components/DateRangePicker/DateRangePicker.test.tsx +0 -92
- package/src/components/DateRangePicker/DateRangePicker.tsx +0 -510
- package/src/components/DateRangePicker/dateRangePicker.types.ts +0 -74
- package/src/components/DateRangePicker/dateRangePicker.variants.ts +0 -101
- package/src/components/DateTimePicker/DateTimePicker.test.tsx +0 -78
- package/src/components/DateTimePicker/DateTimePicker.tsx +0 -347
- package/src/components/DateTimePicker/dateTimePicker.types.ts +0 -79
- package/src/components/DateTimePicker/dateTimePicker.variants.ts +0 -55
- package/src/components/Dialog/Dialog.test.tsx +0 -96
- package/src/components/Dialog/Dialog.tsx +0 -102
- package/src/components/Dialog/dialog.types.ts +0 -62
- package/src/components/Dialog/dialog.variants.ts +0 -66
- package/src/components/Divider/Divider.test.tsx +0 -243
- package/src/components/Divider/Divider.tsx +0 -140
- package/src/components/Divider/divider.types.ts +0 -52
- package/src/components/Divider/divider.variants.ts +0 -116
- package/src/components/Grid/Grid.test.tsx +0 -321
- package/src/components/Grid/Grid.tsx +0 -148
- package/src/components/Grid/grid.types.ts +0 -112
- package/src/components/Grid/grid.variants.ts +0 -133
- package/src/components/IconButton/IconButton.test.tsx +0 -367
- package/src/components/IconButton/IconButton.tsx +0 -159
- package/src/components/IconButton/iconButton.types.ts +0 -106
- package/src/components/IconButton/iconButton.variants.ts +0 -31
- package/src/components/LeftNav/LeftNav.perf.test.tsx +0 -172
- package/src/components/LeftNav/LeftNav.test.tsx +0 -302
- package/src/components/LeftNav/LeftNav.tsx +0 -330
- package/src/components/LeftNav/leftNav.types.ts +0 -120
- package/src/components/LeftNav/leftNav.variants.ts +0 -107
- package/src/components/Menu/Menu.test.tsx +0 -408
- package/src/components/Menu/Menu.tsx +0 -335
- package/src/components/Menu/menu.types.ts +0 -221
- package/src/components/Menu/menu.variants.ts +0 -132
- package/src/components/NumberField/NumberField.test.tsx +0 -83
- package/src/components/NumberField/NumberField.tsx +0 -321
- package/src/components/NumberField/numberField.types.ts +0 -72
- package/src/components/NumberField/numberField.variants.ts +0 -94
- package/src/components/OTPField/OTPField.test.tsx +0 -121
- package/src/components/OTPField/OTPField.tsx +0 -250
- package/src/components/OTPField/otpField.types.ts +0 -58
- package/src/components/OTPField/otpField.variants.ts +0 -72
- package/src/components/Pagination/Pagination.test.tsx +0 -314
- package/src/components/Pagination/Pagination.tsx +0 -277
- package/src/components/Pagination/pagination.helpers.ts +0 -102
- package/src/components/Pagination/pagination.types.ts +0 -117
- package/src/components/Pagination/pagination.variants.ts +0 -117
- package/src/components/Popover/Popover.test.tsx +0 -72
- package/src/components/Popover/Popover.tsx +0 -55
- package/src/components/Popover/popover.types.ts +0 -42
- package/src/components/Popover/popover.variants.ts +0 -24
- package/src/components/RadioGroup/RadioGroup.test.tsx +0 -140
- package/src/components/RadioGroup/RadioGroup.tsx +0 -283
- package/src/components/RadioGroup/radioGroup.types.ts +0 -85
- package/src/components/RadioGroup/radioGroup.variants.ts +0 -87
- package/src/components/Skeleton/Skeleton.test.tsx +0 -127
- package/src/components/Skeleton/Skeleton.tsx +0 -49
- package/src/components/Skeleton/skeleton.types.ts +0 -59
- package/src/components/Skeleton/skeleton.variants.ts +0 -87
- package/src/components/Snackbar/Snackbar.perf.test.tsx +0 -144
- package/src/components/Snackbar/Snackbar.test.tsx +0 -327
- package/src/components/Snackbar/Snackbar.tsx +0 -316
- package/src/components/Snackbar/snackbar.types.ts +0 -127
- package/src/components/Snackbar/snackbar.variants.ts +0 -81
- package/src/components/Spinner/Spinner.test.tsx +0 -199
- package/src/components/Spinner/Spinner.tsx +0 -158
- package/src/components/Spinner/spinner.types.ts +0 -113
- package/src/components/Spinner/spinner.variants.ts +0 -83
- package/src/components/Stack/Stack.test.tsx +0 -309
- package/src/components/Stack/Stack.tsx +0 -115
- package/src/components/Stack/stack.types.ts +0 -68
- package/src/components/Stack/stack.variants.ts +0 -80
- package/src/components/Switch/Switch.test.tsx +0 -131
- package/src/components/Switch/Switch.tsx +0 -158
- package/src/components/Switch/switch.types.ts +0 -36
- package/src/components/Switch/switch.variants.ts +0 -70
- package/src/components/Table/Table.perf.test.tsx +0 -81
- package/src/components/Table/Table.test.tsx +0 -674
- package/src/components/Table/Table.tsx +0 -863
- package/src/components/Table/cells/RenderButton.tsx +0 -23
- package/src/components/Table/cells/RenderChip.tsx +0 -37
- package/src/components/Table/cells/RenderText.tsx +0 -37
- package/src/components/Table/cells/RenderTwoLine.tsx +0 -34
- package/src/components/Table/cells/RowActionsMenu.tsx +0 -147
- package/src/components/Table/cells/cells.test.tsx +0 -247
- package/src/components/Table/cells/index.ts +0 -14
- package/src/components/Table/table.types.ts +0 -438
- package/src/components/Table/table.variants.ts +0 -207
- package/src/components/Tabs/Tabs.test.tsx +0 -145
- package/src/components/Tabs/Tabs.tsx +0 -77
- package/src/components/Tabs/tabs.types.ts +0 -55
- package/src/components/Tabs/tabs.variants.ts +0 -69
- package/src/components/Tabs/useTabs.ts +0 -167
- package/src/components/TextField/TextField.test.tsx +0 -198
- package/src/components/TextField/TextField.tsx +0 -245
- package/src/components/TextField/textField.types.ts +0 -90
- package/src/components/TextField/textField.variants.ts +0 -105
- package/src/components/Textarea/Textarea.test.tsx +0 -75
- package/src/components/Textarea/Textarea.tsx +0 -190
- package/src/components/Textarea/textarea.types.ts +0 -60
- package/src/components/Textarea/textarea.variants.ts +0 -85
- package/src/components/TimePicker/TimePicker.test.tsx +0 -65
- package/src/components/TimePicker/TimePicker.tsx +0 -274
- package/src/components/TimePicker/timePicker.types.ts +0 -69
- package/src/components/TimePicker/timePicker.variants.ts +0 -53
- package/src/components/Tooltip/Tooltip.test.tsx +0 -73
- package/src/components/Tooltip/Tooltip.tsx +0 -70
- package/src/components/Tooltip/tooltip.types.ts +0 -44
- package/src/components/Tooltip/tooltip.variants.ts +0 -28
- package/src/components/TopBar/TopBar.test.tsx +0 -129
- package/src/components/TopBar/TopBar.tsx +0 -66
- package/src/components/TopBar/topBar.types.ts +0 -42
- package/src/components/TopBar/topBar.variants.ts +0 -38
- package/src/components/Typography/Typography.test.tsx +0 -247
- package/src/components/Typography/Typography.tsx +0 -112
- package/src/components/Typography/typography.types.ts +0 -65
- package/src/components/Typography/typography.variants.ts +0 -130
- package/src/components/VisuallyHidden/VisuallyHidden.test.tsx +0 -86
- package/src/components/VisuallyHidden/VisuallyHidden.tsx +0 -59
- package/src/components/VisuallyHidden/visuallyHidden.types.ts +0 -45
- package/src/components/_shared/data/getNestedValue.test.ts +0 -50
- package/src/components/_shared/data/getNestedValue.ts +0 -32
- package/src/components/_shared/data/useColumnAutoDetect.test.ts +0 -123
- package/src/components/_shared/data/useColumnAutoDetect.ts +0 -100
- package/src/components/_shared/data/useControllableState.ts +0 -48
- package/src/components/_shared/data/useDebouncedValue.ts +0 -24
- package/src/components/_shared/data/useTableFilter.test.ts +0 -166
- package/src/components/_shared/data/useTableFilter.ts +0 -118
- package/src/components/_shared/data/useTableSearch.test.ts +0 -115
- package/src/components/_shared/data/useTableSearch.ts +0 -66
- package/src/components/_shared/data/useTableSelection.test.ts +0 -100
- package/src/components/_shared/data/useTableSelection.ts +0 -100
- package/src/components/_shared/data/useTableSort.test.ts +0 -150
- package/src/components/_shared/data/useTableSort.ts +0 -141
- package/src/components/_shared/data/useVirtualizer.test.ts +0 -158
- package/src/components/_shared/data/useVirtualizer.ts +0 -199
- package/src/components/_shared/resolveValidationState.ts +0 -60
- package/src/components/_shared/severity/index.ts +0 -33
- package/src/components/_shared/severity/severity.types.ts +0 -50
- package/src/components/_shared/severity/severityIcons.tsx +0 -104
- package/src/components/_shared/severity/severityVariants.test.ts +0 -136
- package/src/components/_shared/severity/severityVariants.ts +0 -115
- package/src/components/_shared/themeIdentity.test.ts +0 -113
- package/src/hooks/useAccessState.spec.tsx +0 -161
- package/src/hooks/useAccessState.ts +0 -81
- package/src/index.ts +0 -550
- package/src/utils/cn.spec.ts +0 -16
- package/src/utils/cn.ts +0 -29
- package/test-output/vitest/coverage/base.css +0 -224
- package/test-output/vitest/coverage/block-navigation.js +0 -87
- package/test-output/vitest/coverage/clover.xml +0 -2418
- package/test-output/vitest/coverage/coverage-final.json +0 -89
- package/test-output/vitest/coverage/favicon.png +0 -0
- package/test-output/vitest/coverage/index.html +0 -746
- package/test-output/vitest/coverage/prettify.css +0 -1
- package/test-output/vitest/coverage/prettify.js +0 -2
- package/test-output/vitest/coverage/sort-arrow-sprite.png +0 -0
- package/test-output/vitest/coverage/sorter.js +0 -210
- package/test-output/vitest/coverage/src/components/Accordion/Accordion.tsx.html +0 -376
- package/test-output/vitest/coverage/src/components/Accordion/accordion.variants.ts.html +0 -181
- package/test-output/vitest/coverage/src/components/Accordion/index.html +0 -131
- package/test-output/vitest/coverage/src/components/AppShell/AppShell.tsx.html +0 -802
- package/test-output/vitest/coverage/src/components/AppShell/appShell.variants.ts.html +0 -247
- package/test-output/vitest/coverage/src/components/AppShell/index.html +0 -131
- package/test-output/vitest/coverage/src/components/AspectRatio/AspectRatio.tsx.html +0 -304
- package/test-output/vitest/coverage/src/components/AspectRatio/index.html +0 -116
- package/test-output/vitest/coverage/src/components/Autocomplete/Autocomplete.tsx.html +0 -3292
- package/test-output/vitest/coverage/src/components/Autocomplete/autocomplete.variants.ts.html +0 -508
- package/test-output/vitest/coverage/src/components/Autocomplete/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Box/Box.tsx.html +0 -334
- package/test-output/vitest/coverage/src/components/Box/box.variants.ts.html +0 -775
- package/test-output/vitest/coverage/src/components/Box/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Breadcrumbs/Breadcrumbs.tsx.html +0 -694
- package/test-output/vitest/coverage/src/components/Breadcrumbs/breadcrumbs.variants.ts.html +0 -247
- package/test-output/vitest/coverage/src/components/Breadcrumbs/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Button/Button.tsx.html +0 -535
- package/test-output/vitest/coverage/src/components/Button/button.variants.ts.html +0 -562
- package/test-output/vitest/coverage/src/components/Button/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Checkbox/Checkbox.tsx.html +0 -943
- package/test-output/vitest/coverage/src/components/Checkbox/checkbox.variants.ts.html +0 -289
- package/test-output/vitest/coverage/src/components/Checkbox/index.html +0 -131
- package/test-output/vitest/coverage/src/components/ConfirmDialog/ConfirmDialog.tsx.html +0 -841
- package/test-output/vitest/coverage/src/components/ConfirmDialog/confirmDialog.variants.ts.html +0 -313
- package/test-output/vitest/coverage/src/components/ConfirmDialog/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Container/Container.tsx.html +0 -307
- package/test-output/vitest/coverage/src/components/Container/container.variants.ts.html +0 -328
- package/test-output/vitest/coverage/src/components/Container/index.html +0 -131
- package/test-output/vitest/coverage/src/components/DataGrid/DataGrid.tsx.html +0 -3937
- package/test-output/vitest/coverage/src/components/DataGrid/dataGrid.variants.ts.html +0 -541
- package/test-output/vitest/coverage/src/components/DataGrid/filters/ColumnFilters.tsx.html +0 -1453
- package/test-output/vitest/coverage/src/components/DataGrid/filters/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/index.html +0 -131
- package/test-output/vitest/coverage/src/components/DataGrid/reorder/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/reorder/useColumnReorder.ts.html +0 -517
- package/test-output/vitest/coverage/src/components/DataGrid/resize/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DataGrid/resize/useColumnResize.ts.html +0 -490
- package/test-output/vitest/coverage/src/components/DataGrid/visibility/ColumnVisibilityMenu.tsx.html +0 -592
- package/test-output/vitest/coverage/src/components/DataGrid/visibility/index.html +0 -116
- package/test-output/vitest/coverage/src/components/DateTimePicker/DateTimePicker.tsx.html +0 -1042
- package/test-output/vitest/coverage/src/components/DateTimePicker/dateTimePicker.variants.ts.html +0 -337
- package/test-output/vitest/coverage/src/components/DateTimePicker/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Dialog/Dialog.tsx.html +0 -391
- package/test-output/vitest/coverage/src/components/Dialog/dialog.variants.ts.html +0 -283
- package/test-output/vitest/coverage/src/components/Dialog/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Divider/Divider.tsx.html +0 -505
- package/test-output/vitest/coverage/src/components/Divider/divider.variants.ts.html +0 -433
- package/test-output/vitest/coverage/src/components/Divider/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Grid/Grid.tsx.html +0 -529
- package/test-output/vitest/coverage/src/components/Grid/grid.variants.ts.html +0 -484
- package/test-output/vitest/coverage/src/components/Grid/index.html +0 -131
- package/test-output/vitest/coverage/src/components/LeftNav/LeftNav.tsx.html +0 -1075
- package/test-output/vitest/coverage/src/components/LeftNav/index.html +0 -131
- package/test-output/vitest/coverage/src/components/LeftNav/leftNav.variants.ts.html +0 -406
- package/test-output/vitest/coverage/src/components/NumberField/NumberField.tsx.html +0 -1048
- package/test-output/vitest/coverage/src/components/NumberField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/NumberField/numberField.variants.ts.html +0 -367
- package/test-output/vitest/coverage/src/components/OTPField/OTPField.tsx.html +0 -835
- package/test-output/vitest/coverage/src/components/OTPField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/OTPField/otpField.variants.ts.html +0 -301
- package/test-output/vitest/coverage/src/components/Pagination/Pagination.tsx.html +0 -916
- package/test-output/vitest/coverage/src/components/Pagination/index.html +0 -146
- package/test-output/vitest/coverage/src/components/Pagination/pagination.helpers.ts.html +0 -391
- package/test-output/vitest/coverage/src/components/Pagination/pagination.variants.ts.html +0 -436
- package/test-output/vitest/coverage/src/components/Popover/Popover.tsx.html +0 -250
- package/test-output/vitest/coverage/src/components/Popover/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Popover/popover.variants.ts.html +0 -157
- package/test-output/vitest/coverage/src/components/RadioGroup/RadioGroup.tsx.html +0 -934
- package/test-output/vitest/coverage/src/components/RadioGroup/index.html +0 -131
- package/test-output/vitest/coverage/src/components/RadioGroup/radioGroup.variants.ts.html +0 -346
- package/test-output/vitest/coverage/src/components/Skeleton/Skeleton.tsx.html +0 -232
- package/test-output/vitest/coverage/src/components/Skeleton/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Skeleton/skeleton.variants.ts.html +0 -346
- package/test-output/vitest/coverage/src/components/Snackbar/Snackbar.tsx.html +0 -979
- package/test-output/vitest/coverage/src/components/Snackbar/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Snackbar/snackbar.variants.ts.html +0 -307
- package/test-output/vitest/coverage/src/components/Stack/Stack.tsx.html +0 -430
- package/test-output/vitest/coverage/src/components/Stack/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Stack/stack.variants.ts.html +0 -325
- package/test-output/vitest/coverage/src/components/Switch/Switch.tsx.html +0 -559
- package/test-output/vitest/coverage/src/components/Switch/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Switch/switch.variants.ts.html +0 -295
- package/test-output/vitest/coverage/src/components/Table/Table.tsx.html +0 -2677
- package/test-output/vitest/coverage/src/components/Table/cells/RenderButton.tsx.html +0 -154
- package/test-output/vitest/coverage/src/components/Table/cells/RenderChip.tsx.html +0 -385
- package/test-output/vitest/coverage/src/components/Table/cells/RenderText.tsx.html +0 -196
- package/test-output/vitest/coverage/src/components/Table/cells/RenderTwoLine.tsx.html +0 -187
- package/test-output/vitest/coverage/src/components/Table/cells/RowActionsMenu.tsx.html +0 -526
- package/test-output/vitest/coverage/src/components/Table/cells/index.html +0 -191
- package/test-output/vitest/coverage/src/components/Table/cells/index.ts.html +0 -127
- package/test-output/vitest/coverage/src/components/Table/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Table/table.variants.ts.html +0 -706
- package/test-output/vitest/coverage/src/components/Tabs/Tabs.tsx.html +0 -283
- package/test-output/vitest/coverage/src/components/Tabs/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Tabs/tabs.variants.ts.html +0 -292
- package/test-output/vitest/coverage/src/components/TextField/TextField.tsx.html +0 -820
- package/test-output/vitest/coverage/src/components/TextField/index.html +0 -131
- package/test-output/vitest/coverage/src/components/TextField/textField.variants.ts.html +0 -400
- package/test-output/vitest/coverage/src/components/Textarea/Textarea.tsx.html +0 -655
- package/test-output/vitest/coverage/src/components/Textarea/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Textarea/textarea.variants.ts.html +0 -340
- package/test-output/vitest/coverage/src/components/Tooltip/Tooltip.tsx.html +0 -295
- package/test-output/vitest/coverage/src/components/Tooltip/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Tooltip/tooltip.variants.ts.html +0 -169
- package/test-output/vitest/coverage/src/components/TopBar/TopBar.tsx.html +0 -283
- package/test-output/vitest/coverage/src/components/TopBar/index.html +0 -131
- package/test-output/vitest/coverage/src/components/TopBar/topBar.variants.ts.html +0 -199
- package/test-output/vitest/coverage/src/components/Typography/Typography.tsx.html +0 -421
- package/test-output/vitest/coverage/src/components/Typography/index.html +0 -131
- package/test-output/vitest/coverage/src/components/Typography/typography.variants.ts.html +0 -436
- package/test-output/vitest/coverage/src/components/VisuallyHidden/VisuallyHidden.tsx.html +0 -262
- package/test-output/vitest/coverage/src/components/VisuallyHidden/index.html +0 -116
- package/test-output/vitest/coverage/src/components/_shared/data/getNestedValue.ts.html +0 -181
- package/test-output/vitest/coverage/src/components/_shared/data/index.html +0 -236
- package/test-output/vitest/coverage/src/components/_shared/data/useColumnAutoDetect.ts.html +0 -385
- package/test-output/vitest/coverage/src/components/_shared/data/useControllableState.ts.html +0 -229
- package/test-output/vitest/coverage/src/components/_shared/data/useDebouncedValue.ts.html +0 -157
- package/test-output/vitest/coverage/src/components/_shared/data/useTableFilter.ts.html +0 -439
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSearch.ts.html +0 -283
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSelection.ts.html +0 -385
- package/test-output/vitest/coverage/src/components/_shared/data/useTableSort.ts.html +0 -508
- package/test-output/vitest/coverage/src/components/_shared/data/useVirtualizer.ts.html +0 -682
- package/test-output/vitest/coverage/src/components/_shared/index.html +0 -116
- package/test-output/vitest/coverage/src/components/_shared/resolveValidationState.ts.html +0 -265
- package/test-output/vitest/coverage/src/hooks/index.html +0 -116
- package/test-output/vitest/coverage/src/hooks/useAccessState.ts.html +0 -328
- package/test-output/vitest/coverage/src/index.html +0 -116
- package/test-output/vitest/coverage/src/index.ts.html +0 -1153
- package/test-output/vitest/coverage/src/utils/cn.ts.html +0 -172
- package/test-output/vitest/coverage/src/utils/index.html +0 -116
- package/tsconfig.json +0 -13
- package/tsconfig.lib.json +0 -58
- package/tsconfig.spec.json +0 -34
- package/vite.config.ts +0 -33
- package/vitest.config.mts +0 -29
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect } from 'vitest';
|
|
3
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
4
|
-
import { DashFormProvider } from '@dashforge/forms';
|
|
5
|
-
import { NumberField } from './NumberField.js';
|
|
6
|
-
|
|
7
|
-
describe('<NumberField>', () => {
|
|
8
|
-
describe('standalone (no DashForm)', () => {
|
|
9
|
-
it('renders a numeric input with the label', () => {
|
|
10
|
-
render(<NumberField name="age" label="Age" />);
|
|
11
|
-
const el = screen.getByLabelText('Age') as HTMLInputElement;
|
|
12
|
-
expect(el).toBeInstanceOf(HTMLInputElement);
|
|
13
|
-
expect(el.type).toBe('number');
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('respects `min` / `max` / `step` attributes', () => {
|
|
17
|
-
render(
|
|
18
|
-
<NumberField name="qty" label="Qty" min={1} max={10} step={2} />
|
|
19
|
-
);
|
|
20
|
-
const el = screen.getByLabelText('Qty');
|
|
21
|
-
expect(el.getAttribute('min')).toBe('1');
|
|
22
|
-
expect(el.getAttribute('max')).toBe('10');
|
|
23
|
-
expect(el.getAttribute('step')).toBe('2');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('displays a controlled numeric value via `value`', () => {
|
|
27
|
-
const { rerender } = render(
|
|
28
|
-
<NumberField name="qty" label="Qty" value={5} onChange={() => undefined} />
|
|
29
|
-
);
|
|
30
|
-
expect((screen.getByLabelText('Qty') as HTMLInputElement).value).toBe('5');
|
|
31
|
-
rerender(
|
|
32
|
-
<NumberField name="qty" label="Qty" value={null} onChange={() => undefined} />
|
|
33
|
-
);
|
|
34
|
-
expect((screen.getByLabelText('Qty') as HTMLInputElement).value).toBe('');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('renders a `*` for required fields', () => {
|
|
38
|
-
render(<NumberField name="x" label="X" required />);
|
|
39
|
-
expect(screen.getByText('*')).toBeTruthy();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('does NOT render a stepper unless `showStepper` is set', () => {
|
|
43
|
-
render(<NumberField name="x" label="X" />);
|
|
44
|
-
expect(screen.queryByLabelText('Increment')).toBeNull();
|
|
45
|
-
expect(screen.queryByLabelText('Decrement')).toBeNull();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('renders +/- stepper buttons when `showStepper` is true', () => {
|
|
49
|
-
render(<NumberField name="x" label="X" showStepper />);
|
|
50
|
-
expect(screen.getByLabelText('Increment')).toBeTruthy();
|
|
51
|
-
expect(screen.getByLabelText('Decrement')).toBeTruthy();
|
|
52
|
-
});
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
describe('inside DashFormProvider', () => {
|
|
56
|
-
it('mounts cleanly inside a bridge-providing tree', () => {
|
|
57
|
-
render(
|
|
58
|
-
<DashFormProvider defaultValues={{ age: 30 }}>
|
|
59
|
-
<NumberField name="age" label="Age" />
|
|
60
|
-
</DashFormProvider>
|
|
61
|
-
);
|
|
62
|
-
// Bridge mode mounts cleanly with no console errors. Initial-value
|
|
63
|
-
// reflection in the input is timing-sensitive (RHF's defaultValues
|
|
64
|
-
// commit happens after the first render); the actual end-to-end
|
|
65
|
-
// round-trip is verified in the dash smoke page where async
|
|
66
|
-
// RHF state has time to settle.
|
|
67
|
-
expect(screen.getByLabelText('Age')).toBeInstanceOf(HTMLInputElement);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('stepper buttons render and are clickable inside the bridge tree', () => {
|
|
71
|
-
render(
|
|
72
|
-
<DashFormProvider defaultValues={{ qty: 5 }}>
|
|
73
|
-
<NumberField name="qty" label="Qty" showStepper step={3} />
|
|
74
|
-
</DashFormProvider>
|
|
75
|
-
);
|
|
76
|
-
// The stepper itself renders correctly; the bridge round-trip on
|
|
77
|
-
// click is covered by the smoke test in dash.
|
|
78
|
-
const inc = screen.getByLabelText('Increment') as HTMLButtonElement;
|
|
79
|
-
expect(inc.disabled).toBe(false);
|
|
80
|
-
fireEvent.click(inc); // should not throw
|
|
81
|
-
});
|
|
82
|
-
});
|
|
83
|
-
});
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { useCallback, useContext, useEffect, useId, useRef, useState } from 'react';
|
|
2
|
-
import { DashFormContext, useEngineVisibility } from '@dashforge/ui-core';
|
|
3
|
-
import type { DashFormBridge, FieldRegistration } from '@dashforge/ui-core';
|
|
4
|
-
import { useDashFieldMeta } from '@dashforge/forms';
|
|
5
|
-
import { cn } from '../../utils/cn.js';
|
|
6
|
-
import { useAccessState } from '../../hooks/useAccessState.js';
|
|
7
|
-
import { resolveValidationState } from '../_shared/resolveValidationState.js';
|
|
8
|
-
import { numberFieldVariants } from './numberField.variants.js';
|
|
9
|
-
import type { NumberFieldProps } from './numberField.types.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Format a numeric bridge value for the input UI.
|
|
13
|
-
*
|
|
14
|
-
* Strict rules (mirror the MUI side):
|
|
15
|
-
* - `number` → `String(n)` (no thousands separators, no locale)
|
|
16
|
-
* - `null` / `undefined` → empty string (so the input shows blank)
|
|
17
|
-
* - non-finite numbers (NaN, ±Infinity) are coerced to `""` and never written
|
|
18
|
-
* back to the bridge
|
|
19
|
-
*
|
|
20
|
-
* @internal
|
|
21
|
-
*/
|
|
22
|
-
function formatForDisplay(v: unknown): string {
|
|
23
|
-
if (v == null) return '';
|
|
24
|
-
if (typeof v === 'number') {
|
|
25
|
-
return Number.isFinite(v) ? String(v) : '';
|
|
26
|
-
}
|
|
27
|
-
if (typeof v === 'string') return v;
|
|
28
|
-
return '';
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Coerce a string from the input back to the bridge storage type.
|
|
33
|
-
*
|
|
34
|
-
* - `""` → `null` (cleared)
|
|
35
|
-
* - parseable finite number → that number
|
|
36
|
-
* - anything else → `undefined` (caller should NOT write to the bridge)
|
|
37
|
-
*
|
|
38
|
-
* @internal
|
|
39
|
-
*/
|
|
40
|
-
function parseFromInput(str: string): number | null | undefined {
|
|
41
|
-
if (str === '') return null;
|
|
42
|
-
// Allow the user to type the leading sign or a trailing decimal point without
|
|
43
|
-
// committing a partial number to the bridge — those parse to NaN here.
|
|
44
|
-
const n = Number(str);
|
|
45
|
-
return Number.isFinite(n) ? n : undefined;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Dashforge TW NumberField — bridge-integrated numeric input.
|
|
50
|
-
*
|
|
51
|
-
* Mirrors `@dashforge/ui/NumberField` semantics: storage type is
|
|
52
|
-
* `number | null`, NaN is never written, `Number.isFinite` is the guard.
|
|
53
|
-
* Renders a native `<input type="number">` with an OPTIONAL +/− stepper
|
|
54
|
-
* (opt-in via `showStepper`). The native browser spinner is suppressed
|
|
55
|
-
* in CSS so visual consistency is preserved cross-browser.
|
|
56
|
-
*
|
|
57
|
-
* **A11y**:
|
|
58
|
-
* - `aria-invalid` from resolved validation state
|
|
59
|
-
* - `aria-describedby` links the input to the helper/error text
|
|
60
|
-
* - `min` / `max` / `step` forwarded to the native input (which exposes
|
|
61
|
-
* `aria-valuemin` / `aria-valuemax` automatically)
|
|
62
|
-
*/
|
|
63
|
-
export function NumberField(props: NumberFieldProps) {
|
|
64
|
-
const {
|
|
65
|
-
name,
|
|
66
|
-
rules,
|
|
67
|
-
visibleWhen,
|
|
68
|
-
layout = 'stacked',
|
|
69
|
-
size,
|
|
70
|
-
label,
|
|
71
|
-
helperText,
|
|
72
|
-
required,
|
|
73
|
-
error,
|
|
74
|
-
disabled,
|
|
75
|
-
fullWidth,
|
|
76
|
-
access,
|
|
77
|
-
sx,
|
|
78
|
-
slotProps,
|
|
79
|
-
placeholder,
|
|
80
|
-
min,
|
|
81
|
-
max,
|
|
82
|
-
step = 1,
|
|
83
|
-
showStepper = false,
|
|
84
|
-
onChange: userOnChange,
|
|
85
|
-
onBlur: userOnBlur,
|
|
86
|
-
value: userValue,
|
|
87
|
-
defaultValue,
|
|
88
|
-
...rest
|
|
89
|
-
} = props;
|
|
90
|
-
|
|
91
|
-
// ───── Hooks (unconditional) ─────
|
|
92
|
-
const bridge = useContext(DashFormContext) as DashFormBridge | null;
|
|
93
|
-
const isVisible = useEngineVisibility(bridge?.engine, visibleWhen);
|
|
94
|
-
// Per-field subscription side-effect. Value read happens via
|
|
95
|
-
// `bridge.getValue(name)` (mirrors MUI side — avoids first-render
|
|
96
|
-
// undefined window from `useDashFieldMeta.value`).
|
|
97
|
-
useDashFieldMeta(name);
|
|
98
|
-
const accessState = useAccessState(access);
|
|
99
|
-
|
|
100
|
-
const inputId = useId();
|
|
101
|
-
|
|
102
|
-
/*
|
|
103
|
-
* Local state for the STANDALONE UNCONTROLLED case (no bridge, no
|
|
104
|
-
* `value` prop, only `defaultValue`). Mirrors OTPField. Without this,
|
|
105
|
-
* `resolvedDisplayValue` would be a snapshot computed ONCE from
|
|
106
|
-
* `defaultValue` and the controlled `<input value={...}>` would
|
|
107
|
-
* snap user input back on every keystroke / stepper click — same
|
|
108
|
-
* trap that hit Checkbox + RadioGroup in this package.
|
|
109
|
-
*
|
|
110
|
-
* In form mode the bridge owns state. In standalone CONTROLLED mode
|
|
111
|
-
* (consumer passes `value`) the consumer owns state. Only this
|
|
112
|
-
* branch needs the local hook.
|
|
113
|
-
*/
|
|
114
|
-
const [uncontrolledValue, setUncontrolledValue] = useState<string>(() =>
|
|
115
|
-
formatForDisplay(defaultValue)
|
|
116
|
-
);
|
|
117
|
-
const helperId = `${inputId}-help`;
|
|
118
|
-
|
|
119
|
-
// StrictMode-safe unregister-on-unmount
|
|
120
|
-
const unregisterRef = useRef({ bridge, name });
|
|
121
|
-
unregisterRef.current = { bridge, name };
|
|
122
|
-
const isMountedRef = useRef(false);
|
|
123
|
-
|
|
124
|
-
useEffect(() => {
|
|
125
|
-
isMountedRef.current = true;
|
|
126
|
-
return () => {
|
|
127
|
-
isMountedRef.current = false;
|
|
128
|
-
const { bridge: cap, name: capName } = unregisterRef.current;
|
|
129
|
-
queueMicrotask(() => {
|
|
130
|
-
if (!isMountedRef.current) cap?.unregister?.(capName);
|
|
131
|
-
});
|
|
132
|
-
};
|
|
133
|
-
}, []);
|
|
134
|
-
|
|
135
|
-
// ───── Derived (every hook MUST be called above the early returns) ─────
|
|
136
|
-
const effectiveDisabled = Boolean(disabled) || accessState.disabled;
|
|
137
|
-
const effectiveReadOnly = accessState.readonly;
|
|
138
|
-
|
|
139
|
-
const isFormMode = Boolean(bridge?.register);
|
|
140
|
-
|
|
141
|
-
let resolvedError = error;
|
|
142
|
-
let resolvedHelperText: typeof helperText = helperText;
|
|
143
|
-
let registration: FieldRegistration | null = null;
|
|
144
|
-
let resolvedDisplayValue: string;
|
|
145
|
-
|
|
146
|
-
if (isFormMode && bridge) {
|
|
147
|
-
registration = bridge.register(name, rules);
|
|
148
|
-
const validation = resolveValidationState(name, bridge, error, helperText);
|
|
149
|
-
resolvedError = validation.error;
|
|
150
|
-
resolvedHelperText = validation.helperText;
|
|
151
|
-
resolvedDisplayValue =
|
|
152
|
-
userValue !== undefined
|
|
153
|
-
? formatForDisplay(userValue)
|
|
154
|
-
: formatForDisplay(bridge.getValue(name));
|
|
155
|
-
} else {
|
|
156
|
-
resolvedDisplayValue =
|
|
157
|
-
userValue !== undefined
|
|
158
|
-
? formatForDisplay(userValue)
|
|
159
|
-
: uncontrolledValue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const writeToBridge = (parsed: number | null | undefined) => {
|
|
163
|
-
if (!isFormMode || !bridge) return;
|
|
164
|
-
if (parsed === undefined) return; // partial/invalid — don't pollute the bridge
|
|
165
|
-
bridge.setValue?.(name, parsed);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
169
|
-
const parsed = parseFromInput(e.target.value);
|
|
170
|
-
writeToBridge(parsed);
|
|
171
|
-
if (isFormMode && registration?.onChange) {
|
|
172
|
-
// Forward to RHF — pass through the original event so RHF's
|
|
173
|
-
// internal logic still sees the raw string change.
|
|
174
|
-
void registration.onChange(e);
|
|
175
|
-
}
|
|
176
|
-
// Standalone uncontrolled mode: mirror the raw input string so the
|
|
177
|
-
// controlled `<input value={...}>` reflects what the user typed.
|
|
178
|
-
// Partial states (e.g. "-", "1.") are kept verbatim — `parseFromInput`
|
|
179
|
-
// returns `undefined` for them so `writeToBridge` is a no-op above.
|
|
180
|
-
if (!isFormMode && userValue === undefined) {
|
|
181
|
-
setUncontrolledValue(e.target.value);
|
|
182
|
-
}
|
|
183
|
-
userOnChange?.(e);
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
187
|
-
if (isFormMode && registration?.onBlur) {
|
|
188
|
-
registration.onBlur(e);
|
|
189
|
-
}
|
|
190
|
-
userOnBlur?.(e);
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
const stepBy = (delta: number) => {
|
|
194
|
-
const current = isFormMode && bridge
|
|
195
|
-
? (bridge.getValue(name) as number | null | undefined)
|
|
196
|
-
: parseFromInput(resolvedDisplayValue);
|
|
197
|
-
const base = typeof current === 'number' && Number.isFinite(current) ? current : 0;
|
|
198
|
-
let next = base + delta;
|
|
199
|
-
if (typeof min === 'number') next = Math.max(min, next);
|
|
200
|
-
if (typeof max === 'number') next = Math.min(max, next);
|
|
201
|
-
writeToBridge(next);
|
|
202
|
-
// Standalone uncontrolled: also persist the new value to local
|
|
203
|
-
// state so the visible display tracks the stepper click.
|
|
204
|
-
if (!isFormMode && userValue === undefined) {
|
|
205
|
-
setUncontrolledValue(formatForDisplay(next));
|
|
206
|
-
}
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
const canIncrement =
|
|
210
|
-
!effectiveDisabled && (typeof max !== 'number' || (parseFromInput(resolvedDisplayValue) ?? -Infinity) < max);
|
|
211
|
-
const canDecrement =
|
|
212
|
-
!effectiveDisabled && (typeof min !== 'number' || (parseFromInput(resolvedDisplayValue) ?? Infinity) > min);
|
|
213
|
-
|
|
214
|
-
const registrationRefFn = registration?.ref;
|
|
215
|
-
const inputRef = useCallback(
|
|
216
|
-
(instance: HTMLInputElement | null) => {
|
|
217
|
-
if (typeof registrationRefFn === 'function') {
|
|
218
|
-
registrationRefFn(instance);
|
|
219
|
-
}
|
|
220
|
-
},
|
|
221
|
-
[registrationRefFn]
|
|
222
|
-
);
|
|
223
|
-
|
|
224
|
-
// ───── Render-time guards (after all hooks) ─────
|
|
225
|
-
if (!isVisible) return null;
|
|
226
|
-
if (!accessState.visible) return null;
|
|
227
|
-
|
|
228
|
-
const v = numberFieldVariants({
|
|
229
|
-
size,
|
|
230
|
-
layout,
|
|
231
|
-
error: resolvedError,
|
|
232
|
-
fullWidth,
|
|
233
|
-
disabled: effectiveDisabled,
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
return (
|
|
237
|
-
<div className={cn(v.root(), sx, slotProps?.root?.className)}>
|
|
238
|
-
{label && (
|
|
239
|
-
<label
|
|
240
|
-
htmlFor={inputId}
|
|
241
|
-
className={cn(v.label(), slotProps?.label?.className)}
|
|
242
|
-
>
|
|
243
|
-
{label}
|
|
244
|
-
{required && (
|
|
245
|
-
<span
|
|
246
|
-
aria-hidden="true"
|
|
247
|
-
className={cn(v.requiredMark(), slotProps?.requiredMark?.className)}
|
|
248
|
-
>
|
|
249
|
-
*
|
|
250
|
-
</span>
|
|
251
|
-
)}
|
|
252
|
-
</label>
|
|
253
|
-
)}
|
|
254
|
-
|
|
255
|
-
<div className={cn(v.inputWrapper(), slotProps?.inputWrapper?.className)}>
|
|
256
|
-
<input
|
|
257
|
-
{...rest}
|
|
258
|
-
id={inputId}
|
|
259
|
-
name={name}
|
|
260
|
-
type="number"
|
|
261
|
-
placeholder={placeholder}
|
|
262
|
-
value={resolvedDisplayValue}
|
|
263
|
-
min={min}
|
|
264
|
-
max={max}
|
|
265
|
-
step={step}
|
|
266
|
-
disabled={effectiveDisabled}
|
|
267
|
-
readOnly={effectiveReadOnly}
|
|
268
|
-
required={required}
|
|
269
|
-
aria-invalid={resolvedError || undefined}
|
|
270
|
-
aria-describedby={resolvedHelperText ? helperId : undefined}
|
|
271
|
-
onChange={handleChange}
|
|
272
|
-
onBlur={handleBlur}
|
|
273
|
-
ref={inputRef}
|
|
274
|
-
className={cn(v.input(), slotProps?.input?.className)}
|
|
275
|
-
/>
|
|
276
|
-
|
|
277
|
-
{showStepper && (
|
|
278
|
-
<div
|
|
279
|
-
className={cn(v.stepper(), slotProps?.stepper?.className)}
|
|
280
|
-
aria-hidden="true"
|
|
281
|
-
>
|
|
282
|
-
<button
|
|
283
|
-
type="button"
|
|
284
|
-
tabIndex={-1}
|
|
285
|
-
onClick={() => stepBy(step)}
|
|
286
|
-
disabled={!canIncrement}
|
|
287
|
-
className={cn(v.stepperButton(), slotProps?.stepperButton?.className)}
|
|
288
|
-
aria-label="Increment"
|
|
289
|
-
>
|
|
290
|
-
▲
|
|
291
|
-
</button>
|
|
292
|
-
<button
|
|
293
|
-
type="button"
|
|
294
|
-
tabIndex={-1}
|
|
295
|
-
onClick={() => stepBy(-step)}
|
|
296
|
-
disabled={!canDecrement}
|
|
297
|
-
className={cn(v.stepperButton(), slotProps?.stepperButton?.className)}
|
|
298
|
-
aria-label="Decrement"
|
|
299
|
-
>
|
|
300
|
-
▼
|
|
301
|
-
</button>
|
|
302
|
-
</div>
|
|
303
|
-
)}
|
|
304
|
-
</div>
|
|
305
|
-
|
|
306
|
-
{resolvedHelperText && (
|
|
307
|
-
<p
|
|
308
|
-
id={helperId}
|
|
309
|
-
className={cn(
|
|
310
|
-
resolvedError ? v.errorText() : v.helperText(),
|
|
311
|
-
resolvedError
|
|
312
|
-
? slotProps?.errorText?.className
|
|
313
|
-
: slotProps?.helperText?.className
|
|
314
|
-
)}
|
|
315
|
-
>
|
|
316
|
-
{resolvedHelperText}
|
|
317
|
-
</p>
|
|
318
|
-
)}
|
|
319
|
-
</div>
|
|
320
|
-
);
|
|
321
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ChangeEventHandler,
|
|
3
|
-
FocusEventHandler,
|
|
4
|
-
InputHTMLAttributes,
|
|
5
|
-
ReactNode,
|
|
6
|
-
} from 'react';
|
|
7
|
-
import type { Engine } from '@dashforge/ui-core';
|
|
8
|
-
import type { AccessRequirement } from '@dashforge/rbac';
|
|
9
|
-
import type { NumberFieldVariants } from './numberField.variants.js';
|
|
10
|
-
|
|
11
|
-
export interface NumberFieldSlotProps {
|
|
12
|
-
root?: { className?: string };
|
|
13
|
-
label?: { className?: string };
|
|
14
|
-
requiredMark?: { className?: string };
|
|
15
|
-
inputWrapper?: { className?: string };
|
|
16
|
-
input?: { className?: string };
|
|
17
|
-
stepper?: { className?: string };
|
|
18
|
-
stepperButton?: { className?: string };
|
|
19
|
-
helperText?: { className?: string };
|
|
20
|
-
errorText?: { className?: string };
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Props for `<NumberField>`.
|
|
25
|
-
*
|
|
26
|
-
* Value contract (mirrors `@dashforge/ui/NumberField`):
|
|
27
|
-
* - Bridge storage type: `number | null` (NEVER `NaN`).
|
|
28
|
-
* - UI display: `number → String(number)`, `null/undefined → ""`.
|
|
29
|
-
* - On user input:
|
|
30
|
-
* - empty string → `setValue(name, null)`
|
|
31
|
-
* - parseable number → `setValue(name, parsed)`
|
|
32
|
-
* - non-numeric input → no bridge write (UI string is kept as-is for UX,
|
|
33
|
-
* but the bridge stays at the last valid value)
|
|
34
|
-
* - Parsing uses `Number(...)` + `Number.isFinite(...)`. No locale parsing.
|
|
35
|
-
*/
|
|
36
|
-
export interface NumberFieldProps
|
|
37
|
-
extends Omit<
|
|
38
|
-
InputHTMLAttributes<HTMLInputElement>,
|
|
39
|
-
'name' | 'type' | 'size' | 'onChange' | 'onBlur' | 'value' | 'defaultValue'
|
|
40
|
-
>,
|
|
41
|
-
NumberFieldVariants {
|
|
42
|
-
name: string;
|
|
43
|
-
rules?: unknown;
|
|
44
|
-
label?: ReactNode;
|
|
45
|
-
helperText?: ReactNode;
|
|
46
|
-
required?: boolean;
|
|
47
|
-
error?: boolean;
|
|
48
|
-
disabled?: boolean;
|
|
49
|
-
/** Engine predicate — field not rendered when it returns `false`. */
|
|
50
|
-
visibleWhen?: (engine: Engine) => boolean;
|
|
51
|
-
/** RBAC access requirement (combines with explicit `disabled`). */
|
|
52
|
-
access?: AccessRequirement;
|
|
53
|
-
/** Root className shortcut (`cn`'d with the variant root class). */
|
|
54
|
-
sx?: string;
|
|
55
|
-
/** Per-slot className overrides. */
|
|
56
|
-
slotProps?: NumberFieldSlotProps;
|
|
57
|
-
/** Controlled value (form mode reads from the bridge if omitted). */
|
|
58
|
-
value?: number | string | null;
|
|
59
|
-
/** Default value (uncontrolled, standalone mode only). */
|
|
60
|
-
defaultValue?: number | string | null;
|
|
61
|
-
/** Min allowed value (passed to the input + clamps stepper). */
|
|
62
|
-
min?: number;
|
|
63
|
-
/** Max allowed value (passed to the input + clamps stepper). */
|
|
64
|
-
max?: number;
|
|
65
|
-
/** Stepper step size. Default 1. */
|
|
66
|
-
step?: number;
|
|
67
|
-
/** Show inline +/− stepper buttons inside the input wrapper. Default false. */
|
|
68
|
-
showStepper?: boolean;
|
|
69
|
-
/** Change handler — receives the native event; consume value via bridge. */
|
|
70
|
-
onChange?: ChangeEventHandler<HTMLInputElement>;
|
|
71
|
-
onBlur?: FocusEventHandler<HTMLInputElement>;
|
|
72
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import { tv, type VariantProps } from 'tailwind-variants';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tailwind-variants recipe for `<NumberField>`.
|
|
5
|
-
*
|
|
6
|
-
* Same 7-slot taxonomy as `<TextField>` for visual/override parity, plus
|
|
7
|
-
* a dedicated `stepper` slot for the optional +/- increment buttons.
|
|
8
|
-
*/
|
|
9
|
-
export const numberFieldVariants = tv({
|
|
10
|
-
slots: {
|
|
11
|
-
root: 'flex',
|
|
12
|
-
label: 'block text-sm font-medium text-neutral-900 mb-1',
|
|
13
|
-
requiredMark: 'text-danger-500 ml-0.5',
|
|
14
|
-
inputWrapper: [
|
|
15
|
-
'relative flex items-center',
|
|
16
|
-
// bg-neutral-50 inverts via @dashforge/tw-tokens — dark-mode aware.
|
|
17
|
-
'rounded-md border bg-neutral-50',
|
|
18
|
-
'border-neutral-300',
|
|
19
|
-
'transition-colors',
|
|
20
|
-
'focus-within:border-primary-500 focus-within:ring-2 focus-within:ring-primary-500/30',
|
|
21
|
-
],
|
|
22
|
-
input: [
|
|
23
|
-
'block w-full bg-transparent outline-none',
|
|
24
|
-
'text-neutral-900 placeholder:text-neutral-400',
|
|
25
|
-
'disabled:cursor-not-allowed disabled:opacity-60',
|
|
26
|
-
'read-only:cursor-default',
|
|
27
|
-
// Suppress the native browser spinner — we ship our own optional stepper
|
|
28
|
-
'[-moz-appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none',
|
|
29
|
-
// Right-align numeric values by convention
|
|
30
|
-
'text-right',
|
|
31
|
-
],
|
|
32
|
-
stepper: [
|
|
33
|
-
'flex flex-col border-l border-neutral-300',
|
|
34
|
-
'select-none',
|
|
35
|
-
],
|
|
36
|
-
stepperButton: [
|
|
37
|
-
'flex items-center justify-center px-2 cursor-pointer',
|
|
38
|
-
'text-neutral-600 hover:text-neutral-900 hover:bg-neutral-100',
|
|
39
|
-
'disabled:cursor-not-allowed disabled:opacity-40 disabled:hover:bg-transparent',
|
|
40
|
-
'transition-colors',
|
|
41
|
-
],
|
|
42
|
-
helperText: 'mt-1 text-sm text-neutral-600',
|
|
43
|
-
errorText: 'mt-1 text-sm text-danger-600',
|
|
44
|
-
},
|
|
45
|
-
variants: {
|
|
46
|
-
size: {
|
|
47
|
-
sm: {
|
|
48
|
-
inputWrapper: 'h-8',
|
|
49
|
-
input: 'text-sm px-2',
|
|
50
|
-
stepperButton: 'h-4 text-xs',
|
|
51
|
-
},
|
|
52
|
-
md: {
|
|
53
|
-
inputWrapper: 'h-10',
|
|
54
|
-
input: 'text-base px-3',
|
|
55
|
-
stepperButton: 'h-5 text-sm',
|
|
56
|
-
},
|
|
57
|
-
lg: {
|
|
58
|
-
inputWrapper: 'h-12',
|
|
59
|
-
input: 'text-lg px-4',
|
|
60
|
-
stepperButton: 'h-6 text-base',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
layout: {
|
|
64
|
-
stacked: { root: 'flex-col' },
|
|
65
|
-
inline: { root: 'flex-row items-start gap-3', label: 'mb-0 pt-2 whitespace-nowrap shrink-0' },
|
|
66
|
-
},
|
|
67
|
-
error: {
|
|
68
|
-
true: {
|
|
69
|
-
inputWrapper:
|
|
70
|
-
'border-danger-500 focus-within:border-danger-500 focus-within:ring-danger-500/30',
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
fullWidth: {
|
|
74
|
-
true: {
|
|
75
|
-
root: 'w-full',
|
|
76
|
-
inputWrapper: 'w-full',
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
disabled: {
|
|
80
|
-
true: {
|
|
81
|
-
inputWrapper: 'opacity-60 cursor-not-allowed bg-neutral-200',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
defaultVariants: {
|
|
86
|
-
size: 'md',
|
|
87
|
-
layout: 'stacked',
|
|
88
|
-
error: false,
|
|
89
|
-
fullWidth: false,
|
|
90
|
-
disabled: false,
|
|
91
|
-
},
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
export type NumberFieldVariants = VariantProps<typeof numberFieldVariants>;
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
3
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
4
|
-
import { DashFormProvider } from '@dashforge/forms';
|
|
5
|
-
import { OTPField } from './OTPField.js';
|
|
6
|
-
|
|
7
|
-
describe('<OTPField>', () => {
|
|
8
|
-
describe('standalone (no DashForm)', () => {
|
|
9
|
-
it('renders the configured number of slots (default 6)', () => {
|
|
10
|
-
const { container } = render(<OTPField name="otp" label="Code" />);
|
|
11
|
-
// Slots are `<div data-active>` cells inside the slots row — the
|
|
12
|
-
// hidden input is the 7th flex child but is the only role=textbox.
|
|
13
|
-
const slots = container.querySelectorAll('[aria-hidden="true"][class*="rounded-md"]');
|
|
14
|
-
expect(slots).toHaveLength(6);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it('renders the requested number of slots (length=4)', () => {
|
|
18
|
-
const { container } = render(
|
|
19
|
-
<OTPField name="otp" label="Code" length={4} />
|
|
20
|
-
);
|
|
21
|
-
const slots = container.querySelectorAll('[aria-hidden="true"][class*="rounded-md"]');
|
|
22
|
-
expect(slots).toHaveLength(4);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('renders a `*` for required fields', () => {
|
|
26
|
-
render(<OTPField name="otp" label="Code" required />);
|
|
27
|
-
expect(screen.getByText('*')).toBeTruthy();
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
it('renders helperText when provided', () => {
|
|
31
|
-
render(
|
|
32
|
-
<OTPField name="otp" label="Code" helperText="Check your phone" />
|
|
33
|
-
);
|
|
34
|
-
expect(screen.getByText('Check your phone')).toBeTruthy();
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('displays a controlled value across its slots', () => {
|
|
38
|
-
const { container } = render(
|
|
39
|
-
<OTPField name="otp" label="Code" value="1234" length={4} onChange={() => undefined} />
|
|
40
|
-
);
|
|
41
|
-
const charSpans = container.querySelectorAll('[class*="font-mono"] span');
|
|
42
|
-
// First 4 slots are filled with 1234
|
|
43
|
-
expect(charSpans[0]?.textContent).toBe('1');
|
|
44
|
-
expect(charSpans[1]?.textContent).toBe('2');
|
|
45
|
-
expect(charSpans[2]?.textContent).toBe('3');
|
|
46
|
-
expect(charSpans[3]?.textContent).toBe('4');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('numeric mode sanitises non-digit input', () => {
|
|
50
|
-
const onChange = vi.fn();
|
|
51
|
-
render(
|
|
52
|
-
<OTPField name="otp" label="Code" mode="numeric" length={4} onChange={onChange} />
|
|
53
|
-
);
|
|
54
|
-
// The hidden input absorbs keystrokes
|
|
55
|
-
const input = screen.getByLabelText('Code') as HTMLInputElement;
|
|
56
|
-
fireEvent.change(input, { target: { value: '12ab34' } });
|
|
57
|
-
// Only digits survive (and capped at length=4)
|
|
58
|
-
expect(onChange).toHaveBeenCalledWith('1234');
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('alphanumeric mode allows letters + digits', () => {
|
|
62
|
-
const onChange = vi.fn();
|
|
63
|
-
render(
|
|
64
|
-
<OTPField
|
|
65
|
-
name="otp"
|
|
66
|
-
label="Code"
|
|
67
|
-
mode="alphanumeric"
|
|
68
|
-
length={6}
|
|
69
|
-
onChange={onChange}
|
|
70
|
-
/>
|
|
71
|
-
);
|
|
72
|
-
const input = screen.getByLabelText('Code') as HTMLInputElement;
|
|
73
|
-
fireEvent.change(input, { target: { value: 'a1B2-c3' } });
|
|
74
|
-
expect(onChange).toHaveBeenCalledWith('a1B2c3');
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('fires onComplete when all slots are filled', () => {
|
|
78
|
-
const onComplete = vi.fn();
|
|
79
|
-
render(
|
|
80
|
-
<OTPField
|
|
81
|
-
name="otp"
|
|
82
|
-
label="Code"
|
|
83
|
-
length={4}
|
|
84
|
-
onComplete={onComplete}
|
|
85
|
-
/>
|
|
86
|
-
);
|
|
87
|
-
const input = screen.getByLabelText('Code') as HTMLInputElement;
|
|
88
|
-
fireEvent.change(input, { target: { value: '9999' } });
|
|
89
|
-
expect(onComplete).toHaveBeenCalledWith('9999');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('does NOT fire onComplete on partial input', () => {
|
|
93
|
-
const onComplete = vi.fn();
|
|
94
|
-
render(
|
|
95
|
-
<OTPField name="otp" label="Code" length={6} onComplete={onComplete} />
|
|
96
|
-
);
|
|
97
|
-
const input = screen.getByLabelText('Code') as HTMLInputElement;
|
|
98
|
-
fireEvent.change(input, { target: { value: '123' } });
|
|
99
|
-
expect(onComplete).not.toHaveBeenCalled();
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('respects `disabled` on the underlying input', () => {
|
|
103
|
-
render(<OTPField name="otp" label="Code" disabled />);
|
|
104
|
-
const input = screen.getByLabelText('Code') as HTMLInputElement;
|
|
105
|
-
expect(input.disabled).toBe(true);
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
describe('inside DashFormProvider', () => {
|
|
110
|
-
it('mounts cleanly inside a bridge-providing tree', () => {
|
|
111
|
-
render(
|
|
112
|
-
<DashFormProvider defaultValues={{ otp: '' }}>
|
|
113
|
-
<OTPField name="otp" label="Code" length={4} />
|
|
114
|
-
</DashFormProvider>
|
|
115
|
-
);
|
|
116
|
-
// Mount-only assertion. Initial value reflection and round-trip
|
|
117
|
-
// are timing-sensitive in vitest; verified end-to-end in dash.
|
|
118
|
-
expect(screen.getByLabelText('Code')).toBeInstanceOf(HTMLInputElement);
|
|
119
|
-
});
|
|
120
|
-
});
|
|
121
|
-
});
|