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,872 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: LoginButton
|
|
3
|
-
description: Full-width authentication button designed for login forms and auth flows.
|
|
4
|
-
component: true
|
|
5
|
-
group: Buttons & Actions
|
|
6
|
-
keywords: [button, login, auth, authentication, sign-in, full-width]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# LoginButton
|
|
10
|
-
|
|
11
|
-
A specialized full-width button component designed specifically for authentication flows. Features loading states, two visual variants, and optimized sizing for login forms.
|
|
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 LoginButton
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
`add` also copies any components, hooks, and utilities that `LoginButton` depends on.
|
|
24
|
-
|
|
25
|
-
## Imports
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
import { LoginButton } from '@/components/LoginButton'
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Basic Usage
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import { LoginButton } from '@/components/LoginButton'
|
|
35
|
-
|
|
36
|
-
export function BasicLoginButton() {
|
|
37
|
-
return (
|
|
38
|
-
<LoginButton onClick={handleLogin}>
|
|
39
|
-
Sign In
|
|
40
|
-
</LoginButton>
|
|
41
|
-
)
|
|
42
|
-
}
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
## Examples
|
|
46
|
-
|
|
47
|
-
### Login Form
|
|
48
|
-
|
|
49
|
-
Complete login form implementation.
|
|
50
|
-
|
|
51
|
-
```tsx
|
|
52
|
-
export function LoginForm() {
|
|
53
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
54
|
-
const [email, setEmail] = useState('')
|
|
55
|
-
const [password, setPassword] = useState('')
|
|
56
|
-
|
|
57
|
-
const handleSubmit = async (e) => {
|
|
58
|
-
e.preventDefault()
|
|
59
|
-
setIsLoading(true)
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
await login({ email, password })
|
|
63
|
-
// Handle successful login
|
|
64
|
-
} catch (error) {
|
|
65
|
-
// Handle error
|
|
66
|
-
} finally {
|
|
67
|
-
setIsLoading(false)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return (
|
|
72
|
-
<form onSubmit={handleSubmit} className="w-full max-w-md space-y-4">
|
|
73
|
-
<div>
|
|
74
|
-
<label className="block text-sm font-medium mb-1">Email</label>
|
|
75
|
-
<input
|
|
76
|
-
type="email"
|
|
77
|
-
value={email}
|
|
78
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
79
|
-
className="w-full px-3 py-2 border rounded-lg"
|
|
80
|
-
required
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
83
|
-
|
|
84
|
-
<div>
|
|
85
|
-
<label className="block text-sm font-medium mb-1">Password</label>
|
|
86
|
-
<input
|
|
87
|
-
type="password"
|
|
88
|
-
value={password}
|
|
89
|
-
onChange={(e) => setPassword(e.target.value)}
|
|
90
|
-
className="w-full px-3 py-2 border rounded-lg"
|
|
91
|
-
required
|
|
92
|
-
/>
|
|
93
|
-
</div>
|
|
94
|
-
|
|
95
|
-
<LoginButton
|
|
96
|
-
type="submit"
|
|
97
|
-
isLoading={isLoading}
|
|
98
|
-
disabled={isLoading || !email || !password}
|
|
99
|
-
>
|
|
100
|
-
{isLoading ? 'Signing in...' : 'Sign In'}
|
|
101
|
-
</LoginButton>
|
|
102
|
-
</form>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
### Variants
|
|
108
|
-
|
|
109
|
-
LoginButton offers two variants: default and noBg.
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
export function LoginButtonVariants() {
|
|
113
|
-
return (
|
|
114
|
-
<div className="w-full max-w-md space-y-4">
|
|
115
|
-
<LoginButton variant="default">
|
|
116
|
-
Sign In with Email
|
|
117
|
-
</LoginButton>
|
|
118
|
-
|
|
119
|
-
<LoginButton variant="noBg">
|
|
120
|
-
Sign In with Email
|
|
121
|
-
</LoginButton>
|
|
122
|
-
</div>
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
```
|
|
126
|
-
|
|
127
|
-
### Social Login Buttons
|
|
128
|
-
|
|
129
|
-
OAuth and social authentication buttons.
|
|
130
|
-
|
|
131
|
-
```tsx
|
|
132
|
-
export function SocialLoginButtons() {
|
|
133
|
-
const [loadingProvider, setLoadingProvider] = useState(null)
|
|
134
|
-
|
|
135
|
-
const handleSocialLogin = async (provider) => {
|
|
136
|
-
setLoadingProvider(provider)
|
|
137
|
-
try {
|
|
138
|
-
await signInWithProvider(provider)
|
|
139
|
-
} catch (error) {
|
|
140
|
-
console.error(`${provider} login failed:`, error)
|
|
141
|
-
} finally {
|
|
142
|
-
setLoadingProvider(null)
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return (
|
|
147
|
-
<div className="w-full max-w-md space-y-3">
|
|
148
|
-
<LoginButton
|
|
149
|
-
onClick={() => handleSocialLogin('google')}
|
|
150
|
-
isLoading={loadingProvider === 'google'}
|
|
151
|
-
disabled={loadingProvider !== null}
|
|
152
|
-
>
|
|
153
|
-
<i className="ri-google-fill mr-2"></i>
|
|
154
|
-
Continue with Google
|
|
155
|
-
</LoginButton>
|
|
156
|
-
|
|
157
|
-
<LoginButton
|
|
158
|
-
onClick={() => handleSocialLogin('github')}
|
|
159
|
-
isLoading={loadingProvider === 'github'}
|
|
160
|
-
disabled={loadingProvider !== null}
|
|
161
|
-
variant="noBg"
|
|
162
|
-
>
|
|
163
|
-
<i className="ri-github-fill mr-2"></i>
|
|
164
|
-
Continue with GitHub
|
|
165
|
-
</LoginButton>
|
|
166
|
-
|
|
167
|
-
<LoginButton
|
|
168
|
-
onClick={() => handleSocialLogin('microsoft')}
|
|
169
|
-
isLoading={loadingProvider === 'microsoft'}
|
|
170
|
-
disabled={loadingProvider !== null}
|
|
171
|
-
variant="noBg"
|
|
172
|
-
>
|
|
173
|
-
<i className="ri-microsoft-fill mr-2"></i>
|
|
174
|
-
Continue with Microsoft
|
|
175
|
-
</LoginButton>
|
|
176
|
-
</div>
|
|
177
|
-
)
|
|
178
|
-
}
|
|
179
|
-
```
|
|
180
|
-
|
|
181
|
-
### Two-Factor Authentication
|
|
182
|
-
|
|
183
|
-
Login flow with 2FA support.
|
|
184
|
-
|
|
185
|
-
```tsx
|
|
186
|
-
export function TwoFactorLogin() {
|
|
187
|
-
const [step, setStep] = useState('credentials') // 'credentials' | '2fa'
|
|
188
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
189
|
-
const [code, setCode] = useState('')
|
|
190
|
-
|
|
191
|
-
const handleInitialLogin = async () => {
|
|
192
|
-
setIsLoading(true)
|
|
193
|
-
try {
|
|
194
|
-
const requires2FA = await attemptLogin()
|
|
195
|
-
if (requires2FA) {
|
|
196
|
-
setStep('2fa')
|
|
197
|
-
}
|
|
198
|
-
} finally {
|
|
199
|
-
setIsLoading(false)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const handleVerify2FA = async () => {
|
|
204
|
-
setIsLoading(true)
|
|
205
|
-
try {
|
|
206
|
-
await verify2FA(code)
|
|
207
|
-
// Handle successful verification
|
|
208
|
-
} finally {
|
|
209
|
-
setIsLoading(false)
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (step === '2fa') {
|
|
214
|
-
return (
|
|
215
|
-
<div className="w-full max-w-md space-y-4">
|
|
216
|
-
<h3 className="text-lg font-semibold">Enter verification code</h3>
|
|
217
|
-
<p className="text-sm text-gray-600">
|
|
218
|
-
We've sent a code to your registered device
|
|
219
|
-
</p>
|
|
220
|
-
|
|
221
|
-
<input
|
|
222
|
-
type="text"
|
|
223
|
-
value={code}
|
|
224
|
-
onChange={(e) => setCode(e.target.value)}
|
|
225
|
-
placeholder="000000"
|
|
226
|
-
maxLength={6}
|
|
227
|
-
className="w-full px-3 py-2 border rounded-lg text-center text-lg tracking-widest"
|
|
228
|
-
/>
|
|
229
|
-
|
|
230
|
-
<LoginButton
|
|
231
|
-
onClick={handleVerify2FA}
|
|
232
|
-
isLoading={isLoading}
|
|
233
|
-
disabled={isLoading || code.length !== 6}
|
|
234
|
-
>
|
|
235
|
-
{isLoading ? 'Verifying...' : 'Verify Code'}
|
|
236
|
-
</LoginButton>
|
|
237
|
-
|
|
238
|
-
<LoginButton
|
|
239
|
-
variant="noBg"
|
|
240
|
-
onClick={() => setStep('credentials')}
|
|
241
|
-
disabled={isLoading}
|
|
242
|
-
>
|
|
243
|
-
Back to login
|
|
244
|
-
</LoginButton>
|
|
245
|
-
</div>
|
|
246
|
-
)
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
return (
|
|
250
|
-
// Initial login form
|
|
251
|
-
<LoginButton
|
|
252
|
-
onClick={handleInitialLogin}
|
|
253
|
-
isLoading={isLoading}
|
|
254
|
-
>
|
|
255
|
-
Sign In
|
|
256
|
-
</LoginButton>
|
|
257
|
-
)
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
### Sign Up Flow
|
|
262
|
-
|
|
263
|
-
Registration form with LoginButton.
|
|
264
|
-
|
|
265
|
-
```tsx
|
|
266
|
-
export function SignUpForm() {
|
|
267
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
268
|
-
const [agreed, setAgreed] = useState(false)
|
|
269
|
-
|
|
270
|
-
const handleSignUp = async (e) => {
|
|
271
|
-
e.preventDefault()
|
|
272
|
-
if (!agreed) return
|
|
273
|
-
|
|
274
|
-
setIsLoading(true)
|
|
275
|
-
try {
|
|
276
|
-
await createAccount(formData)
|
|
277
|
-
// Handle successful signup
|
|
278
|
-
} finally {
|
|
279
|
-
setIsLoading(false)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return (
|
|
284
|
-
<form onSubmit={handleSignUp} className="w-full max-w-md space-y-4">
|
|
285
|
-
{/* Form fields */}
|
|
286
|
-
|
|
287
|
-
<div className="flex items-start gap-2">
|
|
288
|
-
<input
|
|
289
|
-
type="checkbox"
|
|
290
|
-
id="terms"
|
|
291
|
-
checked={agreed}
|
|
292
|
-
onChange={(e) => setAgreed(e.target.checked)}
|
|
293
|
-
className="mt-1"
|
|
294
|
-
/>
|
|
295
|
-
<label htmlFor="terms" className="text-sm">
|
|
296
|
-
I agree to the Terms of Service and Privacy Policy
|
|
297
|
-
</label>
|
|
298
|
-
</div>
|
|
299
|
-
|
|
300
|
-
<LoginButton
|
|
301
|
-
type="submit"
|
|
302
|
-
isLoading={isLoading}
|
|
303
|
-
disabled={isLoading || !agreed}
|
|
304
|
-
>
|
|
305
|
-
{isLoading ? 'Creating Account...' : 'Create Account'}
|
|
306
|
-
</LoginButton>
|
|
307
|
-
|
|
308
|
-
<div className="relative">
|
|
309
|
-
<div className="absolute inset-0 flex items-center">
|
|
310
|
-
<div className="w-full border-t"></div>
|
|
311
|
-
</div>
|
|
312
|
-
<div className="relative flex justify-center text-sm">
|
|
313
|
-
<span className="px-2 bg-white text-gray-500">Or</span>
|
|
314
|
-
</div>
|
|
315
|
-
</div>
|
|
316
|
-
|
|
317
|
-
<LoginButton variant="noBg">
|
|
318
|
-
Sign In to Existing Account
|
|
319
|
-
</LoginButton>
|
|
320
|
-
</form>
|
|
321
|
-
)
|
|
322
|
-
}
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### Password Reset
|
|
326
|
-
|
|
327
|
-
Password reset flow implementation.
|
|
328
|
-
|
|
329
|
-
```tsx
|
|
330
|
-
export function PasswordResetFlow() {
|
|
331
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
332
|
-
const [email, setEmail] = useState('')
|
|
333
|
-
const [sent, setSent] = useState(false)
|
|
334
|
-
|
|
335
|
-
const handleReset = async () => {
|
|
336
|
-
setIsLoading(true)
|
|
337
|
-
try {
|
|
338
|
-
await sendPasswordResetEmail(email)
|
|
339
|
-
setSent(true)
|
|
340
|
-
} finally {
|
|
341
|
-
setIsLoading(false)
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if (sent) {
|
|
346
|
-
return (
|
|
347
|
-
<div className="w-full max-w-md space-y-4 text-center">
|
|
348
|
-
<i className="ri-mail-check-line text-4xl text-green-500"></i>
|
|
349
|
-
<h3 className="text-lg font-semibold">Check your email</h3>
|
|
350
|
-
<p className="text-sm text-gray-600">
|
|
351
|
-
We've sent password reset instructions to {email}
|
|
352
|
-
</p>
|
|
353
|
-
|
|
354
|
-
<LoginButton onClick={() => setSent(false)}>
|
|
355
|
-
Send Again
|
|
356
|
-
</LoginButton>
|
|
357
|
-
|
|
358
|
-
<LoginButton variant="noBg" onClick={() => window.location.href = '/login'}>
|
|
359
|
-
Back to Login
|
|
360
|
-
</LoginButton>
|
|
361
|
-
</div>
|
|
362
|
-
)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
return (
|
|
366
|
-
<div className="w-full max-w-md space-y-4">
|
|
367
|
-
<h3 className="text-lg font-semibold">Reset your password</h3>
|
|
368
|
-
<p className="text-sm text-gray-600">
|
|
369
|
-
Enter your email and we'll send you instructions
|
|
370
|
-
</p>
|
|
371
|
-
|
|
372
|
-
<input
|
|
373
|
-
type="email"
|
|
374
|
-
value={email}
|
|
375
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
376
|
-
placeholder="Enter your email"
|
|
377
|
-
className="w-full px-3 py-2 border rounded-lg"
|
|
378
|
-
/>
|
|
379
|
-
|
|
380
|
-
<LoginButton
|
|
381
|
-
onClick={handleReset}
|
|
382
|
-
isLoading={isLoading}
|
|
383
|
-
disabled={isLoading || !email}
|
|
384
|
-
>
|
|
385
|
-
{isLoading ? 'Sending...' : 'Send Reset Link'}
|
|
386
|
-
</LoginButton>
|
|
387
|
-
|
|
388
|
-
<LoginButton variant="noBg" onClick={() => window.location.href = '/login'}>
|
|
389
|
-
Back to Login
|
|
390
|
-
</LoginButton>
|
|
391
|
-
</div>
|
|
392
|
-
)
|
|
393
|
-
}
|
|
394
|
-
```
|
|
395
|
-
|
|
396
|
-
### Magic Link Authentication
|
|
397
|
-
|
|
398
|
-
Passwordless authentication flow.
|
|
399
|
-
|
|
400
|
-
```tsx
|
|
401
|
-
export function MagicLinkLogin() {
|
|
402
|
-
const [email, setEmail] = useState('')
|
|
403
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
404
|
-
const [linkSent, setLinkSent] = useState(false)
|
|
405
|
-
|
|
406
|
-
const handleSendMagicLink = async () => {
|
|
407
|
-
setIsLoading(true)
|
|
408
|
-
try {
|
|
409
|
-
await sendMagicLink(email)
|
|
410
|
-
setLinkSent(true)
|
|
411
|
-
} finally {
|
|
412
|
-
setIsLoading(false)
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
if (linkSent) {
|
|
417
|
-
return (
|
|
418
|
-
<div className="w-full max-w-md space-y-4">
|
|
419
|
-
<div className="text-center">
|
|
420
|
-
<i className="ri-mail-send-line text-4xl text-blue-500 mb-4"></i>
|
|
421
|
-
<h3 className="text-lg font-semibold">Check your inbox!</h3>
|
|
422
|
-
<p className="text-sm text-gray-600 mt-2">
|
|
423
|
-
We sent a magic link to {email}
|
|
424
|
-
</p>
|
|
425
|
-
</div>
|
|
426
|
-
|
|
427
|
-
<LoginButton
|
|
428
|
-
variant="noBg"
|
|
429
|
-
onClick={() => setLinkSent(false)}
|
|
430
|
-
>
|
|
431
|
-
Use a different email
|
|
432
|
-
</LoginButton>
|
|
433
|
-
</div>
|
|
434
|
-
)
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
return (
|
|
438
|
-
<div className="w-full max-w-md space-y-4">
|
|
439
|
-
<h3 className="text-lg font-semibold">Sign in with magic link</h3>
|
|
440
|
-
<p className="text-sm text-gray-600">
|
|
441
|
-
We'll email you a secure link to sign in instantly
|
|
442
|
-
</p>
|
|
443
|
-
|
|
444
|
-
<input
|
|
445
|
-
type="email"
|
|
446
|
-
value={email}
|
|
447
|
-
onChange={(e) => setEmail(e.target.value)}
|
|
448
|
-
placeholder="Enter your email"
|
|
449
|
-
className="w-full px-3 py-2 border rounded-lg"
|
|
450
|
-
required
|
|
451
|
-
/>
|
|
452
|
-
|
|
453
|
-
<LoginButton
|
|
454
|
-
onClick={handleSendMagicLink}
|
|
455
|
-
isLoading={isLoading}
|
|
456
|
-
disabled={isLoading || !email}
|
|
457
|
-
>
|
|
458
|
-
{isLoading ? 'Sending magic link...' : 'Send Magic Link'}
|
|
459
|
-
</LoginButton>
|
|
460
|
-
</div>
|
|
461
|
-
)
|
|
462
|
-
}
|
|
463
|
-
```
|
|
464
|
-
|
|
465
|
-
### Session Management
|
|
466
|
-
|
|
467
|
-
Login with session timeout warning.
|
|
468
|
-
|
|
469
|
-
```tsx
|
|
470
|
-
export function SessionLogin() {
|
|
471
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
472
|
-
const [rememberMe, setRememberMe] = useState(false)
|
|
473
|
-
|
|
474
|
-
const handleLogin = async () => {
|
|
475
|
-
setIsLoading(true)
|
|
476
|
-
try {
|
|
477
|
-
await login({
|
|
478
|
-
sessionDuration: rememberMe ? '30d' : '24h'
|
|
479
|
-
})
|
|
480
|
-
} finally {
|
|
481
|
-
setIsLoading(false)
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return (
|
|
486
|
-
<div className="w-full max-w-md space-y-4">
|
|
487
|
-
{/* Login fields */}
|
|
488
|
-
|
|
489
|
-
<div className="flex items-center justify-between">
|
|
490
|
-
<label className="flex items-center gap-2">
|
|
491
|
-
<input
|
|
492
|
-
type="checkbox"
|
|
493
|
-
checked={rememberMe}
|
|
494
|
-
onChange={(e) => setRememberMe(e.target.checked)}
|
|
495
|
-
/>
|
|
496
|
-
<span className="text-sm">Remember me for 30 days</span>
|
|
497
|
-
</label>
|
|
498
|
-
|
|
499
|
-
<a href="/forgot" className="text-sm text-blue-500 hover:underline">
|
|
500
|
-
Forgot password?
|
|
501
|
-
</a>
|
|
502
|
-
</div>
|
|
503
|
-
|
|
504
|
-
<LoginButton
|
|
505
|
-
onClick={handleLogin}
|
|
506
|
-
isLoading={isLoading}
|
|
507
|
-
>
|
|
508
|
-
{isLoading ? 'Signing in...' : 'Sign In'}
|
|
509
|
-
</LoginButton>
|
|
510
|
-
|
|
511
|
-
<p className="text-xs text-center text-gray-500">
|
|
512
|
-
{rememberMe
|
|
513
|
-
? 'You'll stay signed in for 30 days'
|
|
514
|
-
: 'Session expires after 24 hours of inactivity'}
|
|
515
|
-
</p>
|
|
516
|
-
</div>
|
|
517
|
-
)
|
|
518
|
-
}
|
|
519
|
-
```
|
|
520
|
-
|
|
521
|
-
### Multi-Step Authentication
|
|
522
|
-
|
|
523
|
-
Complex authentication with multiple steps.
|
|
524
|
-
|
|
525
|
-
```tsx
|
|
526
|
-
export function MultiStepAuth() {
|
|
527
|
-
const [step, setStep] = useState(1)
|
|
528
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
529
|
-
|
|
530
|
-
const steps = [
|
|
531
|
-
{ title: 'Enter Email', action: 'Continue' },
|
|
532
|
-
{ title: 'Enter Password', action: 'Sign In' },
|
|
533
|
-
{ title: 'Verify Identity', action: 'Verify' },
|
|
534
|
-
]
|
|
535
|
-
|
|
536
|
-
const handleNext = async () => {
|
|
537
|
-
setIsLoading(true)
|
|
538
|
-
try {
|
|
539
|
-
// Process current step
|
|
540
|
-
if (step < steps.length) {
|
|
541
|
-
setStep(step + 1)
|
|
542
|
-
} else {
|
|
543
|
-
// Complete authentication
|
|
544
|
-
}
|
|
545
|
-
} finally {
|
|
546
|
-
setIsLoading(false)
|
|
547
|
-
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
return (
|
|
551
|
-
<div className="w-full max-w-md space-y-4">
|
|
552
|
-
{/* Step indicator */}
|
|
553
|
-
<div className="flex justify-between mb-6">
|
|
554
|
-
{steps.map((_, index) => (
|
|
555
|
-
<div
|
|
556
|
-
key={index}
|
|
557
|
-
className={cn(
|
|
558
|
-
"h-2 flex-1 mx-1 rounded-full",
|
|
559
|
-
index < step
|
|
560
|
-
? "bg-blue-500"
|
|
561
|
-
: "bg-gray-200"
|
|
562
|
-
)}
|
|
563
|
-
/>
|
|
564
|
-
))}
|
|
565
|
-
</div>
|
|
566
|
-
|
|
567
|
-
<h3 className="text-lg font-semibold">
|
|
568
|
-
Step {step}: {steps[step - 1].title}
|
|
569
|
-
</h3>
|
|
570
|
-
|
|
571
|
-
{/* Step-specific content */}
|
|
572
|
-
|
|
573
|
-
<LoginButton
|
|
574
|
-
onClick={handleNext}
|
|
575
|
-
isLoading={isLoading}
|
|
576
|
-
>
|
|
577
|
-
{isLoading ? 'Processing...' : steps[step - 1].action}
|
|
578
|
-
</LoginButton>
|
|
579
|
-
|
|
580
|
-
{step > 1 && (
|
|
581
|
-
<LoginButton
|
|
582
|
-
variant="noBg"
|
|
583
|
-
onClick={() => setStep(step - 1)}
|
|
584
|
-
disabled={isLoading}
|
|
585
|
-
>
|
|
586
|
-
Back
|
|
587
|
-
</LoginButton>
|
|
588
|
-
)}
|
|
589
|
-
</div>
|
|
590
|
-
)
|
|
591
|
-
}
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
## API Reference
|
|
595
|
-
|
|
596
|
-
### LoginButton Props
|
|
597
|
-
|
|
598
|
-
| Prop | Type | Default | Description |
|
|
599
|
-
|------|------|---------|-------------|
|
|
600
|
-
| variant | `'default' \| 'noBg'` | `'default'` | Visual variant |
|
|
601
|
-
| isLoading | `boolean` | `false` | Loading state |
|
|
602
|
-
| theme | `Themes` | - | Theme override |
|
|
603
|
-
| className | `string` | - | Additional CSS classes |
|
|
604
|
-
| children | `ReactNode` | - | Button content |
|
|
605
|
-
| onClick | `() => void` | - | Click handler |
|
|
606
|
-
| type | `'button' \| 'submit' \| 'reset'` | `'button'` | Button type |
|
|
607
|
-
| disabled | `boolean` | `false` | Disabled state |
|
|
608
|
-
|
|
609
|
-
### Variants
|
|
610
|
-
|
|
611
|
-
| Variant | Description | Use Case |
|
|
612
|
-
|---------|-------------|----------|
|
|
613
|
-
| default | Solid background with border | Primary actions |
|
|
614
|
-
| noBg | Transparent background with border | Secondary actions |
|
|
615
|
-
|
|
616
|
-
## Styling
|
|
617
|
-
|
|
618
|
-
### Base Styles
|
|
619
|
-
|
|
620
|
-
- **Full Width**: Always spans 100% of container width
|
|
621
|
-
- **Fixed Height**: 42px for consistent form layouts
|
|
622
|
-
- **Rounded Corners**: 8px border radius
|
|
623
|
-
- **Typography**: body-large-regular for optimal readability
|
|
624
|
-
- **Transitions**: Smooth 250ms animations
|
|
625
|
-
|
|
626
|
-
### Variant Styles
|
|
627
|
-
|
|
628
|
-
#### Default Variant
|
|
629
|
-
```css
|
|
630
|
-
.default {
|
|
631
|
-
background: var(--background-system-body-primary);
|
|
632
|
-
color: var(--content-system-global-primary);
|
|
633
|
-
border: 1px solid var(--border-system-global-primary);
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
.default:hover {
|
|
637
|
-
border-color: #9748FF;
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
#### NoBg Variant
|
|
642
|
-
```css
|
|
643
|
-
.noBg {
|
|
644
|
-
background: transparent;
|
|
645
|
-
color: var(--content-system-global-primary);
|
|
646
|
-
border: 1px solid var(--border-system-global-primary);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
.noBg:hover {
|
|
650
|
-
border-color: #9748FF;
|
|
651
|
-
}
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
## TypeScript Types
|
|
655
|
-
|
|
656
|
-
```typescript
|
|
657
|
-
interface LoginButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
658
|
-
variant?: 'default' | 'noBg'
|
|
659
|
-
isLoading?: boolean
|
|
660
|
-
theme?: Themes
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
type Themes = 'light' | 'dark' | 'default'
|
|
664
|
-
```
|
|
665
|
-
|
|
666
|
-
## Common Patterns
|
|
667
|
-
|
|
668
|
-
### Auth Form Component
|
|
669
|
-
|
|
670
|
-
```tsx
|
|
671
|
-
function AuthForm({ mode = 'signin' }) {
|
|
672
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
673
|
-
|
|
674
|
-
const buttonText = {
|
|
675
|
-
signin: 'Sign In',
|
|
676
|
-
signup: 'Create Account',
|
|
677
|
-
reset: 'Reset Password',
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
return (
|
|
681
|
-
<form className="w-full max-w-md space-y-4">
|
|
682
|
-
{/* Form fields based on mode */}
|
|
683
|
-
|
|
684
|
-
<LoginButton
|
|
685
|
-
type="submit"
|
|
686
|
-
isLoading={isLoading}
|
|
687
|
-
>
|
|
688
|
-
{isLoading ? 'Processing...' : buttonText[mode]}
|
|
689
|
-
</LoginButton>
|
|
690
|
-
|
|
691
|
-
<LoginButton
|
|
692
|
-
variant="noBg"
|
|
693
|
-
type="button"
|
|
694
|
-
onClick={() => {/* Navigate to alternative */}}
|
|
695
|
-
>
|
|
696
|
-
{mode === 'signin' ? "Don't have an account?" : 'Already have an account?'}
|
|
697
|
-
</LoginButton>
|
|
698
|
-
</form>
|
|
699
|
-
)
|
|
700
|
-
}
|
|
701
|
-
```
|
|
702
|
-
|
|
703
|
-
### Error Handling
|
|
704
|
-
|
|
705
|
-
```tsx
|
|
706
|
-
function LoginWithError() {
|
|
707
|
-
const [error, setError] = useState(null)
|
|
708
|
-
const [isLoading, setIsLoading] = useState(false)
|
|
709
|
-
|
|
710
|
-
const handleLogin = async () => {
|
|
711
|
-
setError(null)
|
|
712
|
-
setIsLoading(true)
|
|
713
|
-
|
|
714
|
-
try {
|
|
715
|
-
await login()
|
|
716
|
-
} catch (err) {
|
|
717
|
-
setError(err.message)
|
|
718
|
-
} finally {
|
|
719
|
-
setIsLoading(false)
|
|
720
|
-
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
return (
|
|
724
|
-
<div className="w-full max-w-md space-y-4">
|
|
725
|
-
{error && (
|
|
726
|
-
<div className="p-3 bg-red-50 text-red-600 rounded-lg text-sm">
|
|
727
|
-
{error}
|
|
728
|
-
</div>
|
|
729
|
-
)}
|
|
730
|
-
|
|
731
|
-
<LoginButton
|
|
732
|
-
onClick={handleLogin}
|
|
733
|
-
isLoading={isLoading}
|
|
734
|
-
>
|
|
735
|
-
{isLoading ? 'Signing in...' : 'Sign In'}
|
|
736
|
-
</LoginButton>
|
|
737
|
-
</div>
|
|
738
|
-
)
|
|
739
|
-
}
|
|
740
|
-
```
|
|
741
|
-
|
|
742
|
-
## Testing
|
|
743
|
-
|
|
744
|
-
```typescript
|
|
745
|
-
import { render, screen, fireEvent } from '@testing-library/react'
|
|
746
|
-
import { LoginButton } from '@/components/LoginButton'
|
|
747
|
-
|
|
748
|
-
describe('LoginButton', () => {
|
|
749
|
-
it('renders with full width', () => {
|
|
750
|
-
render(<LoginButton>Sign In</LoginButton>)
|
|
751
|
-
const button = screen.getByText('Sign In')
|
|
752
|
-
expect(button).toHaveClass('w-full')
|
|
753
|
-
})
|
|
754
|
-
|
|
755
|
-
it('shows loading state', () => {
|
|
756
|
-
const { container } = render(
|
|
757
|
-
<LoginButton isLoading={true}>
|
|
758
|
-
Sign In
|
|
759
|
-
</LoginButton>
|
|
760
|
-
)
|
|
761
|
-
|
|
762
|
-
const loadingIcon = container.querySelector('.ri-loader-4-line')
|
|
763
|
-
expect(loadingIcon).toBeInTheDocument()
|
|
764
|
-
})
|
|
765
|
-
|
|
766
|
-
it('applies variant styles', () => {
|
|
767
|
-
render(<LoginButton variant="noBg">Sign In</LoginButton>)
|
|
768
|
-
const button = screen.getByText('Sign In')
|
|
769
|
-
expect(button).toHaveClass('bg-transparent')
|
|
770
|
-
})
|
|
771
|
-
|
|
772
|
-
it('handles click events', () => {
|
|
773
|
-
const handleClick = jest.fn()
|
|
774
|
-
render(
|
|
775
|
-
<LoginButton onClick={handleClick}>
|
|
776
|
-
Sign In
|
|
777
|
-
</LoginButton>
|
|
778
|
-
)
|
|
779
|
-
|
|
780
|
-
fireEvent.click(screen.getByText('Sign In'))
|
|
781
|
-
expect(handleClick).toHaveBeenCalledTimes(1)
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
it('disables when loading', () => {
|
|
785
|
-
render(
|
|
786
|
-
<LoginButton isLoading={true}>
|
|
787
|
-
Sign In
|
|
788
|
-
</LoginButton>
|
|
789
|
-
)
|
|
790
|
-
|
|
791
|
-
const button = screen.getByRole('button')
|
|
792
|
-
// Loading state shows loading icon, not disabled attribute
|
|
793
|
-
expect(button.querySelector('.animate-spin')).toBeInTheDocument()
|
|
794
|
-
})
|
|
795
|
-
})
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
## Accessibility
|
|
799
|
-
|
|
800
|
-
- **Full Width**: Ensures easy tap target on mobile devices
|
|
801
|
-
- **Loading States**: Clear visual and programmatic loading indicators
|
|
802
|
-
- **Keyboard Support**: Full keyboard navigation support
|
|
803
|
-
- **Focus Management**: Clear focus states for keyboard users
|
|
804
|
-
- **ARIA Support**: Proper ARIA attributes for screen readers
|
|
805
|
-
- **Contrast Ratios**: Meets WCAG AA standards
|
|
806
|
-
|
|
807
|
-
### Accessibility Example
|
|
808
|
-
|
|
809
|
-
```tsx
|
|
810
|
-
<LoginButton
|
|
811
|
-
aria-label="Sign in to your account"
|
|
812
|
-
aria-describedby="login-help"
|
|
813
|
-
isLoading={isLoading}
|
|
814
|
-
aria-busy={isLoading}
|
|
815
|
-
>
|
|
816
|
-
{isLoading ? 'Signing in...' : 'Sign In'}
|
|
817
|
-
</LoginButton>
|
|
818
|
-
<span id="login-help" className="sr-only">
|
|
819
|
-
Enter your email and password above, then click this button
|
|
820
|
-
</span>
|
|
821
|
-
```
|
|
822
|
-
|
|
823
|
-
## Performance
|
|
824
|
-
|
|
825
|
-
- **Optimized Animations**: CSS transitions for smooth hover effects
|
|
826
|
-
- **Loading Icon**: Lightweight SVG animation
|
|
827
|
-
- **Minimal Re-renders**: Loading state managed efficiently
|
|
828
|
-
- **CSS Variables**: Theme changes without component re-renders
|
|
829
|
-
|
|
830
|
-
## Migration Guide
|
|
831
|
-
|
|
832
|
-
### From Custom Buttons
|
|
833
|
-
|
|
834
|
-
```tsx
|
|
835
|
-
// Before: Custom full-width button
|
|
836
|
-
<button className="w-full py-2 px-4 bg-blue-500 text-white rounded">
|
|
837
|
-
Sign In
|
|
838
|
-
</button>
|
|
839
|
-
|
|
840
|
-
// After: LoginButton
|
|
841
|
-
<LoginButton>
|
|
842
|
-
Sign In
|
|
843
|
-
</LoginButton>
|
|
844
|
-
```
|
|
845
|
-
|
|
846
|
-
### From Other Libraries
|
|
847
|
-
|
|
848
|
-
```tsx
|
|
849
|
-
// Before: Material-UI Button
|
|
850
|
-
<Button
|
|
851
|
-
fullWidth
|
|
852
|
-
variant="PrimeStyle"
|
|
853
|
-
loading={isLoading}
|
|
854
|
-
>
|
|
855
|
-
Sign In
|
|
856
|
-
</Button>
|
|
857
|
-
|
|
858
|
-
// After: LoginButton
|
|
859
|
-
<LoginButton isLoading={isLoading}>
|
|
860
|
-
Sign In
|
|
861
|
-
</LoginButton>
|
|
862
|
-
```
|
|
863
|
-
|
|
864
|
-
## Best Practices
|
|
865
|
-
|
|
866
|
-
1. **Always Full Width**: LoginButton is designed to span container width
|
|
867
|
-
2. **Loading Feedback**: Always show loading state during async operations
|
|
868
|
-
3. **Error Handling**: Display clear error messages above the button
|
|
869
|
-
4. **Disabled States**: Disable when form is invalid or processing
|
|
870
|
-
5. **Alternative Actions**: Use noBg variant for secondary auth options
|
|
871
|
-
6. **Mobile Optimization**: Ensure parent container has appropriate padding
|
|
872
|
-
7. **Form Integration**: Use type="submit" for form submissions
|