torch-glare 2.5.6 → 2.6.0
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/README.md +4 -3
- package/dist/bin/index.js +9 -24
- package/dist/bin/index.js.map +1 -1
- package/dist/src/commands/add.d.ts +2 -2
- package/dist/src/commands/add.d.ts.map +1 -1
- package/dist/src/commands/add.js +4 -70
- package/dist/src/commands/add.js.map +1 -1
- package/dist/src/commands/hook.d.ts +5 -4
- package/dist/src/commands/hook.d.ts.map +1 -1
- package/dist/src/commands/hook.js +7 -75
- package/dist/src/commands/hook.js.map +1 -1
- package/dist/src/commands/init.js +1 -1
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/layout.d.ts +5 -4
- package/dist/src/commands/layout.d.ts.map +1 -1
- package/dist/src/commands/layout.js +7 -75
- package/dist/src/commands/layout.js.map +1 -1
- package/dist/src/commands/provider.d.ts +5 -4
- package/dist/src/commands/provider.d.ts.map +1 -1
- package/dist/src/commands/provider.js +7 -75
- package/dist/src/commands/provider.js.map +1 -1
- package/dist/src/commands/update.d.ts.map +1 -1
- package/dist/src/commands/update.js +78 -43
- package/dist/src/commands/update.js.map +1 -1
- package/dist/src/commands/utils.d.ts +5 -4
- package/dist/src/commands/utils.d.ts.map +1 -1
- package/dist/src/commands/utils.js +7 -80
- package/dist/src/commands/utils.js.map +1 -1
- package/dist/src/shared/addFromRegistry.d.ts +16 -0
- package/dist/src/shared/addFromRegistry.d.ts.map +1 -0
- package/dist/src/shared/addFromRegistry.js +88 -0
- package/dist/src/shared/addFromRegistry.js.map +1 -0
- package/dist/src/shared/getInstallCommand.d.ts.map +1 -1
- package/dist/src/shared/getInstallCommand.js +5 -0
- package/dist/src/shared/getInstallCommand.js.map +1 -1
- package/dist/src/shared/installDependencies.d.ts +0 -10
- package/dist/src/shared/installDependencies.d.ts.map +1 -1
- package/dist/src/shared/installDependencies.js +0 -17
- package/dist/src/shared/installDependencies.js.map +1 -1
- package/dist/src/shared/installFromPlan.d.ts +7 -5
- package/dist/src/shared/installFromPlan.d.ts.map +1 -1
- package/dist/src/shared/installFromPlan.js +103 -36
- package/dist/src/shared/installFromPlan.js.map +1 -1
- package/dist/src/shared/loadRegistry.d.ts +12 -3
- package/dist/src/shared/loadRegistry.d.ts.map +1 -1
- package/dist/src/shared/loadRegistry.js +19 -20
- package/dist/src/shared/loadRegistry.js.map +1 -1
- package/dist/src/shared/registryClient.d.ts +19 -0
- package/dist/src/shared/registryClient.d.ts.map +1 -0
- package/dist/src/shared/registryClient.js +128 -0
- package/dist/src/shared/registryClient.js.map +1 -0
- package/dist/src/shared/resolveEntry.d.ts +10 -7
- package/dist/src/shared/resolveEntry.d.ts.map +1 -1
- package/dist/src/shared/resolveEntry.js +18 -13
- package/dist/src/shared/resolveEntry.js.map +1 -1
- package/dist/src/shared/suggestOtherCommand.d.ts +7 -1
- package/dist/src/shared/suggestOtherCommand.d.ts.map +1 -1
- package/dist/src/shared/suggestOtherCommand.js +29 -23
- package/dist/src/shared/suggestOtherCommand.js.map +1 -1
- package/dist/src/types/main.d.ts +31 -2
- package/dist/src/types/main.d.ts.map +1 -1
- package/package.json +6 -11
- package/apps/lib/components/ActionButton.tsx +0 -93
- package/apps/lib/components/ActionsGroup.tsx +0 -27
- package/apps/lib/components/AlertDialog.tsx +0 -204
- package/apps/lib/components/Avatar.tsx +0 -46
- package/apps/lib/components/Badge.tsx +0 -250
- package/apps/lib/components/BadgeField.tsx +0 -403
- package/apps/lib/components/Breadcrumb.tsx +0 -280
- package/apps/lib/components/Button.tsx +0 -325
- package/apps/lib/components/ButtonGroup.tsx +0 -190
- package/apps/lib/components/Calendar.tsx +0 -115
- package/apps/lib/components/Card.tsx +0 -95
- package/apps/lib/components/Checkbox.tsx +0 -42
- package/apps/lib/components/ColorPicker.tsx +0 -441
- package/apps/lib/components/ConclusionHeader.tsx +0 -148
- package/apps/lib/components/ContextMenu.tsx +0 -571
- package/apps/lib/components/CountBadge.tsx +0 -52
- package/apps/lib/components/DataTable.tsx +0 -213
- package/apps/lib/components/DataViews/badge.ts +0 -49
- package/apps/lib/components/DataViews/cell.tsx +0 -324
- package/apps/lib/components/DataViews/context.ts +0 -144
- package/apps/lib/components/DataViews/data-views.tsx +0 -395
- package/apps/lib/components/DataViews/filters/children.tsx +0 -98
- package/apps/lib/components/DataViews/filters/custom.tsx +0 -34
- package/apps/lib/components/DataViews/filters/filters.tsx +0 -157
- package/apps/lib/components/DataViews/filters/index.ts +0 -4
- package/apps/lib/components/DataViews/filters/labelled.tsx +0 -20
- package/apps/lib/components/DataViews/filters/presets.tsx +0 -65
- package/apps/lib/components/DataViews/filters/sync.tsx +0 -35
- package/apps/lib/components/DataViews/filters/values.ts +0 -173
- package/apps/lib/components/DataViews/header.tsx +0 -220
- package/apps/lib/components/DataViews/hooks/index.ts +0 -5
- package/apps/lib/components/DataViews/hooks/useActiveRow.ts +0 -22
- package/apps/lib/components/DataViews/hooks/useControllable.ts +0 -52
- package/apps/lib/components/DataViews/index.ts +0 -88
- package/apps/lib/components/DataViews/panel/columns.tsx +0 -153
- package/apps/lib/components/DataViews/panel/controls.tsx +0 -106
- package/apps/lib/components/DataViews/panel/index.ts +0 -3
- package/apps/lib/components/DataViews/panel/panel.tsx +0 -164
- package/apps/lib/components/DataViews/panel/saved-views.tsx +0 -67
- package/apps/lib/components/DataViews/panel/section.tsx +0 -81
- package/apps/lib/components/DataViews/panel/sort.tsx +0 -42
- package/apps/lib/components/DataViews/panel/tab.tsx +0 -31
- package/apps/lib/components/DataViews/slots.ts +0 -72
- package/apps/lib/components/DataViews/states.tsx +0 -73
- package/apps/lib/components/DataViews/types.ts +0 -510
- package/apps/lib/components/DataViews/views/board-view.tsx +0 -381
- package/apps/lib/components/DataViews/views/card-rows.tsx +0 -36
- package/apps/lib/components/DataViews/views/inbox-view.tsx +0 -260
- package/apps/lib/components/DataViews/views/pane-views.tsx +0 -196
- package/apps/lib/components/DataViews/views/table-view.tsx +0 -493
- package/apps/lib/components/DataViews/views/tree-view.tsx +0 -367
- package/apps/lib/components/DatePicker.tsx +0 -232
- package/apps/lib/components/Dialog.tsx +0 -116
- package/apps/lib/components/Divider.tsx +0 -28
- package/apps/lib/components/Drawer.tsx +0 -451
- package/apps/lib/components/DropdownMenu.tsx +0 -554
- package/apps/lib/components/FieldHint.tsx +0 -69
- package/apps/lib/components/Form.tsx +0 -182
- package/apps/lib/components/FormBuilder/context.ts +0 -85
- package/apps/lib/components/FormBuilder/field-kind.ts +0 -28
- package/apps/lib/components/FormBuilder/fields/ChoiceFields.tsx +0 -60
- package/apps/lib/components/FormBuilder/fields/ColorField.tsx +0 -59
- package/apps/lib/components/FormBuilder/fields/CustomField.tsx +0 -11
- package/apps/lib/components/FormBuilder/fields/DateField.tsx +0 -32
- package/apps/lib/components/FormBuilder/fields/FieldArray.tsx +0 -74
- package/apps/lib/components/FormBuilder/fields/FieldShell.tsx +0 -168
- package/apps/lib/components/FormBuilder/fields/FileField.tsx +0 -39
- package/apps/lib/components/FormBuilder/fields/OptionListFields.tsx +0 -132
- package/apps/lib/components/FormBuilder/fields/OtpField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/PhoneField.tsx +0 -109
- package/apps/lib/components/FormBuilder/fields/RichTextEditorField.tsx +0 -31
- package/apps/lib/components/FormBuilder/fields/SelectField.tsx +0 -114
- package/apps/lib/components/FormBuilder/fields/SignatureField.tsx +0 -157
- package/apps/lib/components/FormBuilder/fields/SliderField.tsx +0 -67
- package/apps/lib/components/FormBuilder/fields/SwitchBoxField.tsx +0 -41
- package/apps/lib/components/FormBuilder/fields/TableField.tsx +0 -336
- package/apps/lib/components/FormBuilder/fields/TextField.tsx +0 -213
- package/apps/lib/components/FormBuilder/fields/TreeSelectField.tsx +0 -43
- package/apps/lib/components/FormBuilder/fields/countries.ts +0 -303
- package/apps/lib/components/FormBuilder/fields/index.ts +0 -25
- package/apps/lib/components/FormBuilder/form-builder.tsx +0 -198
- package/apps/lib/components/FormBuilder/index.ts +0 -31
- package/apps/lib/components/FormBuilder/numberFormat.ts +0 -16
- package/apps/lib/components/FormBuilder/submit.tsx +0 -67
- package/apps/lib/components/FormBuilder/types.ts +0 -316
- package/apps/lib/components/FormRenderer/FormDrawer.tsx +0 -250
- package/apps/lib/components/FormRenderer/detail.tsx +0 -256
- package/apps/lib/components/FormRenderer/form-renderer.tsx +0 -358
- package/apps/lib/components/FormRenderer/header.tsx +0 -88
- package/apps/lib/components/FormRenderer/index.ts +0 -17
- package/apps/lib/components/FormRenderer/notch-action.tsx +0 -64
- package/apps/lib/components/FormRenderer/section.tsx +0 -39
- package/apps/lib/components/FormRenderer/stepper.tsx +0 -393
- package/apps/lib/components/FormRenderer/types.ts +0 -115
- package/apps/lib/components/FormSummary.tsx +0 -282
- package/apps/lib/components/HeaderBar.tsx +0 -117
- package/apps/lib/components/ImageAttachment.tsx +0 -209
- package/apps/lib/components/InnerLabelField.tsx +0 -154
- package/apps/lib/components/Input.tsx +0 -224
- package/apps/lib/components/InputField.tsx +0 -147
- package/apps/lib/components/InputOTP.tsx +0 -84
- package/apps/lib/components/Label.tsx +0 -129
- package/apps/lib/components/LabelField.tsx +0 -76
- package/apps/lib/components/LabeledCheckBox.tsx +0 -51
- package/apps/lib/components/LabeledRadio.tsx +0 -51
- package/apps/lib/components/LinkButton.tsx +0 -94
- package/apps/lib/components/LoginButton.tsx +0 -51
- package/apps/lib/components/PasswordLevel.tsx +0 -58
- package/apps/lib/components/Popover.tsx +0 -312
- package/apps/lib/components/ProfileMenu.tsx +0 -85
- package/apps/lib/components/Radio.tsx +0 -59
- package/apps/lib/components/RadioCard.tsx +0 -50
- package/apps/lib/components/ScrollArea.tsx +0 -54
- package/apps/lib/components/SearchField.tsx +0 -62
- package/apps/lib/components/SearchableSelect.tsx +0 -318
- package/apps/lib/components/SearchableTable.tsx +0 -344
- package/apps/lib/components/SearchableTree.tsx +0 -485
- package/apps/lib/components/SearchableTreeDialog.tsx +0 -502
- package/apps/lib/components/SectionBlock.tsx +0 -168
- package/apps/lib/components/Select.tsx +0 -347
- package/apps/lib/components/SimpleSelect.tsx +0 -225
- package/apps/lib/components/Skeleton.tsx +0 -12
- package/apps/lib/components/SlideDatePicker.tsx +0 -255
- package/apps/lib/components/SpinLoading.tsx +0 -174
- package/apps/lib/components/Stepper.tsx +0 -480
- package/apps/lib/components/Switch.tsx +0 -82
- package/apps/lib/components/TabFormItem.tsx +0 -153
- package/apps/lib/components/TabSwitch.tsx +0 -172
- package/apps/lib/components/Table.tsx +0 -597
- package/apps/lib/components/TextEditor/ChartBlockTool.ts +0 -676
- package/apps/lib/components/TextEditor/RichTextField.tsx +0 -46
- package/apps/lib/components/TextEditor/TableDnDWrapper.ts +0 -462
- package/apps/lib/components/TextEditor/TextEditor.tsx +0 -1164
- package/apps/lib/components/TextEditor/TextEditorToolbar.tsx +0 -429
- package/apps/lib/components/TextEditor/editor-tools/AlignmentTune.ts +0 -70
- package/apps/lib/components/TextEditor/editor-tools/ColorInlineTool.ts +0 -50
- package/apps/lib/components/TextEditor/editor-tools/StrikethroughInlineTool.ts +0 -48
- package/apps/lib/components/TextEditor/editor-tools/inlineFormat.ts +0 -98
- package/apps/lib/components/TextEditor/editorjs.d.ts +0 -60
- package/apps/lib/components/TextEditor/index.ts +0 -7
- package/apps/lib/components/TextEditor/markdownParser.ts +0 -346
- package/apps/lib/components/Textarea.tsx +0 -117
- package/apps/lib/components/Timeline.tsx +0 -263
- package/apps/lib/components/Toast.tsx +0 -87
- package/apps/lib/components/Toggle.tsx +0 -125
- package/apps/lib/components/ToggleButton.tsx +0 -140
- package/apps/lib/components/Tooltip.tsx +0 -116
- package/apps/lib/components/TransparentLabel.tsx +0 -71
- package/apps/lib/components/TreeDropDown.tsx +0 -115
- package/apps/lib/components/TreeFolder/TreeFolder.tsx +0 -380
- package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +0 -75
- package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +0 -354
- package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +0 -60
- package/apps/lib/components/TreeFolder/icons.tsx +0 -60
- package/apps/lib/components/TreeFolder/index.ts +0 -24
- package/apps/lib/components/TreeFolder/treeFolderUtils.ts +0 -109
- package/apps/lib/components/TreeFolder/types.ts +0 -77
- package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +0 -118
- package/apps/lib/hooks/useActiveTreeItem.ts +0 -61
- package/apps/lib/hooks/useClickOutside.ts +0 -27
- package/apps/lib/hooks/useDragDrop.tsx +0 -365
- package/apps/lib/hooks/useHtmlDir.ts +0 -31
- package/apps/lib/hooks/useInfiniteScroll.ts +0 -108
- package/apps/lib/hooks/useIsMobile.ts +0 -21
- package/apps/lib/hooks/useResize.ts +0 -67
- package/apps/lib/hooks/useTagSelection.ts +0 -300
- package/apps/lib/layouts/CNLayout.tsx +0 -372
- package/apps/lib/layouts/DataViewCard.tsx +0 -62
- package/apps/lib/layouts/FieldSection.tsx +0 -91
- package/apps/lib/layouts/TreeSubLayout.tsx +0 -173
- package/apps/lib/providers/ThemeProvider.tsx +0 -84
- package/apps/lib/registry.json +0 -1217
- package/apps/lib/tsconfig.json +0 -9
- package/apps/lib/tsconfig.tsbuildinfo +0 -1
- package/apps/lib/utils/cn.ts +0 -6
- package/apps/lib/utils/color.ts +0 -175
- package/apps/lib/utils/dataViews/path.ts +0 -67
- package/apps/lib/utils/dataViews/query.ts +0 -73
- package/apps/lib/utils/dataViews/types.ts +0 -187
- package/apps/lib/utils/dateFormat.ts +0 -108
- package/apps/lib/utils/resize.ts +0 -31
- package/apps/lib/utils/scroller.ts +0 -26
- package/apps/lib/utils/types.ts +0 -16
- package/docs/BLOCKS.md +0 -129
- package/docs/CHANGELOG-1.1.16.md +0 -108
- package/docs/Cover.png +0 -0
- package/docs/README.md +0 -215
- package/docs/components/action-button.md +0 -638
- package/docs/components/actions-group.md +0 -708
- package/docs/components/alert-dialog.md +0 -920
- package/docs/components/avatar.md +0 -709
- package/docs/components/badge-field.md +0 -738
- package/docs/components/badge.md +0 -301
- package/docs/components/breadcrumb.md +0 -930
- package/docs/components/button-group.md +0 -650
- package/docs/components/button.md +0 -639
- package/docs/components/calendar.md +0 -555
- package/docs/components/card.md +0 -622
- package/docs/components/chart-block-tool.md +0 -129
- package/docs/components/checkbox.md +0 -699
- package/docs/components/cn-layout.md +0 -854
- package/docs/components/color-picker.md +0 -101
- package/docs/components/conclusion-header.md +0 -80
- package/docs/components/context-menu.md +0 -483
- package/docs/components/count-badge.md +0 -560
- package/docs/components/data-table.md +0 -854
- package/docs/components/data-views/backend-response.md +0 -324
- package/docs/components/data-views/examples/a11y-rtl.md +0 -250
- package/docs/components/data-views/examples/api-orders-route.md +0 -130
- package/docs/components/data-views/examples/fields.md +0 -362
- package/docs/components/data-views/examples/filters.md +0 -307
- package/docs/components/data-views/examples/inbox-routing.md +0 -218
- package/docs/components/data-views/examples/index.md +0 -29
- package/docs/components/data-views/examples/overview.md +0 -244
- package/docs/components/data-views/examples/panel.md +0 -212
- package/docs/components/data-views/examples/scale.md +0 -231
- package/docs/components/data-views/examples/server-side.md +0 -210
- package/docs/components/data-views/examples/state.md +0 -250
- package/docs/components/data-views/examples/tree-custom.md +0 -388
- package/docs/components/data-views/examples/view-registry.md +0 -313
- package/docs/components/data-views/examples/views.md +0 -534
- package/docs/components/data-views/guide.md +0 -405
- package/docs/components/data-views/index.md +0 -1521
- package/docs/components/data-views/migration.md +0 -81
- package/docs/components/date-picker.md +0 -857
- package/docs/components/dialog.md +0 -929
- package/docs/components/divider.md +0 -766
- package/docs/components/drawer.md +0 -654
- package/docs/components/dropdown-menu.md +0 -668
- package/docs/components/field-hint.md +0 -898
- package/docs/components/field-section.md +0 -781
- package/docs/components/form-builder.md +0 -284
- package/docs/components/form-renderer.md +0 -396
- package/docs/components/form-summary.md +0 -138
- package/docs/components/form.md +0 -785
- package/docs/components/header-bar.md +0 -188
- package/docs/components/image-attachment.md +0 -906
- package/docs/components/inner-label-field.md +0 -819
- package/docs/components/input-field.md +0 -623
- package/docs/components/input-otp.md +0 -756
- package/docs/components/input.md +0 -634
- package/docs/components/label-field.md +0 -770
- package/docs/components/label.md +0 -761
- package/docs/components/labeled-check-box.md +0 -642
- package/docs/components/labeled-radio.md +0 -796
- package/docs/components/link-button.md +0 -685
- package/docs/components/login-button.md +0 -872
- package/docs/components/password-level.md +0 -803
- package/docs/components/popover.md +0 -649
- package/docs/components/profile-menu.md +0 -656
- package/docs/components/radio-card.md +0 -801
- package/docs/components/radio.md +0 -735
- package/docs/components/scroll-area.md +0 -691
- package/docs/components/search-field.md +0 -702
- package/docs/components/searchable-select.md +0 -368
- package/docs/components/searchable-table.md +0 -438
- package/docs/components/searchable-tree-dialog.md +0 -190
- package/docs/components/searchable-tree.md +0 -200
- package/docs/components/section-block.md +0 -483
- package/docs/components/select.md +0 -709
- package/docs/components/simple-select.md +0 -741
- package/docs/components/skeleton.md +0 -706
- package/docs/components/slide-date-picker.md +0 -837
- package/docs/components/spin-loading.md +0 -696
- package/docs/components/stepper.md +0 -316
- package/docs/components/switch.md +0 -770
- package/docs/components/tab-form-item.md +0 -773
- package/docs/components/tab-switch.md +0 -170
- package/docs/components/table-dnd-wrapper.md +0 -107
- package/docs/components/table.md +0 -1088
- package/docs/components/text-editor.md +0 -735
- package/docs/components/textarea.md +0 -681
- package/docs/components/timeline.md +0 -254
- package/docs/components/toast.md +0 -773
- package/docs/components/toggle-button.md +0 -643
- package/docs/components/toggle.md +0 -882
- package/docs/components/tooltip.md +0 -513
- package/docs/components/transparent-label.md +0 -721
- package/docs/components/tree-drop-down.md +0 -740
- package/docs/components/tree-folder.md +0 -110
- package/docs/components/tree-sub-layout.md +0 -707
- package/docs/explanation/architecture.md +0 -54
- package/docs/explanation/design-system.md +0 -52
- package/docs/how-to/form-and-list-recipes.md +0 -386
- package/docs/how-to/forms-with-form-builder.md +0 -472
- package/docs/how-to/guides.md +0 -1027
- package/docs/migration/changelog.md +0 -60
- package/docs/migration/form-builder-2.5.2.md +0 -113
- package/docs/reference/cli.md +0 -112
- package/docs/reference/components.md +0 -572
- package/docs/reference/hooks.md +0 -1523
- package/docs/reference/providers.md +0 -866
- package/docs/reference/tailwind-plugins.md +0 -942
- package/docs/reference/theme.md +0 -85
- package/docs/reference/types.md +0 -1016
- package/docs/reference/utilities.md +0 -726
- package/docs/tutorials/building-first-form.md +0 -846
- package/docs/tutorials/component-composition.md +0 -882
- package/docs/tutorials/getting-started.md +0 -302
- package/docs/tutorials/theming-basics.md +0 -785
|
@@ -1,773 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: TabFormItem
|
|
3
|
-
description: Tab-style navigation component for forms and multi-step workflows with top, side, and tree layouts.
|
|
4
|
-
component: true
|
|
5
|
-
group: Forms & Inputs
|
|
6
|
-
keywords: [tab, form, navigation, step, multi-step, wizard, sidebar, topbar]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# TabFormItem
|
|
10
|
-
|
|
11
|
-
A flexible tab navigation component designed for forms and multi-step workflows. Supports three distinct layouts (top, side, tree) with active states and icon support.
|
|
12
|
-
|
|
13
|
-
## Installation
|
|
14
|
-
|
|
15
|
-
TORCH Glare is a copy-in library: the CLI copies this component's source into your project
|
|
16
|
-
(you do **not** install it from the npm package). Run `init` once, then `add`:
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npx torch-glare@latest init
|
|
20
|
-
npx torch-glare@latest add TabFormItem
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`add` also copies any components, hooks, and utilities that `TabFormItem` depends on.
|
|
24
|
-
|
|
25
|
-
## Imports
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import TabFormItem from '@/components/TabFormItem'
|
|
29
|
-
import { formBarItemStyles } from '@/components/TabFormItem'
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
## Basic Usage
|
|
33
|
-
|
|
34
|
-
```tsx
|
|
35
|
-
import TabFormItem from '@/components/TabFormItem'
|
|
36
|
-
|
|
37
|
-
export function BasicTabFormItem() {
|
|
38
|
-
const [activeTab, setActiveTab] = useState('general')
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<div className="flex gap-2">
|
|
42
|
-
<TabFormItem
|
|
43
|
-
componentType="top"
|
|
44
|
-
active={activeTab === 'general'}
|
|
45
|
-
onClick={() => setActiveTab('general')}
|
|
46
|
-
>
|
|
47
|
-
General Settings
|
|
48
|
-
</TabFormItem>
|
|
49
|
-
<TabFormItem
|
|
50
|
-
componentType="top"
|
|
51
|
-
active={activeTab === 'advanced'}
|
|
52
|
-
onClick={() => setActiveTab('advanced')}
|
|
53
|
-
>
|
|
54
|
-
Advanced Settings
|
|
55
|
-
</TabFormItem>
|
|
56
|
-
<TabFormItem
|
|
57
|
-
componentType="top"
|
|
58
|
-
active={activeTab === 'security'}
|
|
59
|
-
onClick={() => setActiveTab('security')}
|
|
60
|
-
>
|
|
61
|
-
Security
|
|
62
|
-
</TabFormItem>
|
|
63
|
-
</div>
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Examples
|
|
69
|
-
|
|
70
|
-
### Top Navigation Tabs
|
|
71
|
-
|
|
72
|
-
Top tabs are typically used for primary navigation within forms or settings pages.
|
|
73
|
-
|
|
74
|
-
```tsx
|
|
75
|
-
export function TopNavigationTabs() {
|
|
76
|
-
const [activeTab, setActiveTab] = useState('profile')
|
|
77
|
-
|
|
78
|
-
const tabs = [
|
|
79
|
-
{ id: 'profile', label: 'Profile', icon: 'ri-user-line' },
|
|
80
|
-
{ id: 'notifications', label: 'Notifications', icon: 'ri-notification-line' },
|
|
81
|
-
{ id: 'privacy', label: 'Privacy', icon: 'ri-shield-line' },
|
|
82
|
-
{ id: 'billing', label: 'Billing', icon: 'ri-bank-card-line' },
|
|
83
|
-
]
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div className="border-b border-border-presentation-tab-topbar-primary">
|
|
87
|
-
<div className="flex gap-1 px-4">
|
|
88
|
-
{tabs.map(tab => (
|
|
89
|
-
<TabFormItem
|
|
90
|
-
key={tab.id}
|
|
91
|
-
componentType="top"
|
|
92
|
-
active={activeTab === tab.id}
|
|
93
|
-
onClick={() => setActiveTab(tab.id)}
|
|
94
|
-
>
|
|
95
|
-
<i className={`${tab.icon} mr-2`}></i>
|
|
96
|
-
{tab.label}
|
|
97
|
-
</TabFormItem>
|
|
98
|
-
))}
|
|
99
|
-
</div>
|
|
100
|
-
</div>
|
|
101
|
-
)
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
### Sidebar Navigation
|
|
106
|
-
|
|
107
|
-
Side tabs are perfect for vertical navigation in settings panels or multi-step forms.
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
export function SidebarNavigation() {
|
|
111
|
-
const [activeSection, setActiveSection] = useState('account')
|
|
112
|
-
|
|
113
|
-
const sections = [
|
|
114
|
-
{ id: 'account', label: 'Account', description: 'Manage your account settings' },
|
|
115
|
-
{ id: 'appearance', label: 'Appearance', description: 'Customize the look and feel' },
|
|
116
|
-
{ id: 'integrations', label: 'Integrations', description: 'Connect third-party services' },
|
|
117
|
-
{ id: 'api', label: 'API Keys', description: 'Manage API access' },
|
|
118
|
-
{ id: 'team', label: 'Team', description: 'Manage team members' },
|
|
119
|
-
]
|
|
120
|
-
|
|
121
|
-
return (
|
|
122
|
-
<div className="flex">
|
|
123
|
-
<div className="w-64 border-r border-border-presentation-tab-sidebar-primary">
|
|
124
|
-
<div className="p-2 space-y-1">
|
|
125
|
-
{sections.map(section => (
|
|
126
|
-
<TabFormItem
|
|
127
|
-
key={section.id}
|
|
128
|
-
componentType="side"
|
|
129
|
-
active={activeSection === section.id}
|
|
130
|
-
onClick={() => setActiveSection(section.id)}
|
|
131
|
-
className="text-left"
|
|
132
|
-
>
|
|
133
|
-
<div className="flex-1">
|
|
134
|
-
<div className="font-medium">{section.label}</div>
|
|
135
|
-
<div className="text-xs text-content-presentation-global-secondary">
|
|
136
|
-
{section.description}
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
</TabFormItem>
|
|
140
|
-
))}
|
|
141
|
-
</div>
|
|
142
|
-
</div>
|
|
143
|
-
<div className="flex-1 p-6">
|
|
144
|
-
{/* Content for active section */}
|
|
145
|
-
</div>
|
|
146
|
-
</div>
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Icon-Only Tabs
|
|
152
|
-
|
|
153
|
-
Use icon-only tabs for compact navigation or mobile interfaces.
|
|
154
|
-
|
|
155
|
-
```tsx
|
|
156
|
-
export function IconOnlyTabs() {
|
|
157
|
-
const [activeTab, setActiveTab] = useState('home')
|
|
158
|
-
|
|
159
|
-
const tabs = [
|
|
160
|
-
{ id: 'home', icon: 'ri-home-3-line', tooltip: 'Home' },
|
|
161
|
-
{ id: 'search', icon: 'ri-search-line', tooltip: 'Search' },
|
|
162
|
-
{ id: 'notifications', icon: 'ri-notification-3-line', tooltip: 'Notifications' },
|
|
163
|
-
{ id: 'profile', icon: 'ri-user-3-line', tooltip: 'Profile' },
|
|
164
|
-
{ id: 'settings', icon: 'ri-settings-3-line', tooltip: 'Settings' },
|
|
165
|
-
]
|
|
166
|
-
|
|
167
|
-
return (
|
|
168
|
-
<div className="flex gap-2">
|
|
169
|
-
{tabs.map(tab => (
|
|
170
|
-
<TabFormItem
|
|
171
|
-
key={tab.id}
|
|
172
|
-
componentType="top"
|
|
173
|
-
buttonType="icon"
|
|
174
|
-
active={activeTab === tab.id}
|
|
175
|
-
onClick={() => setActiveTab(tab.id)}
|
|
176
|
-
title={tab.tooltip}
|
|
177
|
-
aria-label={tab.tooltip}
|
|
178
|
-
>
|
|
179
|
-
<i className={tab.icon}></i>
|
|
180
|
-
</TabFormItem>
|
|
181
|
-
))}
|
|
182
|
-
</div>
|
|
183
|
-
)
|
|
184
|
-
}
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
### Tree Navigation
|
|
188
|
-
|
|
189
|
-
Tree tabs are designed for hierarchical navigation structures.
|
|
190
|
-
|
|
191
|
-
```tsx
|
|
192
|
-
export function TreeNavigation() {
|
|
193
|
-
const [expanded, setExpanded] = useState<string[]>(['docs'])
|
|
194
|
-
const [active, setActive] = useState('getting-started')
|
|
195
|
-
|
|
196
|
-
const treeItems = [
|
|
197
|
-
{
|
|
198
|
-
id: 'docs',
|
|
199
|
-
label: 'Documentation',
|
|
200
|
-
children: [
|
|
201
|
-
{ id: 'getting-started', label: 'Getting Started' },
|
|
202
|
-
{ id: 'installation', label: 'Installation' },
|
|
203
|
-
{ id: 'configuration', label: 'Configuration' },
|
|
204
|
-
]
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
id: 'api',
|
|
208
|
-
label: 'API Reference',
|
|
209
|
-
children: [
|
|
210
|
-
{ id: 'endpoints', label: 'Endpoints' },
|
|
211
|
-
{ id: 'authentication', label: 'Authentication' },
|
|
212
|
-
{ id: 'rate-limits', label: 'Rate Limits' },
|
|
213
|
-
]
|
|
214
|
-
},
|
|
215
|
-
]
|
|
216
|
-
|
|
217
|
-
const toggleExpand = (id: string) => {
|
|
218
|
-
setExpanded(prev =>
|
|
219
|
-
prev.includes(id)
|
|
220
|
-
? prev.filter(i => i !== id)
|
|
221
|
-
: [...prev, id]
|
|
222
|
-
)
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return (
|
|
226
|
-
<div className="w-64">
|
|
227
|
-
{treeItems.map(item => (
|
|
228
|
-
<div key={item.id}>
|
|
229
|
-
<TabFormItem
|
|
230
|
-
componentType="tree"
|
|
231
|
-
onClick={() => toggleExpand(item.id)}
|
|
232
|
-
className="font-medium"
|
|
233
|
-
>
|
|
234
|
-
<i className={`ri-arrow-${expanded.includes(item.id) ? 'down' : 'right'}-s-line mr-1`}></i>
|
|
235
|
-
{item.label}
|
|
236
|
-
</TabFormItem>
|
|
237
|
-
{expanded.includes(item.id) && (
|
|
238
|
-
<div className="ml-6">
|
|
239
|
-
{item.children.map(child => (
|
|
240
|
-
<TabFormItem
|
|
241
|
-
key={child.id}
|
|
242
|
-
componentType="tree"
|
|
243
|
-
active={active === child.id}
|
|
244
|
-
onClick={() => setActive(child.id)}
|
|
245
|
-
>
|
|
246
|
-
{child.label}
|
|
247
|
-
</TabFormItem>
|
|
248
|
-
))}
|
|
249
|
-
</div>
|
|
250
|
-
)}
|
|
251
|
-
</div>
|
|
252
|
-
))}
|
|
253
|
-
</div>
|
|
254
|
-
)
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
### Multi-Step Form Wizard
|
|
259
|
-
|
|
260
|
-
Combine TabFormItem with form steps for a wizard-like experience.
|
|
261
|
-
|
|
262
|
-
```tsx
|
|
263
|
-
export function FormWizard() {
|
|
264
|
-
const [currentStep, setCurrentStep] = useState(0)
|
|
265
|
-
const [completedSteps, setCompletedSteps] = useState<number[]>([])
|
|
266
|
-
|
|
267
|
-
const steps = [
|
|
268
|
-
{ label: 'Personal Info', icon: 'ri-user-line' },
|
|
269
|
-
{ label: 'Address', icon: 'ri-map-pin-line' },
|
|
270
|
-
{ label: 'Payment', icon: 'ri-bank-card-line' },
|
|
271
|
-
{ label: 'Review', icon: 'ri-check-double-line' },
|
|
272
|
-
]
|
|
273
|
-
|
|
274
|
-
const goToStep = (index: number) => {
|
|
275
|
-
if (index <= currentStep || completedSteps.includes(index - 1)) {
|
|
276
|
-
setCurrentStep(index)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const nextStep = () => {
|
|
281
|
-
if (currentStep < steps.length - 1) {
|
|
282
|
-
setCompletedSteps(prev => [...prev, currentStep])
|
|
283
|
-
setCurrentStep(currentStep + 1)
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
return (
|
|
288
|
-
<div>
|
|
289
|
-
{/* Step indicators */}
|
|
290
|
-
<div className="flex justify-between mb-8">
|
|
291
|
-
{steps.map((step, index) => (
|
|
292
|
-
<div key={index} className="flex-1">
|
|
293
|
-
<TabFormItem
|
|
294
|
-
componentType="top"
|
|
295
|
-
active={currentStep === index}
|
|
296
|
-
onClick={() => goToStep(index)}
|
|
297
|
-
disabled={index > currentStep && !completedSteps.includes(index - 1)}
|
|
298
|
-
className={cn(
|
|
299
|
-
"w-full",
|
|
300
|
-
completedSteps.includes(index) && "text-content-presentation-action-success-primary"
|
|
301
|
-
)}
|
|
302
|
-
>
|
|
303
|
-
<div className="flex items-center gap-2">
|
|
304
|
-
<span className="flex items-center justify-center w-6 h-6 rounded-full bg-background-presentation-global-tertiary text-xs">
|
|
305
|
-
{completedSteps.includes(index) ? (
|
|
306
|
-
<i className="ri-check-line text-content-presentation-action-success-primary"></i>
|
|
307
|
-
) : (
|
|
308
|
-
index + 1
|
|
309
|
-
)}
|
|
310
|
-
</span>
|
|
311
|
-
<span className="hidden sm:inline">{step.label}</span>
|
|
312
|
-
</div>
|
|
313
|
-
</TabFormItem>
|
|
314
|
-
{index < steps.length - 1 && (
|
|
315
|
-
<div className={cn(
|
|
316
|
-
"h-0.5 mt-4",
|
|
317
|
-
completedSteps.includes(index)
|
|
318
|
-
? "bg-background-presentation-action-success-primary"
|
|
319
|
-
: "bg-border-presentation-global-primary"
|
|
320
|
-
)} />
|
|
321
|
-
)}
|
|
322
|
-
</div>
|
|
323
|
-
))}
|
|
324
|
-
</div>
|
|
325
|
-
|
|
326
|
-
{/* Step content */}
|
|
327
|
-
<div className="p-6 border border-border-presentation-global-primary rounded-lg">
|
|
328
|
-
<h2 className="text-xl font-semibold mb-4">{steps[currentStep].label}</h2>
|
|
329
|
-
{/* Form fields for current step */}
|
|
330
|
-
<button onClick={nextStep} className="mt-4">
|
|
331
|
-
{currentStep === steps.length - 1 ? 'Submit' : 'Next'}
|
|
332
|
-
</button>
|
|
333
|
-
</div>
|
|
334
|
-
</div>
|
|
335
|
-
)
|
|
336
|
-
}
|
|
337
|
-
```
|
|
338
|
-
|
|
339
|
-
### With React Router
|
|
340
|
-
|
|
341
|
-
Integrate TabFormItem with React Router for navigation.
|
|
342
|
-
|
|
343
|
-
```tsx
|
|
344
|
-
import { NavLink } from 'react-router-dom'
|
|
345
|
-
|
|
346
|
-
export function RouterTabs() {
|
|
347
|
-
const routes = [
|
|
348
|
-
{ path: '/dashboard', label: 'Dashboard', icon: 'ri-dashboard-line' },
|
|
349
|
-
{ path: '/projects', label: 'Projects', icon: 'ri-folder-line' },
|
|
350
|
-
{ path: '/tasks', label: 'Tasks', icon: 'ri-task-line' },
|
|
351
|
-
{ path: '/calendar', label: 'Calendar', icon: 'ri-calendar-line' },
|
|
352
|
-
]
|
|
353
|
-
|
|
354
|
-
return (
|
|
355
|
-
<nav className="border-b border-border-presentation-tab-topbar-primary">
|
|
356
|
-
<div className="flex gap-1 px-4">
|
|
357
|
-
{routes.map(route => (
|
|
358
|
-
<NavLink key={route.path} to={route.path}>
|
|
359
|
-
{({ isActive }) => (
|
|
360
|
-
<TabFormItem
|
|
361
|
-
componentType="top"
|
|
362
|
-
active={isActive}
|
|
363
|
-
asChild
|
|
364
|
-
>
|
|
365
|
-
<span>
|
|
366
|
-
<i className={`${route.icon} mr-2`}></i>
|
|
367
|
-
{route.label}
|
|
368
|
-
</span>
|
|
369
|
-
</TabFormItem>
|
|
370
|
-
)}
|
|
371
|
-
</NavLink>
|
|
372
|
-
))}
|
|
373
|
-
</div>
|
|
374
|
-
</nav>
|
|
375
|
-
)
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
### Themed Tabs
|
|
380
|
-
|
|
381
|
-
Apply different themes for various contexts.
|
|
382
|
-
|
|
383
|
-
```tsx
|
|
384
|
-
export function ThemedTabs() {
|
|
385
|
-
const [theme, setTheme] = useState<'default' | 'dark' | 'light'>('default')
|
|
386
|
-
const [activeTab, setActiveTab] = useState('overview')
|
|
387
|
-
|
|
388
|
-
return (
|
|
389
|
-
<div className="space-y-4">
|
|
390
|
-
{/* Theme selector */}
|
|
391
|
-
<div className="flex gap-2">
|
|
392
|
-
{(['default', 'dark', 'light'] as const).map(t => (
|
|
393
|
-
<button
|
|
394
|
-
key={t}
|
|
395
|
-
onClick={() => setTheme(t)}
|
|
396
|
-
className={cn(
|
|
397
|
-
"px-3 py-1 rounded",
|
|
398
|
-
theme === t && "bg-background-presentation-action-primary-hover text-white"
|
|
399
|
-
)}
|
|
400
|
-
>
|
|
401
|
-
{t}
|
|
402
|
-
</button>
|
|
403
|
-
))}
|
|
404
|
-
</div>
|
|
405
|
-
|
|
406
|
-
{/* Themed tabs */}
|
|
407
|
-
<div className={cn(
|
|
408
|
-
"p-4 rounded-lg",
|
|
409
|
-
theme === 'dark' && "bg-gray-900",
|
|
410
|
-
theme === 'light' && "bg-gray-50"
|
|
411
|
-
)}>
|
|
412
|
-
<div className="flex gap-2">
|
|
413
|
-
{['overview', 'analytics', 'reports', 'settings'].map(tab => (
|
|
414
|
-
<TabFormItem
|
|
415
|
-
key={tab}
|
|
416
|
-
componentType="top"
|
|
417
|
-
theme={theme}
|
|
418
|
-
active={activeTab === tab}
|
|
419
|
-
onClick={() => setActiveTab(tab)}
|
|
420
|
-
>
|
|
421
|
-
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
|
422
|
-
</TabFormItem>
|
|
423
|
-
))}
|
|
424
|
-
</div>
|
|
425
|
-
</div>
|
|
426
|
-
</div>
|
|
427
|
-
)
|
|
428
|
-
}
|
|
429
|
-
```
|
|
430
|
-
|
|
431
|
-
## API Reference
|
|
432
|
-
|
|
433
|
-
### TabFormItem Props
|
|
434
|
-
|
|
435
|
-
| Prop | Type | Default | Description |
|
|
436
|
-
|------|------|---------|-------------|
|
|
437
|
-
| componentType | `'top' \| 'side' \| 'tree'` | `'side'` | Layout style of the tab |
|
|
438
|
-
| active | `boolean` | `false` | Whether the tab is currently active |
|
|
439
|
-
| buttonType | `'button' \| 'icon'` | `'button'` | Display mode for the tab |
|
|
440
|
-
| theme | `'dark' \| 'light' \| 'default'` | `'default'` | Theme variant |
|
|
441
|
-
| asChild | `boolean` | `false` | Merge props with child element |
|
|
442
|
-
| as | `React.ElementType` | `'button'` | Custom element type to render |
|
|
443
|
-
| className | `string` | - | Additional CSS classes |
|
|
444
|
-
| children | `ReactNode` | - | Tab content |
|
|
445
|
-
| onClick | `() => void` | - | Click handler |
|
|
446
|
-
| disabled | `boolean` | - | Disable tab interaction |
|
|
447
|
-
|
|
448
|
-
### formBarItemStyles
|
|
449
|
-
|
|
450
|
-
The `formBarItemStyles` function is exported for custom implementations:
|
|
451
|
-
|
|
452
|
-
```typescript
|
|
453
|
-
const customTabStyles = formBarItemStyles({
|
|
454
|
-
componentType: 'top',
|
|
455
|
-
active: true,
|
|
456
|
-
buttonType: 'button'
|
|
457
|
-
})
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
## Styling
|
|
461
|
-
|
|
462
|
-
### Component Types
|
|
463
|
-
|
|
464
|
-
#### Top Style
|
|
465
|
-
- Height: 28px
|
|
466
|
-
- Horizontal padding: 24px
|
|
467
|
-
- Rounded corners: 4px
|
|
468
|
-
- Hover effect with background color change
|
|
469
|
-
- Active state with distinct background and text color
|
|
470
|
-
|
|
471
|
-
#### Side Style
|
|
472
|
-
- Height: 36px
|
|
473
|
-
- Full width
|
|
474
|
-
- Left-aligned content
|
|
475
|
-
- Padding animation on hover (8px → 16px)
|
|
476
|
-
- Active state with selected background
|
|
477
|
-
|
|
478
|
-
#### Tree Style
|
|
479
|
-
- Height: 36px
|
|
480
|
-
- Full width
|
|
481
|
-
- Similar to side style but without sidebar-specific borders
|
|
482
|
-
- Designed for hierarchical navigation
|
|
483
|
-
|
|
484
|
-
### Button Types
|
|
485
|
-
|
|
486
|
-
#### Button Mode
|
|
487
|
-
- Standard padding and text display
|
|
488
|
-
- Suitable for text labels
|
|
489
|
-
|
|
490
|
-
#### Icon Mode
|
|
491
|
-
- Square dimensions (28px × 28px)
|
|
492
|
-
- No padding
|
|
493
|
-
- Centered icon display
|
|
494
|
-
|
|
495
|
-
## TypeScript Types
|
|
496
|
-
|
|
497
|
-
```typescript
|
|
498
|
-
interface TabFormItemProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
499
|
-
componentType: 'top' | 'side' | 'tree'
|
|
500
|
-
active?: boolean
|
|
501
|
-
buttonType?: 'icon' | 'button'
|
|
502
|
-
theme?: 'dark' | 'light' | 'default'
|
|
503
|
-
asChild?: boolean
|
|
504
|
-
as?: React.ElementType
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
type FormBarItemStylesProps = {
|
|
508
|
-
componentType?: 'top' | 'side' | 'tree'
|
|
509
|
-
active?: boolean
|
|
510
|
-
buttonType?: 'icon' | 'button'
|
|
511
|
-
}
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
## Common Patterns
|
|
515
|
-
|
|
516
|
-
### Tab Groups
|
|
517
|
-
|
|
518
|
-
```tsx
|
|
519
|
-
function TabGroup({ tabs, value, onChange, componentType = 'top' }) {
|
|
520
|
-
return (
|
|
521
|
-
<div className="flex gap-2">
|
|
522
|
-
{tabs.map(tab => (
|
|
523
|
-
<TabFormItem
|
|
524
|
-
key={tab.value}
|
|
525
|
-
componentType={componentType}
|
|
526
|
-
active={value === tab.value}
|
|
527
|
-
onClick={() => onChange(tab.value)}
|
|
528
|
-
disabled={tab.disabled}
|
|
529
|
-
>
|
|
530
|
-
{tab.icon && <i className={`${tab.icon} mr-2`}></i>}
|
|
531
|
-
{tab.label}
|
|
532
|
-
{tab.badge && (
|
|
533
|
-
<span className="ml-2 px-1.5 py-0.5 text-xs bg-background-presentation-action-primary-hover text-white rounded">
|
|
534
|
-
{tab.badge}
|
|
535
|
-
</span>
|
|
536
|
-
)}
|
|
537
|
-
</TabFormItem>
|
|
538
|
-
))}
|
|
539
|
-
</div>
|
|
540
|
-
)
|
|
541
|
-
}
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
### Scrollable Tabs
|
|
545
|
-
|
|
546
|
-
```tsx
|
|
547
|
-
function ScrollableTabs({ tabs, value, onChange }) {
|
|
548
|
-
const scrollRef = useRef<HTMLDivElement>(null)
|
|
549
|
-
|
|
550
|
-
return (
|
|
551
|
-
<div className="relative">
|
|
552
|
-
<div
|
|
553
|
-
ref={scrollRef}
|
|
554
|
-
className="flex gap-2 overflow-x-auto scrollbar-hide"
|
|
555
|
-
>
|
|
556
|
-
{tabs.map(tab => (
|
|
557
|
-
<TabFormItem
|
|
558
|
-
key={tab.id}
|
|
559
|
-
componentType="top"
|
|
560
|
-
active={value === tab.id}
|
|
561
|
-
onClick={() => onChange(tab.id)}
|
|
562
|
-
className="flex-shrink-0"
|
|
563
|
-
>
|
|
564
|
-
{tab.label}
|
|
565
|
-
</TabFormItem>
|
|
566
|
-
))}
|
|
567
|
-
</div>
|
|
568
|
-
|
|
569
|
-
{/* Scroll indicators */}
|
|
570
|
-
<div className="absolute left-0 top-0 bottom-0 w-8 bg-gradient-to-r from-white to-transparent pointer-events-none" />
|
|
571
|
-
<div className="absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-white to-transparent pointer-events-none" />
|
|
572
|
-
</div>
|
|
573
|
-
)
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
## Testing
|
|
578
|
-
|
|
579
|
-
```typescript
|
|
580
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
581
|
-
import TabFormItem from '@/components/TabFormItem'
|
|
582
|
-
|
|
583
|
-
describe('TabFormItem', () => {
|
|
584
|
-
it('renders with correct component type', () => {
|
|
585
|
-
render(
|
|
586
|
-
<TabFormItem componentType="top">
|
|
587
|
-
Test Tab
|
|
588
|
-
</TabFormItem>
|
|
589
|
-
)
|
|
590
|
-
const tab = screen.getByText('Test Tab')
|
|
591
|
-
expect(tab).toHaveClass('h-[28px]', 'px-[24px]')
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
it('applies active state correctly', () => {
|
|
595
|
-
render(
|
|
596
|
-
<TabFormItem componentType="side" active>
|
|
597
|
-
Active Tab
|
|
598
|
-
</TabFormItem>
|
|
599
|
-
)
|
|
600
|
-
const tab = screen.getByText('Active Tab')
|
|
601
|
-
expect(tab).toHaveClass('bg-background-presentation-tab-sidebar-selected')
|
|
602
|
-
})
|
|
603
|
-
|
|
604
|
-
it('handles click events', () => {
|
|
605
|
-
const handleClick = jest.fn()
|
|
606
|
-
render(
|
|
607
|
-
<TabFormItem componentType="top" onClick={handleClick}>
|
|
608
|
-
Clickable Tab
|
|
609
|
-
</TabFormItem>
|
|
610
|
-
)
|
|
611
|
-
|
|
612
|
-
fireEvent.click(screen.getByText('Clickable Tab'))
|
|
613
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
614
|
-
})
|
|
615
|
-
|
|
616
|
-
it('renders as icon button', () => {
|
|
617
|
-
render(
|
|
618
|
-
<TabFormItem componentType="top" buttonType="icon">
|
|
619
|
-
<i className="ri-home-line"></i>
|
|
620
|
-
</TabFormItem>
|
|
621
|
-
)
|
|
622
|
-
const tab = screen.getByRole('button')
|
|
623
|
-
expect(tab).toHaveClass('h-[28px]', 'w-[28px]')
|
|
624
|
-
})
|
|
625
|
-
|
|
626
|
-
it('supports polymorphic rendering', () => {
|
|
627
|
-
render(
|
|
628
|
-
<TabFormItem componentType="top" as="a" href="/test">
|
|
629
|
-
Link Tab
|
|
630
|
-
</TabFormItem>
|
|
631
|
-
)
|
|
632
|
-
const link = screen.getByText('Link Tab')
|
|
633
|
-
expect(link.tagName).toBe('A')
|
|
634
|
-
expect(link).toHaveAttribute('href', '/test')
|
|
635
|
-
})
|
|
636
|
-
})
|
|
637
|
-
```
|
|
638
|
-
|
|
639
|
-
## Accessibility
|
|
640
|
-
|
|
641
|
-
- **Semantic HTML**: Uses button element by default for proper keyboard interaction
|
|
642
|
-
- **Keyboard Navigation**: Full keyboard support with Tab and Enter/Space keys
|
|
643
|
-
- **ARIA Attributes**: Supports aria-label, aria-selected, and other ARIA properties
|
|
644
|
-
- **Focus Management**: Clear focus indicators and proper focus order
|
|
645
|
-
- **Screen Reader Support**: Announces active state and tab purpose
|
|
646
|
-
- **Disabled State**: Properly communicates disabled state to assistive technologies
|
|
647
|
-
|
|
648
|
-
### ARIA Example
|
|
649
|
-
|
|
650
|
-
```tsx
|
|
651
|
-
<div role="tablist" aria-orientation="horizontal">
|
|
652
|
-
{tabs.map((tab, index) => (
|
|
653
|
-
<TabFormItem
|
|
654
|
-
key={tab.id}
|
|
655
|
-
componentType="top"
|
|
656
|
-
role="tab"
|
|
657
|
-
aria-selected={activeTab === tab.id}
|
|
658
|
-
aria-controls={`tabpanel-${tab.id}`}
|
|
659
|
-
tabIndex={activeTab === tab.id ? 0 : -1}
|
|
660
|
-
active={activeTab === tab.id}
|
|
661
|
-
onClick={() => setActiveTab(tab.id)}
|
|
662
|
-
>
|
|
663
|
-
{tab.label}
|
|
664
|
-
</TabFormItem>
|
|
665
|
-
))}
|
|
666
|
-
</div>
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
## Performance
|
|
670
|
-
|
|
671
|
-
- **Optimized Animations**: CSS transitions for smooth hover and active states
|
|
672
|
-
- **Minimal Re-renders**: Use React.memo for large tab lists
|
|
673
|
-
- **Event Delegation**: Efficient event handling for multiple tabs
|
|
674
|
-
- **Class Generation**: CVA for optimized className generation
|
|
675
|
-
|
|
676
|
-
### Performance Tips
|
|
677
|
-
|
|
678
|
-
1. **Memoize Tab Lists**:
|
|
679
|
-
```tsx
|
|
680
|
-
const MemoizedTabFormItem = React.memo(TabFormItem)
|
|
681
|
-
|
|
682
|
-
const tabs = useMemo(() =>
|
|
683
|
-
data.map(item => ({
|
|
684
|
-
id: item.id,
|
|
685
|
-
label: item.label,
|
|
686
|
-
icon: item.icon
|
|
687
|
-
})),
|
|
688
|
-
[data]
|
|
689
|
-
)
|
|
690
|
-
```
|
|
691
|
-
|
|
692
|
-
2. **Virtualize Long Lists**:
|
|
693
|
-
```tsx
|
|
694
|
-
import { Virtuoso } from 'react-virtuoso'
|
|
695
|
-
|
|
696
|
-
function VirtualizedSidebar({ items, activeId, onSelect }) {
|
|
697
|
-
return (
|
|
698
|
-
<Virtuoso
|
|
699
|
-
data={items}
|
|
700
|
-
itemContent={(index, item) => (
|
|
701
|
-
<TabFormItem
|
|
702
|
-
componentType="side"
|
|
703
|
-
active={activeId === item.id}
|
|
704
|
-
onClick={() => onSelect(item.id)}
|
|
705
|
-
>
|
|
706
|
-
{item.label}
|
|
707
|
-
</TabFormItem>
|
|
708
|
-
)}
|
|
709
|
-
/>
|
|
710
|
-
)
|
|
711
|
-
}
|
|
712
|
-
```
|
|
713
|
-
|
|
714
|
-
## Migration Guide
|
|
715
|
-
|
|
716
|
-
### From Custom Tab Components
|
|
717
|
-
|
|
718
|
-
```tsx
|
|
719
|
-
// Before: Custom tab implementation
|
|
720
|
-
<button
|
|
721
|
-
className={`px-4 py-2 ${active ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}
|
|
722
|
-
onClick={onClick}
|
|
723
|
-
>
|
|
724
|
-
{label}
|
|
725
|
-
</button>
|
|
726
|
-
|
|
727
|
-
// After: Using TabFormItem
|
|
728
|
-
<TabFormItem
|
|
729
|
-
componentType="top"
|
|
730
|
-
active={active}
|
|
731
|
-
onClick={onClick}
|
|
732
|
-
>
|
|
733
|
-
{label}
|
|
734
|
-
</TabFormItem>
|
|
735
|
-
```
|
|
736
|
-
|
|
737
|
-
### From Other Tab Libraries
|
|
738
|
-
|
|
739
|
-
```tsx
|
|
740
|
-
// Before: Material-UI Tabs
|
|
741
|
-
<Tabs value={value} onChange={handleChange}>
|
|
742
|
-
<Tab label="Item One" />
|
|
743
|
-
<Tab label="Item Two" />
|
|
744
|
-
</Tabs>
|
|
745
|
-
|
|
746
|
-
// After: TabFormItem
|
|
747
|
-
<div className="flex gap-2">
|
|
748
|
-
<TabFormItem
|
|
749
|
-
componentType="top"
|
|
750
|
-
active={value === 0}
|
|
751
|
-
onClick={() => handleChange(0)}
|
|
752
|
-
>
|
|
753
|
-
Item One
|
|
754
|
-
</TabFormItem>
|
|
755
|
-
<TabFormItem
|
|
756
|
-
componentType="top"
|
|
757
|
-
active={value === 1}
|
|
758
|
-
onClick={() => handleChange(1)}
|
|
759
|
-
>
|
|
760
|
-
Item Two
|
|
761
|
-
</TabFormItem>
|
|
762
|
-
</div>
|
|
763
|
-
```
|
|
764
|
-
|
|
765
|
-
## Best Practices
|
|
766
|
-
|
|
767
|
-
1. **Use Appropriate Component Type**: Choose the right type (top/side/tree) based on your navigation hierarchy
|
|
768
|
-
2. **Maintain Active State**: Always track and display the active tab for user orientation
|
|
769
|
-
3. **Provide Visual Feedback**: Use hover and focus states to indicate interactivity
|
|
770
|
-
4. **Consider Mobile**: Use icon-only mode or horizontal scrolling for mobile layouts
|
|
771
|
-
5. **Group Related Tabs**: Organize tabs into logical groups for better user experience
|
|
772
|
-
6. **Limit Tab Count**: Consider using dropdown menus or pagination for many tabs
|
|
773
|
-
7. **Accessible Labels**: Always provide descriptive labels or aria-labels for icon-only tabs
|