alope-ui 1.1.5 → 1.1.6
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 +3013 -4
- package/package.json +3 -3
- package/dist/components/Accordion/Accordion.d.ts +0 -18
- package/dist/components/Alert/Alert.d.ts +0 -34
- package/dist/components/Avatar/Avatar.d.ts +0 -38
- package/dist/components/Avatar/AvatarGroup.d.ts +0 -10
- package/dist/components/Badge/Badge.d.ts +0 -25
- package/dist/components/Breadcrumb/Breadcrumb.d.ts +0 -16
- package/dist/components/Button/Button.d.ts +0 -67
- package/dist/components/Card/Card.d.ts +0 -19
- package/dist/components/CheckboxInput/CheckboxInput.d.ts +0 -10
- package/dist/components/CheckboxInput/ListCheckboxInput.d.ts +0 -55
- package/dist/components/FileUpload/FIleUpload.d.ts +0 -14
- package/dist/components/Modal/Modal.d.ts +0 -13
- package/dist/components/Offcanvas/Offcanvas.d.ts +0 -14
- package/dist/components/Pagination/Pagination.d.ts +0 -22
- package/dist/components/RadioInput/RadioInput.d.ts +0 -55
- package/dist/components/SelectInput/SelectInput.d.ts +0 -14
- package/dist/components/Skeleton/Skeleton.d.ts +0 -10
- package/dist/components/Spinner/Spinner.d.ts +0 -23
- package/dist/components/Table/Table.d.ts +0 -34
- package/dist/components/Tabs/Tabs.d.ts +0 -57
- package/dist/components/TextInput/TextInput.d.ts +0 -6
- package/dist/components/Textarea/Textarea.d.ts +0 -8
- package/dist/components/Toast/Toast.d.ts +0 -28
- package/dist/components/Toast/ToastProvider.d.ts +0 -11
- package/dist/components/Toggle/Toggle.d.ts +0 -27
- package/dist/components/Tooltip/Tooltip.d.ts +0 -14
- package/dist/components/index.d.ts +0 -24
- package/dist/index.css +0 -1
- package/dist/main.cjs +0 -723
- package/dist/main.d.ts +0 -25
package/README.md
CHANGED
|
@@ -98,6 +98,9 @@ export default App;
|
|
|
98
98
|
- [🍞 Breadcrumb](#breadcrumb) - Navigation trail
|
|
99
99
|
- [📑 Tabs](#tabs) - Tabbed navigation interface
|
|
100
100
|
- [📄 Pagination](#pagination) - Page navigation controls
|
|
101
|
+
- [📱 Bottom Navbar](#bottom-navbar) - Mobile bottom navigation bar
|
|
102
|
+
|
|
103
|
+
---
|
|
101
104
|
|
|
102
105
|
### Feedback Components
|
|
103
106
|
|
|
@@ -106,6 +109,9 @@ export default App;
|
|
|
106
109
|
- [💡 Tooltip](#tooltip) - Contextual information on hover
|
|
107
110
|
- [⏳ Spinner](#spinner) - Loading indicators
|
|
108
111
|
- [💀 Skeleton](#skeleton) - Content loading placeholders
|
|
112
|
+
- [📅 Date Picker](#datepicker) - Date selection calendar
|
|
113
|
+
|
|
114
|
+
---
|
|
109
115
|
|
|
110
116
|
### Data Display
|
|
111
117
|
|
|
@@ -113,6 +119,12 @@ export default App;
|
|
|
113
119
|
- [🃏 Card](#card) - Content containers
|
|
114
120
|
- [📊 Table](#table) - Tabular data display
|
|
115
121
|
- [👤 Avatar](#avatar) - User profile images
|
|
122
|
+
- [🧱 Grid](#grid) - Structured layout with columns and rows
|
|
123
|
+
- [🧩 Masonry](#masonry) - Pinterest-style staggered grid layout
|
|
124
|
+
- [🕒 Timeline](#timeline) - Chronological event display
|
|
125
|
+
- [📚 Collection](#collection) - Grouped content or list display
|
|
126
|
+
|
|
127
|
+
---
|
|
116
128
|
|
|
117
129
|
### Form Controls
|
|
118
130
|
|
|
@@ -123,12 +135,17 @@ export default App;
|
|
|
123
135
|
- [📝 Textarea](#textarea) - Multi-line text input
|
|
124
136
|
- [🔄 Toggle](#toggle) - Switch controls
|
|
125
137
|
- [📁 File Upload](#file-upload) - File selection and upload
|
|
138
|
+
- [🔢 Code Input](#codeinput) - Multi-field input for codes or OTPs
|
|
139
|
+
- [🎚️ Slider](#slider) - Range or value selector
|
|
140
|
+
|
|
141
|
+
---
|
|
126
142
|
|
|
127
143
|
### Interactive Elements
|
|
128
144
|
|
|
129
145
|
- [🔘 Button](#button) - Clickable actions
|
|
130
146
|
- [🪟 Modal](#modal) - Dialog boxes and overlays
|
|
131
147
|
- [📱 Offcanvas](#offcanvas) - Side panels and drawers
|
|
148
|
+
- [➕ FAB (Floating Action Button)](#fab-floating-action-button) - Prominent floating action button
|
|
132
149
|
|
|
133
150
|
---
|
|
134
151
|
|
|
@@ -553,7 +570,7 @@ import { Button } from "alope-ui";
|
|
|
553
570
|
| `variant` | `'solid' \| 'outline' \| 'ghost' \| 'plain'` | `'solid'` | Button style variant |
|
|
554
571
|
| `variantType` | `'primary' \| 'secondary' \| 'success' \| 'info' \| 'warning' \| 'error'` | `'primary'` | Button color theme |
|
|
555
572
|
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Button size |
|
|
556
|
-
| `
|
|
573
|
+
| `borderType` | `'regular' \| 'stadium'` | `'regular'` | Border radius style |
|
|
557
574
|
| `fullWidth` | `boolean` | `false` | Full width button |
|
|
558
575
|
| `disabled` | `boolean` | `false` | Disabled state |
|
|
559
576
|
| `to` | `string` | `undefined` | Link destination |
|
|
@@ -600,8 +617,8 @@ import { Button } from "alope-ui";
|
|
|
600
617
|
|
|
601
618
|
```jsx
|
|
602
619
|
<div className="flex flex-wrap gap-2">
|
|
603
|
-
<Button
|
|
604
|
-
<Button
|
|
620
|
+
<Button borderType="regular">Regular Radius</Button>
|
|
621
|
+
<Button borderType="stadium">Stadium Radius</Button>
|
|
605
622
|
</div>
|
|
606
623
|
```
|
|
607
624
|
|
|
@@ -3197,7 +3214,7 @@ const CompleteExample = () => {
|
|
|
3197
3214
|
|
|
3198
3215
|
---
|
|
3199
3216
|
|
|
3200
|
-
##
|
|
3217
|
+
## File Upload
|
|
3201
3218
|
|
|
3202
3219
|
The FileUpload component provides a flexible file upload interface with support for drag-and-drop, file validation, preview, and two display modes (dropzone and text input).
|
|
3203
3220
|
|
|
@@ -4227,6 +4244,2998 @@ const CompleteGroupExample = () => {
|
|
|
4227
4244
|
};
|
|
4228
4245
|
```
|
|
4229
4246
|
|
|
4247
|
+
## Collection
|
|
4248
|
+
|
|
4249
|
+
The Collection component displays a list of items with optional active state management and customizable rendering.
|
|
4250
|
+
|
|
4251
|
+
### Import
|
|
4252
|
+
|
|
4253
|
+
```jsx
|
|
4254
|
+
import { Collection } from "alope-ui";
|
|
4255
|
+
import type { CollectionProps } from "alope-ui";
|
|
4256
|
+
```
|
|
4257
|
+
|
|
4258
|
+
### Props
|
|
4259
|
+
|
|
4260
|
+
| Prop | Type | Default | Description |
|
|
4261
|
+
| ------------------ | ---------------------------------------------------------------------------------------- | ----------- | --------------------------------------- |
|
|
4262
|
+
| `data` | `T[]` | `required` | Array of items to display |
|
|
4263
|
+
| `title` | `string` | `undefined` | Optional title for the collection |
|
|
4264
|
+
| `children` | `(item: T, index: number, isActive: boolean) => React.ReactNode` | `undefined` | Custom render function for items |
|
|
4265
|
+
| `className` | `string` | `""` | Container CSS class |
|
|
4266
|
+
| `wrapperClassName` | `string` | `""` | List wrapper CSS class |
|
|
4267
|
+
| `suffixIcon` | `React.ReactNode \| ((item?: T, index?: number, isActive?: boolean) => React.ReactNode)` | `undefined` | Icon or render function for item suffix |
|
|
4268
|
+
| `activeItem` | `any` | `undefined` | ID of the currently active item |
|
|
4269
|
+
| `onChangeActive` | `(item: T, index: number) => void` | `undefined` | Callback when item is clicked |
|
|
4270
|
+
|
|
4271
|
+
### Type Constraint
|
|
4272
|
+
|
|
4273
|
+
The generic type `T` must extend `{ id: any, name?: string }`:
|
|
4274
|
+
|
|
4275
|
+
```typescript
|
|
4276
|
+
type ItemType = {
|
|
4277
|
+
id: string | number;
|
|
4278
|
+
name?: string;
|
|
4279
|
+
// ... other properties
|
|
4280
|
+
};
|
|
4281
|
+
```
|
|
4282
|
+
|
|
4283
|
+
### Basic Collection
|
|
4284
|
+
|
|
4285
|
+
```jsx
|
|
4286
|
+
import { Collection } from "alope-ui";
|
|
4287
|
+
|
|
4288
|
+
const BasicExample = () => {
|
|
4289
|
+
const items = [
|
|
4290
|
+
{ id: 1, name: "Item 1" },
|
|
4291
|
+
{ id: 2, name: "Item 2" },
|
|
4292
|
+
{ id: 3, name: "Item 3" },
|
|
4293
|
+
];
|
|
4294
|
+
|
|
4295
|
+
return <Collection data={items} />;
|
|
4296
|
+
};
|
|
4297
|
+
```
|
|
4298
|
+
|
|
4299
|
+
### With Title
|
|
4300
|
+
|
|
4301
|
+
```jsx
|
|
4302
|
+
<Collection data={items} title="My Collection" />
|
|
4303
|
+
```
|
|
4304
|
+
|
|
4305
|
+
### With Active State
|
|
4306
|
+
|
|
4307
|
+
```jsx
|
|
4308
|
+
import { useState } from "react";
|
|
4309
|
+
import { Collection } from "alope-ui";
|
|
4310
|
+
|
|
4311
|
+
const ActiveStateExample = () => {
|
|
4312
|
+
const [activeId, setActiveId] = useState(1);
|
|
4313
|
+
|
|
4314
|
+
const items = [
|
|
4315
|
+
{ id: 1, name: "Item 1" },
|
|
4316
|
+
{ id: 2, name: "Item 2" },
|
|
4317
|
+
{ id: 3, name: "Item 3" },
|
|
4318
|
+
];
|
|
4319
|
+
|
|
4320
|
+
return (
|
|
4321
|
+
<Collection
|
|
4322
|
+
data={items}
|
|
4323
|
+
activeItem={activeId}
|
|
4324
|
+
onChangeActive={(item) => setActiveId(item.id)}
|
|
4325
|
+
/>
|
|
4326
|
+
);
|
|
4327
|
+
};
|
|
4328
|
+
```
|
|
4329
|
+
|
|
4330
|
+
### With Suffix Icon
|
|
4331
|
+
|
|
4332
|
+
```jsx
|
|
4333
|
+
import { ChevronRight } from "lucide-react";
|
|
4334
|
+
|
|
4335
|
+
{
|
|
4336
|
+
/* Static icon */
|
|
4337
|
+
}
|
|
4338
|
+
<Collection data={items} suffixIcon={<ChevronRight size={20} />} />;
|
|
4339
|
+
|
|
4340
|
+
{
|
|
4341
|
+
/* Dynamic icon based on state */
|
|
4342
|
+
}
|
|
4343
|
+
<Collection
|
|
4344
|
+
data={items}
|
|
4345
|
+
activeItem={activeId}
|
|
4346
|
+
onChangeActive={(item) => setActiveId(item.id)}
|
|
4347
|
+
suffixIcon={(item, index, isActive) =>
|
|
4348
|
+
isActive ? <Check size={20} /> : <ChevronRight size={20} />
|
|
4349
|
+
}
|
|
4350
|
+
/>;
|
|
4351
|
+
```
|
|
4352
|
+
|
|
4353
|
+
### Custom Rendering
|
|
4354
|
+
|
|
4355
|
+
```jsx
|
|
4356
|
+
import { Collection } from "alope-ui";
|
|
4357
|
+
|
|
4358
|
+
const CustomRenderExample = () => {
|
|
4359
|
+
const [activeId, setActiveId] = useState(1);
|
|
4360
|
+
|
|
4361
|
+
const items = [
|
|
4362
|
+
{ id: 1, name: "Task 1", completed: false },
|
|
4363
|
+
{ id: 2, name: "Task 2", completed: true },
|
|
4364
|
+
{ id: 3, name: "Task 3", completed: false },
|
|
4365
|
+
];
|
|
4366
|
+
|
|
4367
|
+
return (
|
|
4368
|
+
<Collection
|
|
4369
|
+
data={items}
|
|
4370
|
+
activeItem={activeId}
|
|
4371
|
+
onChangeActive={(item) => setActiveId(item.id)}
|
|
4372
|
+
>
|
|
4373
|
+
{(item, index, isActive) => (
|
|
4374
|
+
<div
|
|
4375
|
+
className={`
|
|
4376
|
+
p-4 border rounded-lg cursor-pointer
|
|
4377
|
+
${isActive ? "bg-blue-100 border-blue-500" : "border-gray-300"}
|
|
4378
|
+
${item.completed ? "opacity-50" : ""}
|
|
4379
|
+
`}
|
|
4380
|
+
>
|
|
4381
|
+
<div className="flex items-center justify-between">
|
|
4382
|
+
<span className={item.completed ? "line-through" : ""}>
|
|
4383
|
+
{item.name}
|
|
4384
|
+
</span>
|
|
4385
|
+
{item.completed && <span className="text-green-600">✓</span>}
|
|
4386
|
+
</div>
|
|
4387
|
+
</div>
|
|
4388
|
+
)}
|
|
4389
|
+
</Collection>
|
|
4390
|
+
);
|
|
4391
|
+
};
|
|
4392
|
+
```
|
|
4393
|
+
|
|
4394
|
+
### Custom Styling
|
|
4395
|
+
|
|
4396
|
+
```jsx
|
|
4397
|
+
<Collection
|
|
4398
|
+
data={items}
|
|
4399
|
+
title="Styled Collection"
|
|
4400
|
+
className="bg-gray-50 dark:bg-gray-900 p-6 rounded-xl border-2"
|
|
4401
|
+
wrapperClassName="space-y-4"
|
|
4402
|
+
activeItem={activeId}
|
|
4403
|
+
onChangeActive={(item) => setActiveId(item.id)}
|
|
4404
|
+
/>
|
|
4405
|
+
```
|
|
4406
|
+
|
|
4407
|
+
### Empty State
|
|
4408
|
+
|
|
4409
|
+
```jsx
|
|
4410
|
+
{
|
|
4411
|
+
/* When data array is empty */
|
|
4412
|
+
}
|
|
4413
|
+
<Collection data={[]} title="Empty Collection" />;
|
|
4414
|
+
{
|
|
4415
|
+
/* Displays: "No items to display." */
|
|
4416
|
+
}
|
|
4417
|
+
```
|
|
4418
|
+
|
|
4419
|
+
### Complete Example
|
|
4420
|
+
|
|
4421
|
+
```jsx
|
|
4422
|
+
import { useState } from "react";
|
|
4423
|
+
import { Collection } from "alope-ui";
|
|
4424
|
+
import { CheckCircle, Circle, Trash2 } from "lucide-react";
|
|
4425
|
+
|
|
4426
|
+
const CompleteExample = () => {
|
|
4427
|
+
const [activeId, setActiveId] = useState(null);
|
|
4428
|
+
const [todos, setTodos] = useState([
|
|
4429
|
+
{ id: 1, name: "Complete project documentation", completed: false },
|
|
4430
|
+
{ id: 2, name: "Review pull requests", completed: true },
|
|
4431
|
+
{ id: 3, name: "Update dependencies", completed: false },
|
|
4432
|
+
]);
|
|
4433
|
+
|
|
4434
|
+
const toggleComplete = (id) => {
|
|
4435
|
+
setTodos(
|
|
4436
|
+
todos.map((todo) =>
|
|
4437
|
+
todo.id === id ? { ...todo, completed: !todo.completed } : todo
|
|
4438
|
+
)
|
|
4439
|
+
);
|
|
4440
|
+
};
|
|
4441
|
+
|
|
4442
|
+
return (
|
|
4443
|
+
<Collection
|
|
4444
|
+
data={todos}
|
|
4445
|
+
title="Todo List"
|
|
4446
|
+
activeItem={activeId}
|
|
4447
|
+
onChangeActive={(item) => setActiveId(item.id)}
|
|
4448
|
+
className="max-w-md"
|
|
4449
|
+
>
|
|
4450
|
+
{(item, index, isActive) => (
|
|
4451
|
+
<div
|
|
4452
|
+
className={`
|
|
4453
|
+
p-4 border rounded-lg cursor-pointer transition-all
|
|
4454
|
+
${isActive ? "bg-blue-50 border-blue-500" : "border-gray-300"}
|
|
4455
|
+
`}
|
|
4456
|
+
>
|
|
4457
|
+
<div className="flex items-center justify-between">
|
|
4458
|
+
<div className="flex items-center gap-3">
|
|
4459
|
+
<button
|
|
4460
|
+
onClick={(e) => {
|
|
4461
|
+
e.stopPropagation();
|
|
4462
|
+
toggleComplete(item.id);
|
|
4463
|
+
}}
|
|
4464
|
+
className="text-gray-600 hover:text-blue-600"
|
|
4465
|
+
>
|
|
4466
|
+
{item.completed ? (
|
|
4467
|
+
<CheckCircle size={20} className="text-green-600" />
|
|
4468
|
+
) : (
|
|
4469
|
+
<Circle size={20} />
|
|
4470
|
+
)}
|
|
4471
|
+
</button>
|
|
4472
|
+
<span
|
|
4473
|
+
className={item.completed ? "line-through text-gray-500" : ""}
|
|
4474
|
+
>
|
|
4475
|
+
{item.name}
|
|
4476
|
+
</span>
|
|
4477
|
+
</div>
|
|
4478
|
+
<button
|
|
4479
|
+
onClick={(e) => {
|
|
4480
|
+
e.stopPropagation();
|
|
4481
|
+
setTodos(todos.filter((t) => t.id !== item.id));
|
|
4482
|
+
}}
|
|
4483
|
+
className="text-red-500 hover:text-red-700"
|
|
4484
|
+
>
|
|
4485
|
+
<Trash2 size={18} />
|
|
4486
|
+
</button>
|
|
4487
|
+
</div>
|
|
4488
|
+
</div>
|
|
4489
|
+
)}
|
|
4490
|
+
</Collection>
|
|
4491
|
+
);
|
|
4492
|
+
};
|
|
4493
|
+
```
|
|
4494
|
+
|
|
4495
|
+
## FAB (Floating Action Button)
|
|
4496
|
+
|
|
4497
|
+
The FAB component provides a floating action button with expandable child actions, typically positioned at the bottom-right corner of the screen.
|
|
4498
|
+
|
|
4499
|
+
### Import
|
|
4500
|
+
|
|
4501
|
+
```jsx
|
|
4502
|
+
import { FAB } from "alope-ui";
|
|
4503
|
+
```
|
|
4504
|
+
|
|
4505
|
+
### Props
|
|
4506
|
+
|
|
4507
|
+
| Prop | Type | Default | Description |
|
|
4508
|
+
| ------------- | ------------------------------------------------------------------------- | ------------ | --------------------------------------- |
|
|
4509
|
+
| `children` | `React.ReactNode` | `required` | Action buttons to display when expanded |
|
|
4510
|
+
| `isOpen` | `boolean` | `required` | Controls the expanded state |
|
|
4511
|
+
| `onOpen` | `() => void` | `required` | Callback to toggle open/close state |
|
|
4512
|
+
| `direction` | `'horizontal' \| 'vertical'` | `'vertical'` | Direction of child actions expansion |
|
|
4513
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Size of the FAB button |
|
|
4514
|
+
| `variant` | `'solid' \| 'outline' \| 'ghost' \| 'soft'` | `'solid'` | Visual variant style |
|
|
4515
|
+
| `variantType` | `'primary' \| 'secondary' \| 'success' \| 'error' \| 'warning' \| 'info'` | `'primary'` | Color theme |
|
|
4516
|
+
| `icon` | `React.ReactNode` | `'+'` | Icon displayed on the main FAB button |
|
|
4517
|
+
|
|
4518
|
+
### Basic FAB
|
|
4519
|
+
|
|
4520
|
+
```jsx
|
|
4521
|
+
import { useState } from "react";
|
|
4522
|
+
import { FAB } from "alope-ui";
|
|
4523
|
+
import { Button } from "alope-ui";
|
|
4524
|
+
|
|
4525
|
+
const BasicExample = () => {
|
|
4526
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
4527
|
+
|
|
4528
|
+
return (
|
|
4529
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)}>
|
|
4530
|
+
<Button variant="solid" variantType="primary">
|
|
4531
|
+
Action 1
|
|
4532
|
+
</Button>
|
|
4533
|
+
<Button variant="solid" variantType="secondary">
|
|
4534
|
+
Action 2
|
|
4535
|
+
</Button>
|
|
4536
|
+
<Button variant="solid" variantType="success">
|
|
4537
|
+
Action 3
|
|
4538
|
+
</Button>
|
|
4539
|
+
</FAB>
|
|
4540
|
+
);
|
|
4541
|
+
};
|
|
4542
|
+
```
|
|
4543
|
+
|
|
4544
|
+
### Directions
|
|
4545
|
+
|
|
4546
|
+
```jsx
|
|
4547
|
+
{
|
|
4548
|
+
/* Vertical direction (default) */
|
|
4549
|
+
}
|
|
4550
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} direction="vertical">
|
|
4551
|
+
{/* Actions */}
|
|
4552
|
+
</FAB>;
|
|
4553
|
+
|
|
4554
|
+
{
|
|
4555
|
+
/* Horizontal direction */
|
|
4556
|
+
}
|
|
4557
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} direction="horizontal">
|
|
4558
|
+
{/* Actions */}
|
|
4559
|
+
</FAB>;
|
|
4560
|
+
```
|
|
4561
|
+
|
|
4562
|
+
### Sizes
|
|
4563
|
+
|
|
4564
|
+
```jsx
|
|
4565
|
+
{
|
|
4566
|
+
/* Small size */
|
|
4567
|
+
}
|
|
4568
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} size="sm">
|
|
4569
|
+
{/* Actions */}
|
|
4570
|
+
</FAB>;
|
|
4571
|
+
|
|
4572
|
+
{
|
|
4573
|
+
/* Medium size */
|
|
4574
|
+
}
|
|
4575
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} size="md">
|
|
4576
|
+
{/* Actions */}
|
|
4577
|
+
</FAB>;
|
|
4578
|
+
|
|
4579
|
+
{
|
|
4580
|
+
/* Large size */
|
|
4581
|
+
}
|
|
4582
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} size="lg">
|
|
4583
|
+
{/* Actions */}
|
|
4584
|
+
</FAB>;
|
|
4585
|
+
```
|
|
4586
|
+
|
|
4587
|
+
### Variants
|
|
4588
|
+
|
|
4589
|
+
```jsx
|
|
4590
|
+
{
|
|
4591
|
+
/* Solid variant */
|
|
4592
|
+
}
|
|
4593
|
+
<FAB
|
|
4594
|
+
isOpen={isOpen}
|
|
4595
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4596
|
+
variant="solid"
|
|
4597
|
+
variantType="primary"
|
|
4598
|
+
>
|
|
4599
|
+
{/* Actions */}
|
|
4600
|
+
</FAB>;
|
|
4601
|
+
|
|
4602
|
+
{
|
|
4603
|
+
/* Outline variant */
|
|
4604
|
+
}
|
|
4605
|
+
<FAB
|
|
4606
|
+
isOpen={isOpen}
|
|
4607
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4608
|
+
variant="outline"
|
|
4609
|
+
variantType="secondary"
|
|
4610
|
+
>
|
|
4611
|
+
{/* Actions */}
|
|
4612
|
+
</FAB>;
|
|
4613
|
+
|
|
4614
|
+
{
|
|
4615
|
+
/* Ghost variant */
|
|
4616
|
+
}
|
|
4617
|
+
<FAB
|
|
4618
|
+
isOpen={isOpen}
|
|
4619
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4620
|
+
variant="ghost"
|
|
4621
|
+
variantType="success"
|
|
4622
|
+
>
|
|
4623
|
+
{/* Actions */}
|
|
4624
|
+
</FAB>;
|
|
4625
|
+
|
|
4626
|
+
{
|
|
4627
|
+
/* Soft variant */
|
|
4628
|
+
}
|
|
4629
|
+
<FAB
|
|
4630
|
+
isOpen={isOpen}
|
|
4631
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4632
|
+
variant="soft"
|
|
4633
|
+
variantType="warning"
|
|
4634
|
+
>
|
|
4635
|
+
{/* Actions */}
|
|
4636
|
+
</FAB>;
|
|
4637
|
+
```
|
|
4638
|
+
|
|
4639
|
+
### Variant Types (Colors)
|
|
4640
|
+
|
|
4641
|
+
```jsx
|
|
4642
|
+
{
|
|
4643
|
+
/* Primary */
|
|
4644
|
+
}
|
|
4645
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="primary">
|
|
4646
|
+
{/* Actions */}
|
|
4647
|
+
</FAB>;
|
|
4648
|
+
|
|
4649
|
+
{
|
|
4650
|
+
/* Secondary */
|
|
4651
|
+
}
|
|
4652
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="secondary">
|
|
4653
|
+
{/* Actions */}
|
|
4654
|
+
</FAB>;
|
|
4655
|
+
|
|
4656
|
+
{
|
|
4657
|
+
/* Success */
|
|
4658
|
+
}
|
|
4659
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="success">
|
|
4660
|
+
{/* Actions */}
|
|
4661
|
+
</FAB>;
|
|
4662
|
+
|
|
4663
|
+
{
|
|
4664
|
+
/* Error */
|
|
4665
|
+
}
|
|
4666
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="error">
|
|
4667
|
+
{/* Actions */}
|
|
4668
|
+
</FAB>;
|
|
4669
|
+
|
|
4670
|
+
{
|
|
4671
|
+
/* Warning */
|
|
4672
|
+
}
|
|
4673
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="warning">
|
|
4674
|
+
{/* Actions */}
|
|
4675
|
+
</FAB>;
|
|
4676
|
+
|
|
4677
|
+
{
|
|
4678
|
+
/* Info */
|
|
4679
|
+
}
|
|
4680
|
+
<FAB isOpen={isOpen} onOpen={() => setIsOpen(!isOpen)} variantType="info">
|
|
4681
|
+
{/* Actions */}
|
|
4682
|
+
</FAB>;
|
|
4683
|
+
```
|
|
4684
|
+
|
|
4685
|
+
### Custom Icon
|
|
4686
|
+
|
|
4687
|
+
```jsx
|
|
4688
|
+
import { Plus, Menu } from "lucide-react";
|
|
4689
|
+
|
|
4690
|
+
{
|
|
4691
|
+
/* Custom icon */
|
|
4692
|
+
}
|
|
4693
|
+
<FAB
|
|
4694
|
+
isOpen={isOpen}
|
|
4695
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4696
|
+
icon={<Plus size={24} />}
|
|
4697
|
+
>
|
|
4698
|
+
{/* Actions */}
|
|
4699
|
+
</FAB>;
|
|
4700
|
+
|
|
4701
|
+
{
|
|
4702
|
+
/* Menu icon */
|
|
4703
|
+
}
|
|
4704
|
+
<FAB
|
|
4705
|
+
isOpen={isOpen}
|
|
4706
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4707
|
+
icon={<Menu size={24} />}
|
|
4708
|
+
>
|
|
4709
|
+
{/* Actions */}
|
|
4710
|
+
</FAB>;
|
|
4711
|
+
```
|
|
4712
|
+
|
|
4713
|
+
### With Icon Actions
|
|
4714
|
+
|
|
4715
|
+
```jsx
|
|
4716
|
+
import { useState } from "react";
|
|
4717
|
+
import { FAB } from "alope-ui";
|
|
4718
|
+
import { Button } from "alope-ui";
|
|
4719
|
+
import { Edit, Trash, Share, Plus } from "lucide-react";
|
|
4720
|
+
|
|
4721
|
+
const IconActionsExample = () => {
|
|
4722
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
4723
|
+
|
|
4724
|
+
return (
|
|
4725
|
+
<FAB
|
|
4726
|
+
isOpen={isOpen}
|
|
4727
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4728
|
+
icon={<Plus size={24} />}
|
|
4729
|
+
>
|
|
4730
|
+
<Button variant="solid" variantType="primary" size="sm">
|
|
4731
|
+
<Edit size={18} />
|
|
4732
|
+
</Button>
|
|
4733
|
+
<Button variant="solid" variantType="error" size="sm">
|
|
4734
|
+
<Trash size={18} />
|
|
4735
|
+
</Button>
|
|
4736
|
+
<Button variant="solid" variantType="success" size="sm">
|
|
4737
|
+
<Share size={18} />
|
|
4738
|
+
</Button>
|
|
4739
|
+
</FAB>
|
|
4740
|
+
);
|
|
4741
|
+
};
|
|
4742
|
+
```
|
|
4743
|
+
|
|
4744
|
+
### Complete Example
|
|
4745
|
+
|
|
4746
|
+
```jsx
|
|
4747
|
+
import { useState } from "react";
|
|
4748
|
+
import { FAB } from "alope-ui";
|
|
4749
|
+
import { Button } from "alope-ui";
|
|
4750
|
+
import { Plus, MessageCircle, Phone, Video, Mail } from "lucide-react";
|
|
4751
|
+
|
|
4752
|
+
const CompleteExample = () => {
|
|
4753
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
4754
|
+
|
|
4755
|
+
const handleAction = (action) => {
|
|
4756
|
+
console.log(`${action} clicked`);
|
|
4757
|
+
setIsOpen(false);
|
|
4758
|
+
};
|
|
4759
|
+
|
|
4760
|
+
return (
|
|
4761
|
+
<FAB
|
|
4762
|
+
isOpen={isOpen}
|
|
4763
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4764
|
+
direction="vertical"
|
|
4765
|
+
size="md"
|
|
4766
|
+
variant="solid"
|
|
4767
|
+
variantType="primary"
|
|
4768
|
+
icon={<Plus size={24} />}
|
|
4769
|
+
>
|
|
4770
|
+
<Button
|
|
4771
|
+
variant="solid"
|
|
4772
|
+
variantType="primary"
|
|
4773
|
+
size="sm"
|
|
4774
|
+
onClick={() => handleAction("Message")}
|
|
4775
|
+
>
|
|
4776
|
+
<MessageCircle size={20} />
|
|
4777
|
+
<span className="ml-2">Message</span>
|
|
4778
|
+
</Button>
|
|
4779
|
+
|
|
4780
|
+
<Button
|
|
4781
|
+
variant="solid"
|
|
4782
|
+
variantType="success"
|
|
4783
|
+
size="sm"
|
|
4784
|
+
onClick={() => handleAction("Call")}
|
|
4785
|
+
>
|
|
4786
|
+
<Phone size={20} />
|
|
4787
|
+
<span className="ml-2">Call</span>
|
|
4788
|
+
</Button>
|
|
4789
|
+
|
|
4790
|
+
<Button
|
|
4791
|
+
variant="solid"
|
|
4792
|
+
variantType="info"
|
|
4793
|
+
size="sm"
|
|
4794
|
+
onClick={() => handleAction("Video")}
|
|
4795
|
+
>
|
|
4796
|
+
<Video size={20} />
|
|
4797
|
+
<span className="ml-2">Video</span>
|
|
4798
|
+
</Button>
|
|
4799
|
+
|
|
4800
|
+
<Button
|
|
4801
|
+
variant="solid"
|
|
4802
|
+
variantType="secondary"
|
|
4803
|
+
size="sm"
|
|
4804
|
+
onClick={() => handleAction("Email")}
|
|
4805
|
+
>
|
|
4806
|
+
<Mail size={20} />
|
|
4807
|
+
<span className="ml-2">Email</span>
|
|
4808
|
+
</Button>
|
|
4809
|
+
</FAB>
|
|
4810
|
+
);
|
|
4811
|
+
};
|
|
4812
|
+
```
|
|
4813
|
+
|
|
4814
|
+
### Horizontal Layout Example
|
|
4815
|
+
|
|
4816
|
+
```jsx
|
|
4817
|
+
import { useState } from "react";
|
|
4818
|
+
import { FAB } from "alope-ui";
|
|
4819
|
+
import { Button } from "alope-ui";
|
|
4820
|
+
import { Plus, Home, User, Settings } from "lucide-react";
|
|
4821
|
+
|
|
4822
|
+
const HorizontalExample = () => {
|
|
4823
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
4824
|
+
|
|
4825
|
+
return (
|
|
4826
|
+
<FAB
|
|
4827
|
+
isOpen={isOpen}
|
|
4828
|
+
onOpen={() => setIsOpen(!isOpen)}
|
|
4829
|
+
direction="horizontal"
|
|
4830
|
+
size="md"
|
|
4831
|
+
variant="solid"
|
|
4832
|
+
variantType="primary"
|
|
4833
|
+
icon={<Plus size={24} />}
|
|
4834
|
+
>
|
|
4835
|
+
<Button variant="solid" variantType="primary" size="sm">
|
|
4836
|
+
<Home size={20} />
|
|
4837
|
+
</Button>
|
|
4838
|
+
<Button variant="solid" variantType="secondary" size="sm">
|
|
4839
|
+
<User size={20} />
|
|
4840
|
+
</Button>
|
|
4841
|
+
<Button variant="solid" variantType="info" size="sm">
|
|
4842
|
+
<Settings size={20} />
|
|
4843
|
+
</Button>
|
|
4844
|
+
</FAB>
|
|
4845
|
+
);
|
|
4846
|
+
};
|
|
4847
|
+
```
|
|
4848
|
+
|
|
4849
|
+
## BottomNavbar
|
|
4850
|
+
|
|
4851
|
+
The BottomNavbar component provides a mobile-friendly navigation bar fixed at the bottom of the screen, composed of BottomNavbarAction items.
|
|
4852
|
+
|
|
4853
|
+
### Import
|
|
4854
|
+
|
|
4855
|
+
```jsx
|
|
4856
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
4857
|
+
```
|
|
4858
|
+
|
|
4859
|
+
### BottomNavbar Props
|
|
4860
|
+
|
|
4861
|
+
| Prop | Type | Default | Description |
|
|
4862
|
+
| --------------- | ------------------------- | ---------- | -------------------------------- |
|
|
4863
|
+
| `children` | `React.ReactNode` | `required` | BottomNavbarAction components |
|
|
4864
|
+
| `value` | `string` | `required` | Currently selected action value |
|
|
4865
|
+
| `onChangeValue` | `(value: string) => void` | `required` | Callback when action is selected |
|
|
4866
|
+
| `className` | `string` | `""` | Additional CSS class |
|
|
4867
|
+
|
|
4868
|
+
### BottomNavbarAction Props
|
|
4869
|
+
|
|
4870
|
+
| Prop | Type | Default | Description |
|
|
4871
|
+
| ---------------- | ------------------------- | ----------- | ---------------------------------------- |
|
|
4872
|
+
| `value` | `string` | `required` | Unique identifier for the action |
|
|
4873
|
+
| `label` | `React.ReactNode` | `undefined` | Label text or component |
|
|
4874
|
+
| `icon` | `React.ReactNode` | `undefined` | Icon component |
|
|
4875
|
+
| `selectedValue` | `string` | `undefined` | Currently selected value (auto-injected) |
|
|
4876
|
+
| `onChangeValue` | `(value: string) => void` | `undefined` | Change handler (auto-injected) |
|
|
4877
|
+
| `labelClassName` | `string` | `""` | Label CSS class |
|
|
4878
|
+
| `className` | `string` | `""` | Additional CSS class |
|
|
4879
|
+
|
|
4880
|
+
### Basic BottomNavbar
|
|
4881
|
+
|
|
4882
|
+
```jsx
|
|
4883
|
+
import { useState } from "react";
|
|
4884
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
4885
|
+
import { Home, Search, Bell, User } from "lucide-react";
|
|
4886
|
+
|
|
4887
|
+
const BasicExample = () => {
|
|
4888
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
4889
|
+
|
|
4890
|
+
return (
|
|
4891
|
+
<BottomNavbar value={activeTab} onChangeValue={setActiveTab}>
|
|
4892
|
+
<BottomNavbarAction value="home" icon={<Home size={24} />} label="Home" />
|
|
4893
|
+
<BottomNavbarAction
|
|
4894
|
+
value="search"
|
|
4895
|
+
icon={<Search size={24} />}
|
|
4896
|
+
label="Search"
|
|
4897
|
+
/>
|
|
4898
|
+
<BottomNavbarAction
|
|
4899
|
+
value="notifications"
|
|
4900
|
+
icon={<Bell size={24} />}
|
|
4901
|
+
label="Notifications"
|
|
4902
|
+
/>
|
|
4903
|
+
<BottomNavbarAction
|
|
4904
|
+
value="profile"
|
|
4905
|
+
icon={<User size={24} />}
|
|
4906
|
+
label="Profile"
|
|
4907
|
+
/>
|
|
4908
|
+
</BottomNavbar>
|
|
4909
|
+
);
|
|
4910
|
+
};
|
|
4911
|
+
```
|
|
4912
|
+
|
|
4913
|
+
### Without Labels
|
|
4914
|
+
|
|
4915
|
+
```jsx
|
|
4916
|
+
import { useState } from "react";
|
|
4917
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
4918
|
+
import { Home, Search, Bell, User } from "lucide-react";
|
|
4919
|
+
|
|
4920
|
+
const IconOnlyExample = () => {
|
|
4921
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
4922
|
+
|
|
4923
|
+
return (
|
|
4924
|
+
<BottomNavbar value={activeTab} onChangeValue={setActiveTab}>
|
|
4925
|
+
<BottomNavbarAction value="home" icon={<Home size={24} />} />
|
|
4926
|
+
<BottomNavbarAction value="search" icon={<Search size={24} />} />
|
|
4927
|
+
<BottomNavbarAction value="notifications" icon={<Bell size={24} />} />
|
|
4928
|
+
<BottomNavbarAction value="profile" icon={<User size={24} />} />
|
|
4929
|
+
</BottomNavbar>
|
|
4930
|
+
);
|
|
4931
|
+
};
|
|
4932
|
+
```
|
|
4933
|
+
|
|
4934
|
+
### With Badge Indicators
|
|
4935
|
+
|
|
4936
|
+
```jsx
|
|
4937
|
+
import { useState } from "react";
|
|
4938
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
4939
|
+
import { Home, ShoppingCart, Bell, User } from "lucide-react";
|
|
4940
|
+
|
|
4941
|
+
const BadgeExample = () => {
|
|
4942
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
4943
|
+
|
|
4944
|
+
return (
|
|
4945
|
+
<BottomNavbar value={activeTab} onChangeValue={setActiveTab}>
|
|
4946
|
+
<BottomNavbarAction value="home" icon={<Home size={24} />} label="Home" />
|
|
4947
|
+
<BottomNavbarAction
|
|
4948
|
+
value="cart"
|
|
4949
|
+
icon={
|
|
4950
|
+
<div className="relative">
|
|
4951
|
+
<ShoppingCart size={24} />
|
|
4952
|
+
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
|
|
4953
|
+
3
|
|
4954
|
+
</span>
|
|
4955
|
+
</div>
|
|
4956
|
+
}
|
|
4957
|
+
label="Cart"
|
|
4958
|
+
/>
|
|
4959
|
+
<BottomNavbarAction
|
|
4960
|
+
value="notifications"
|
|
4961
|
+
icon={
|
|
4962
|
+
<div className="relative">
|
|
4963
|
+
<Bell size={24} />
|
|
4964
|
+
<span className="absolute -top-1 -right-1 bg-red-500 w-2 h-2 rounded-full"></span>
|
|
4965
|
+
</div>
|
|
4966
|
+
}
|
|
4967
|
+
label="Notifications"
|
|
4968
|
+
/>
|
|
4969
|
+
<BottomNavbarAction
|
|
4970
|
+
value="profile"
|
|
4971
|
+
icon={<User size={24} />}
|
|
4972
|
+
label="Profile"
|
|
4973
|
+
/>
|
|
4974
|
+
</BottomNavbar>
|
|
4975
|
+
);
|
|
4976
|
+
};
|
|
4977
|
+
```
|
|
4978
|
+
|
|
4979
|
+
### Custom Styling
|
|
4980
|
+
|
|
4981
|
+
```jsx
|
|
4982
|
+
import { useState } from "react";
|
|
4983
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
4984
|
+
import { Home, Search, Heart, User } from "lucide-react";
|
|
4985
|
+
|
|
4986
|
+
const CustomStyledExample = () => {
|
|
4987
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
4988
|
+
|
|
4989
|
+
return (
|
|
4990
|
+
<BottomNavbar
|
|
4991
|
+
value={activeTab}
|
|
4992
|
+
onChangeValue={setActiveTab}
|
|
4993
|
+
className="border-t-2 border-primary"
|
|
4994
|
+
>
|
|
4995
|
+
<BottomNavbarAction
|
|
4996
|
+
value="home"
|
|
4997
|
+
icon={<Home size={24} />}
|
|
4998
|
+
label="Home"
|
|
4999
|
+
labelClassName="font-semibold"
|
|
5000
|
+
/>
|
|
5001
|
+
<BottomNavbarAction
|
|
5002
|
+
value="search"
|
|
5003
|
+
icon={<Search size={24} />}
|
|
5004
|
+
label="Search"
|
|
5005
|
+
labelClassName="font-semibold"
|
|
5006
|
+
/>
|
|
5007
|
+
<BottomNavbarAction
|
|
5008
|
+
value="favorites"
|
|
5009
|
+
icon={<Heart size={24} />}
|
|
5010
|
+
label="Favorites"
|
|
5011
|
+
labelClassName="font-semibold"
|
|
5012
|
+
/>
|
|
5013
|
+
<BottomNavbarAction
|
|
5014
|
+
value="profile"
|
|
5015
|
+
icon={<User size={24} />}
|
|
5016
|
+
label="Profile"
|
|
5017
|
+
labelClassName="font-semibold"
|
|
5018
|
+
/>
|
|
5019
|
+
</BottomNavbar>
|
|
5020
|
+
);
|
|
5021
|
+
};
|
|
5022
|
+
```
|
|
5023
|
+
|
|
5024
|
+
### With Routing Integration
|
|
5025
|
+
|
|
5026
|
+
```jsx
|
|
5027
|
+
import { useState, useEffect } from "react";
|
|
5028
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
5029
|
+
import { Home, Compass, PlusSquare, Heart, User } from "lucide-react";
|
|
5030
|
+
import { useNavigate, useLocation } from "react-router-dom";
|
|
5031
|
+
|
|
5032
|
+
const RoutingExample = () => {
|
|
5033
|
+
const navigate = useNavigate();
|
|
5034
|
+
const location = useLocation();
|
|
5035
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
5036
|
+
|
|
5037
|
+
useEffect(() => {
|
|
5038
|
+
// Update active tab based on current route
|
|
5039
|
+
const path = location.pathname.substring(1) || "home";
|
|
5040
|
+
setActiveTab(path);
|
|
5041
|
+
}, [location]);
|
|
5042
|
+
|
|
5043
|
+
const handleTabChange = (value) => {
|
|
5044
|
+
setActiveTab(value);
|
|
5045
|
+
navigate(`/${value}`);
|
|
5046
|
+
};
|
|
5047
|
+
|
|
5048
|
+
return (
|
|
5049
|
+
<BottomNavbar value={activeTab} onChangeValue={handleTabChange}>
|
|
5050
|
+
<BottomNavbarAction value="home" icon={<Home size={24} />} label="Home" />
|
|
5051
|
+
<BottomNavbarAction
|
|
5052
|
+
value="explore"
|
|
5053
|
+
icon={<Compass size={24} />}
|
|
5054
|
+
label="Explore"
|
|
5055
|
+
/>
|
|
5056
|
+
<BottomNavbarAction
|
|
5057
|
+
value="create"
|
|
5058
|
+
icon={<PlusSquare size={24} />}
|
|
5059
|
+
label="Create"
|
|
5060
|
+
/>
|
|
5061
|
+
<BottomNavbarAction
|
|
5062
|
+
value="likes"
|
|
5063
|
+
icon={<Heart size={24} />}
|
|
5064
|
+
label="Likes"
|
|
5065
|
+
/>
|
|
5066
|
+
<BottomNavbarAction
|
|
5067
|
+
value="profile"
|
|
5068
|
+
icon={<User size={24} />}
|
|
5069
|
+
label="Profile"
|
|
5070
|
+
/>
|
|
5071
|
+
</BottomNavbar>
|
|
5072
|
+
);
|
|
5073
|
+
};
|
|
5074
|
+
```
|
|
5075
|
+
|
|
5076
|
+
### Complete Example
|
|
5077
|
+
|
|
5078
|
+
```jsx
|
|
5079
|
+
import { useState } from "react";
|
|
5080
|
+
import { BottomNavbar, BottomNavbarAction } from "alope-ui";
|
|
5081
|
+
import { Home, Search, PlusCircle, MessageCircle, User } from "lucide-react";
|
|
5082
|
+
|
|
5083
|
+
const CompleteExample = () => {
|
|
5084
|
+
const [activeTab, setActiveTab] = useState("home");
|
|
5085
|
+
const [notifications, setNotifications] = useState({
|
|
5086
|
+
messages: 5,
|
|
5087
|
+
hasNewNotifications: true,
|
|
5088
|
+
});
|
|
5089
|
+
|
|
5090
|
+
const handleTabChange = (value) => {
|
|
5091
|
+
setActiveTab(value);
|
|
5092
|
+
|
|
5093
|
+
// Clear notifications when viewing messages
|
|
5094
|
+
if (value === "messages") {
|
|
5095
|
+
setNotifications({ ...notifications, messages: 0 });
|
|
5096
|
+
}
|
|
5097
|
+
};
|
|
5098
|
+
|
|
5099
|
+
return (
|
|
5100
|
+
<div className="relative min-h-screen pb-20">
|
|
5101
|
+
{/* Your page content here */}
|
|
5102
|
+
<div className="p-4">
|
|
5103
|
+
<h1 className="text-2xl font-bold">
|
|
5104
|
+
{activeTab.charAt(0).toUpperCase() + activeTab.slice(1)} Page
|
|
5105
|
+
</h1>
|
|
5106
|
+
<p className="text-gray-600 mt-2">Current active tab: {activeTab}</p>
|
|
5107
|
+
</div>
|
|
5108
|
+
|
|
5109
|
+
{/* Bottom Navigation */}
|
|
5110
|
+
<BottomNavbar value={activeTab} onChangeValue={handleTabChange}>
|
|
5111
|
+
<BottomNavbarAction
|
|
5112
|
+
value="home"
|
|
5113
|
+
icon={<Home size={24} />}
|
|
5114
|
+
label="Home"
|
|
5115
|
+
/>
|
|
5116
|
+
|
|
5117
|
+
<BottomNavbarAction
|
|
5118
|
+
value="search"
|
|
5119
|
+
icon={<Search size={24} />}
|
|
5120
|
+
label="Search"
|
|
5121
|
+
/>
|
|
5122
|
+
|
|
5123
|
+
<BottomNavbarAction
|
|
5124
|
+
value="create"
|
|
5125
|
+
icon={<PlusCircle size={28} />}
|
|
5126
|
+
label="Create"
|
|
5127
|
+
className="relative -top-2"
|
|
5128
|
+
/>
|
|
5129
|
+
|
|
5130
|
+
<BottomNavbarAction
|
|
5131
|
+
value="messages"
|
|
5132
|
+
icon={
|
|
5133
|
+
<div className="relative">
|
|
5134
|
+
<MessageCircle size={24} />
|
|
5135
|
+
{notifications.messages > 0 && (
|
|
5136
|
+
<span className="absolute -top-2 -right-2 bg-red-500 text-white text-xs rounded-full w-5 h-5 flex items-center justify-center">
|
|
5137
|
+
{notifications.messages}
|
|
5138
|
+
</span>
|
|
5139
|
+
)}
|
|
5140
|
+
</div>
|
|
5141
|
+
}
|
|
5142
|
+
label="Messages"
|
|
5143
|
+
/>
|
|
5144
|
+
|
|
5145
|
+
<BottomNavbarAction
|
|
5146
|
+
value="profile"
|
|
5147
|
+
icon={
|
|
5148
|
+
<div className="relative">
|
|
5149
|
+
<User size={24} />
|
|
5150
|
+
{notifications.hasNewNotifications && (
|
|
5151
|
+
<span className="absolute -top-1 -right-1 bg-red-500 w-2 h-2 rounded-full"></span>
|
|
5152
|
+
)}
|
|
5153
|
+
</div>
|
|
5154
|
+
}
|
|
5155
|
+
label="Profile"
|
|
5156
|
+
/>
|
|
5157
|
+
</BottomNavbar>
|
|
5158
|
+
</div>
|
|
5159
|
+
);
|
|
5160
|
+
};
|
|
5161
|
+
```
|
|
5162
|
+
|
|
5163
|
+
## Grid
|
|
5164
|
+
|
|
5165
|
+
The Grid component provides a flexible and responsive CSS Grid layout system with support for responsive columns and gaps.
|
|
5166
|
+
|
|
5167
|
+
### Import
|
|
5168
|
+
|
|
5169
|
+
```jsx
|
|
5170
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5171
|
+
```
|
|
5172
|
+
|
|
5173
|
+
### Grid Props
|
|
5174
|
+
|
|
5175
|
+
| Prop | Type | Default | Description |
|
|
5176
|
+
| ----------- | ----------------- | ----------- | -------------------------------------- |
|
|
5177
|
+
| `children` | `React.ReactNode` | `required` | Grid items to display |
|
|
5178
|
+
| `cols` | `ResponsiveCols` | `undefined` | Number of columns (1-12) or responsive |
|
|
5179
|
+
| `gap` | `ResponsiveGap` | `undefined` | Gap between items (1-6) or responsive |
|
|
5180
|
+
| `className` | `string` | `""` | Additional CSS class |
|
|
5181
|
+
|
|
5182
|
+
### GridItem Props
|
|
5183
|
+
|
|
5184
|
+
| Prop | Type | Default | Description |
|
|
5185
|
+
| ----------- | ------------------- | ----------- | ------------------------------------------ |
|
|
5186
|
+
| `children` | `React.ReactNode` | `required` | Content to display |
|
|
5187
|
+
| `colSpan` | `ResponsiveColSpan` | `undefined` | Number of columns to span (1-12 or 'full') |
|
|
5188
|
+
| `className` | `string` | `""` | Additional CSS class |
|
|
5189
|
+
|
|
5190
|
+
### Responsive Types
|
|
5191
|
+
|
|
5192
|
+
```typescript
|
|
5193
|
+
type ResponsiveValue<T> = T | Partial<Record<Breakpoint, T>>
|
|
5194
|
+
type Breakpoint = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
5195
|
+
|
|
5196
|
+
// Examples:
|
|
5197
|
+
cols={3} // Same for all breakpoints
|
|
5198
|
+
cols={{ base: 1, md: 2, lg: 3 }} // Responsive columns
|
|
5199
|
+
gap={4} // Same for all breakpoints
|
|
5200
|
+
gap={{ base: 2, md: 4, lg: 6 }} // Responsive gaps
|
|
5201
|
+
```
|
|
5202
|
+
|
|
5203
|
+
### Basic Grid
|
|
5204
|
+
|
|
5205
|
+
```jsx
|
|
5206
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5207
|
+
|
|
5208
|
+
const BasicExample = () => {
|
|
5209
|
+
return (
|
|
5210
|
+
<Grid cols={3} gap={4}>
|
|
5211
|
+
<GridItem>Item 1</GridItem>
|
|
5212
|
+
<GridItem>Item 2</GridItem>
|
|
5213
|
+
<GridItem>Item 3</GridItem>
|
|
5214
|
+
<GridItem>Item 4</GridItem>
|
|
5215
|
+
<GridItem>Item 5</GridItem>
|
|
5216
|
+
<GridItem>Item 6</GridItem>
|
|
5217
|
+
</Grid>
|
|
5218
|
+
);
|
|
5219
|
+
};
|
|
5220
|
+
```
|
|
5221
|
+
|
|
5222
|
+
### Responsive Columns
|
|
5223
|
+
|
|
5224
|
+
```jsx
|
|
5225
|
+
{
|
|
5226
|
+
/* 1 column on mobile, 2 on tablet, 3 on desktop */
|
|
5227
|
+
}
|
|
5228
|
+
<Grid cols={{ base: 1, md: 2, lg: 3 }} gap={4}>
|
|
5229
|
+
<GridItem>Item 1</GridItem>
|
|
5230
|
+
<GridItem>Item 2</GridItem>
|
|
5231
|
+
<GridItem>Item 3</GridItem>
|
|
5232
|
+
<GridItem>Item 4</GridItem>
|
|
5233
|
+
<GridItem>Item 5</GridItem>
|
|
5234
|
+
<GridItem>Item 6</GridItem>
|
|
5235
|
+
</Grid>;
|
|
5236
|
+
```
|
|
5237
|
+
|
|
5238
|
+
### Responsive Gaps
|
|
5239
|
+
|
|
5240
|
+
```jsx
|
|
5241
|
+
{
|
|
5242
|
+
/* Small gap on mobile, larger on desktop */
|
|
5243
|
+
}
|
|
5244
|
+
<Grid cols={3} gap={{ base: 2, md: 4, lg: 6 }}>
|
|
5245
|
+
<GridItem>Item 1</GridItem>
|
|
5246
|
+
<GridItem>Item 2</GridItem>
|
|
5247
|
+
<GridItem>Item 3</GridItem>
|
|
5248
|
+
</Grid>;
|
|
5249
|
+
```
|
|
5250
|
+
|
|
5251
|
+
### Column Spanning
|
|
5252
|
+
|
|
5253
|
+
```jsx
|
|
5254
|
+
{
|
|
5255
|
+
/* Items spanning multiple columns */
|
|
5256
|
+
}
|
|
5257
|
+
<Grid cols={4} gap={4}>
|
|
5258
|
+
<GridItem colSpan={2}>Spans 2 columns</GridItem>
|
|
5259
|
+
<GridItem colSpan={2}>Spans 2 columns</GridItem>
|
|
5260
|
+
<GridItem colSpan={1}>1 column</GridItem>
|
|
5261
|
+
<GridItem colSpan={3}>Spans 3 columns</GridItem>
|
|
5262
|
+
<GridItem colSpan={4}>Spans full width</GridItem>
|
|
5263
|
+
</Grid>;
|
|
5264
|
+
```
|
|
5265
|
+
|
|
5266
|
+
### Responsive Column Spanning
|
|
5267
|
+
|
|
5268
|
+
```jsx
|
|
5269
|
+
{
|
|
5270
|
+
/* Different spans at different breakpoints */
|
|
5271
|
+
}
|
|
5272
|
+
<Grid cols={{ base: 2, md: 4, lg: 6 }} gap={4}>
|
|
5273
|
+
<GridItem colSpan={{ base: 2, md: 2, lg: 3 }}>
|
|
5274
|
+
Full width on mobile, half on tablet, half on desktop
|
|
5275
|
+
</GridItem>
|
|
5276
|
+
<GridItem colSpan={{ base: 2, md: 2, lg: 3 }}>
|
|
5277
|
+
Full width on mobile, half on tablet, half on desktop
|
|
5278
|
+
</GridItem>
|
|
5279
|
+
<GridItem colSpan={{ base: 1, md: 2, lg: 2 }}>Responsive span</GridItem>
|
|
5280
|
+
<GridItem colSpan={{ base: 1, md: 2, lg: 2 }}>Responsive span</GridItem>
|
|
5281
|
+
</Grid>;
|
|
5282
|
+
```
|
|
5283
|
+
|
|
5284
|
+
### Card Layout
|
|
5285
|
+
|
|
5286
|
+
```jsx
|
|
5287
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5288
|
+
|
|
5289
|
+
const CardLayoutExample = () => {
|
|
5290
|
+
const cards = [
|
|
5291
|
+
{ id: 1, title: "Card 1", description: "Description 1" },
|
|
5292
|
+
{ id: 2, title: "Card 2", description: "Description 2" },
|
|
5293
|
+
{ id: 3, title: "Card 3", description: "Description 3" },
|
|
5294
|
+
{ id: 4, title: "Card 4", description: "Description 4" },
|
|
5295
|
+
];
|
|
5296
|
+
|
|
5297
|
+
return (
|
|
5298
|
+
<Grid cols={{ base: 1, md: 2, lg: 4 }} gap={4}>
|
|
5299
|
+
{cards.map((card) => (
|
|
5300
|
+
<GridItem key={card.id}>
|
|
5301
|
+
<div className="p-4 border rounded-lg shadow-sm">
|
|
5302
|
+
<h3 className="font-bold text-lg">{card.title}</h3>
|
|
5303
|
+
<p className="text-gray-600">{card.description}</p>
|
|
5304
|
+
</div>
|
|
5305
|
+
</GridItem>
|
|
5306
|
+
))}
|
|
5307
|
+
</Grid>
|
|
5308
|
+
);
|
|
5309
|
+
};
|
|
5310
|
+
```
|
|
5311
|
+
|
|
5312
|
+
### Dashboard Layout
|
|
5313
|
+
|
|
5314
|
+
```jsx
|
|
5315
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5316
|
+
|
|
5317
|
+
const DashboardExample = () => {
|
|
5318
|
+
return (
|
|
5319
|
+
<Grid cols={{ base: 1, lg: 12 }} gap={4}>
|
|
5320
|
+
{/* Header - Full width */}
|
|
5321
|
+
<GridItem colSpan={{ base: 1, lg: 12 }}>
|
|
5322
|
+
<div className="p-6 bg-blue-500 text-white rounded-lg">
|
|
5323
|
+
<h1 className="text-2xl font-bold">Dashboard Header</h1>
|
|
5324
|
+
</div>
|
|
5325
|
+
</GridItem>
|
|
5326
|
+
|
|
5327
|
+
{/* Sidebar - Full width on mobile, 3 cols on desktop */}
|
|
5328
|
+
<GridItem colSpan={{ base: 1, lg: 3 }}>
|
|
5329
|
+
<div className="p-6 bg-gray-100 rounded-lg h-full">
|
|
5330
|
+
<h2 className="font-bold mb-4">Sidebar</h2>
|
|
5331
|
+
<ul className="space-y-2">
|
|
5332
|
+
<li>Menu Item 1</li>
|
|
5333
|
+
<li>Menu Item 2</li>
|
|
5334
|
+
<li>Menu Item 3</li>
|
|
5335
|
+
</ul>
|
|
5336
|
+
</div>
|
|
5337
|
+
</GridItem>
|
|
5338
|
+
|
|
5339
|
+
{/* Main content - Full width on mobile, 9 cols on desktop */}
|
|
5340
|
+
<GridItem colSpan={{ base: 1, lg: 9 }}>
|
|
5341
|
+
<div className="p-6 bg-white border rounded-lg">
|
|
5342
|
+
<h2 className="font-bold text-xl mb-4">Main Content</h2>
|
|
5343
|
+
<p>Your main content goes here...</p>
|
|
5344
|
+
</div>
|
|
5345
|
+
</GridItem>
|
|
5346
|
+
</Grid>
|
|
5347
|
+
);
|
|
5348
|
+
};
|
|
5349
|
+
```
|
|
5350
|
+
|
|
5351
|
+
### Image Gallery
|
|
5352
|
+
|
|
5353
|
+
```jsx
|
|
5354
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5355
|
+
|
|
5356
|
+
const GalleryExample = () => {
|
|
5357
|
+
const images = [
|
|
5358
|
+
{ id: 1, src: "image1.jpg", featured: true },
|
|
5359
|
+
{ id: 2, src: "image2.jpg", featured: false },
|
|
5360
|
+
{ id: 3, src: "image3.jpg", featured: false },
|
|
5361
|
+
{ id: 4, src: "image4.jpg", featured: false },
|
|
5362
|
+
{ id: 5, src: "image5.jpg", featured: true },
|
|
5363
|
+
{ id: 6, src: "image6.jpg", featured: false },
|
|
5364
|
+
];
|
|
5365
|
+
|
|
5366
|
+
return (
|
|
5367
|
+
<Grid cols={{ base: 2, md: 4, lg: 6 }} gap={2}>
|
|
5368
|
+
{images.map((image) => (
|
|
5369
|
+
<GridItem
|
|
5370
|
+
key={image.id}
|
|
5371
|
+
colSpan={
|
|
5372
|
+
image.featured
|
|
5373
|
+
? { base: 2, md: 2, lg: 3 }
|
|
5374
|
+
: { base: 1, md: 1, lg: 1 }
|
|
5375
|
+
}
|
|
5376
|
+
>
|
|
5377
|
+
<div className="aspect-square bg-gray-200 rounded-lg overflow-hidden">
|
|
5378
|
+
<img
|
|
5379
|
+
src={image.src}
|
|
5380
|
+
alt={`Image ${image.id}`}
|
|
5381
|
+
className="w-full h-full object-cover"
|
|
5382
|
+
/>
|
|
5383
|
+
</div>
|
|
5384
|
+
</GridItem>
|
|
5385
|
+
))}
|
|
5386
|
+
</Grid>
|
|
5387
|
+
);
|
|
5388
|
+
};
|
|
5389
|
+
```
|
|
5390
|
+
|
|
5391
|
+
### Complete Example
|
|
5392
|
+
|
|
5393
|
+
```jsx
|
|
5394
|
+
import { Grid, GridItem } from "alope-ui";
|
|
5395
|
+
|
|
5396
|
+
const CompleteExample = () => {
|
|
5397
|
+
return (
|
|
5398
|
+
<div className="p-8">
|
|
5399
|
+
<h1 className="text-3xl font-bold mb-8">Responsive Grid Layout</h1>
|
|
5400
|
+
|
|
5401
|
+
<Grid
|
|
5402
|
+
cols={{ base: 1, sm: 2, md: 3, lg: 4, xl: 6 }}
|
|
5403
|
+
gap={{ base: 2, md: 4, lg: 6 }}
|
|
5404
|
+
>
|
|
5405
|
+
{/* Hero section - Full width */}
|
|
5406
|
+
<GridItem colSpan={{ base: 1, sm: 2, md: 3, lg: 4, xl: 6 }}>
|
|
5407
|
+
<div className="p-8 bg-gradient-to-r from-blue-500 to-purple-600 text-white rounded-lg">
|
|
5408
|
+
<h2 className="text-2xl font-bold mb-2">Hero Section</h2>
|
|
5409
|
+
<p>Full width across all breakpoints</p>
|
|
5410
|
+
</div>
|
|
5411
|
+
</GridItem>
|
|
5412
|
+
|
|
5413
|
+
{/* Feature cards */}
|
|
5414
|
+
<GridItem colSpan={{ base: 1, sm: 1, md: 1, lg: 2, xl: 2 }}>
|
|
5415
|
+
<div className="p-6 bg-blue-100 rounded-lg h-full">
|
|
5416
|
+
<h3 className="font-bold mb-2">Feature 1</h3>
|
|
5417
|
+
<p className="text-sm">Description of feature 1</p>
|
|
5418
|
+
</div>
|
|
5419
|
+
</GridItem>
|
|
5420
|
+
|
|
5421
|
+
<GridItem colSpan={{ base: 1, sm: 1, md: 1, lg: 2, xl: 2 }}>
|
|
5422
|
+
<div className="p-6 bg-green-100 rounded-lg h-full">
|
|
5423
|
+
<h3 className="font-bold mb-2">Feature 2</h3>
|
|
5424
|
+
<p className="text-sm">Description of feature 2</p>
|
|
5425
|
+
</div>
|
|
5426
|
+
</GridItem>
|
|
5427
|
+
|
|
5428
|
+
<GridItem colSpan={{ base: 1, sm: 2, md: 1, lg: 4, xl: 2 }}>
|
|
5429
|
+
<div className="p-6 bg-purple-100 rounded-lg h-full">
|
|
5430
|
+
<h3 className="font-bold mb-2">Feature 3</h3>
|
|
5431
|
+
<p className="text-sm">Description of feature 3</p>
|
|
5432
|
+
</div>
|
|
5433
|
+
</GridItem>
|
|
5434
|
+
|
|
5435
|
+
{/* Grid items */}
|
|
5436
|
+
{[1, 2, 3, 4, 5, 6].map((item) => (
|
|
5437
|
+
<GridItem key={item}>
|
|
5438
|
+
<div className="p-4 border rounded-lg text-center">
|
|
5439
|
+
<p className="font-semibold">Item {item}</p>
|
|
5440
|
+
</div>
|
|
5441
|
+
</GridItem>
|
|
5442
|
+
))}
|
|
5443
|
+
|
|
5444
|
+
{/* Footer - Full width */}
|
|
5445
|
+
<GridItem colSpan={{ base: 1, sm: 2, md: 3, lg: 4, xl: 6 }}>
|
|
5446
|
+
<div className="p-6 bg-gray-800 text-white rounded-lg text-center">
|
|
5447
|
+
<p>Footer - Full width</p>
|
|
5448
|
+
</div>
|
|
5449
|
+
</GridItem>
|
|
5450
|
+
</Grid>
|
|
5451
|
+
</div>
|
|
5452
|
+
);
|
|
5453
|
+
};
|
|
5454
|
+
```
|
|
5455
|
+
|
|
5456
|
+
## Masonry
|
|
5457
|
+
|
|
5458
|
+
The Masonry component provides a Pinterest-style masonry layout using CSS columns, perfect for displaying items of varying heights in a visually appealing grid.
|
|
5459
|
+
|
|
5460
|
+
### Import
|
|
5461
|
+
|
|
5462
|
+
```jsx
|
|
5463
|
+
import { Masonry } from "alope-ui";
|
|
5464
|
+
```
|
|
5465
|
+
|
|
5466
|
+
### Props
|
|
5467
|
+
|
|
5468
|
+
| Prop | Type | Default | Description |
|
|
5469
|
+
| ----------- | ----------------- | ----------- | -------------------------------------- |
|
|
5470
|
+
| `children` | `React.ReactNode` | `required` | Items to display in masonry layout |
|
|
5471
|
+
| `cols` | `ResponsiveCols` | `undefined` | Number of columns (1-12) or responsive |
|
|
5472
|
+
| `gap` | `ResponsiveGap` | `4` | Gap between items (1-6) or responsive |
|
|
5473
|
+
| `className` | `string` | `""` | Additional CSS class |
|
|
5474
|
+
|
|
5475
|
+
### Responsive Types
|
|
5476
|
+
|
|
5477
|
+
```typescript
|
|
5478
|
+
type ResponsiveValue<T> = T | Partial<Record<Breakpoint, T>>
|
|
5479
|
+
type Breakpoint = 'base' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'
|
|
5480
|
+
|
|
5481
|
+
// Examples:
|
|
5482
|
+
cols={3} // Same for all breakpoints
|
|
5483
|
+
cols={{ base: 1, md: 2, lg: 3 }} // Responsive columns
|
|
5484
|
+
gap={4} // Same for all breakpoints
|
|
5485
|
+
gap={{ base: 2, md: 4, lg: 6 }} // Responsive gaps
|
|
5486
|
+
```
|
|
5487
|
+
|
|
5488
|
+
### Basic Masonry
|
|
5489
|
+
|
|
5490
|
+
```jsx
|
|
5491
|
+
import { Masonry } from "alope-ui";
|
|
5492
|
+
|
|
5493
|
+
const BasicExample = () => {
|
|
5494
|
+
return (
|
|
5495
|
+
<Masonry cols={3} gap={4}>
|
|
5496
|
+
<div className="p-4 bg-blue-100 rounded">Item 1</div>
|
|
5497
|
+
<div className="p-4 bg-green-100 rounded">Item 2</div>
|
|
5498
|
+
<div className="p-4 bg-purple-100 rounded">Item 3</div>
|
|
5499
|
+
<div className="p-4 bg-yellow-100 rounded">Item 4</div>
|
|
5500
|
+
<div className="p-4 bg-pink-100 rounded">Item 5</div>
|
|
5501
|
+
<div className="p-4 bg-indigo-100 rounded">Item 6</div>
|
|
5502
|
+
</Masonry>
|
|
5503
|
+
);
|
|
5504
|
+
};
|
|
5505
|
+
```
|
|
5506
|
+
|
|
5507
|
+
### Responsive Columns
|
|
5508
|
+
|
|
5509
|
+
```jsx
|
|
5510
|
+
{
|
|
5511
|
+
/* 1 column on mobile, 2 on tablet, 3 on desktop */
|
|
5512
|
+
}
|
|
5513
|
+
<Masonry cols={{ base: 1, md: 2, lg: 3 }} gap={4}>
|
|
5514
|
+
<div className="p-4 bg-blue-100 rounded">Item 1</div>
|
|
5515
|
+
<div className="p-4 bg-green-100 rounded">Item 2</div>
|
|
5516
|
+
<div className="p-4 bg-purple-100 rounded">Item 3</div>
|
|
5517
|
+
<div className="p-4 bg-yellow-100 rounded">Item 4</div>
|
|
5518
|
+
<div className="p-4 bg-pink-100 rounded">Item 5</div>
|
|
5519
|
+
<div className="p-4 bg-indigo-100 rounded">Item 6</div>
|
|
5520
|
+
</Masonry>;
|
|
5521
|
+
```
|
|
5522
|
+
|
|
5523
|
+
### Responsive Gaps
|
|
5524
|
+
|
|
5525
|
+
```jsx
|
|
5526
|
+
{
|
|
5527
|
+
/* Small gap on mobile, larger on desktop */
|
|
5528
|
+
}
|
|
5529
|
+
<Masonry cols={3} gap={{ base: 2, md: 4, lg: 6 }}>
|
|
5530
|
+
<div className="p-4 bg-blue-100 rounded">Item 1</div>
|
|
5531
|
+
<div className="p-4 bg-green-100 rounded">Item 2</div>
|
|
5532
|
+
<div className="p-4 bg-purple-100 rounded">Item 3</div>
|
|
5533
|
+
</Masonry>;
|
|
5534
|
+
```
|
|
5535
|
+
|
|
5536
|
+
### Image Gallery
|
|
5537
|
+
|
|
5538
|
+
```jsx
|
|
5539
|
+
import { Masonry } from "alope-ui";
|
|
5540
|
+
|
|
5541
|
+
const ImageGalleryExample = () => {
|
|
5542
|
+
const images = [
|
|
5543
|
+
{ id: 1, src: "image1.jpg", height: "h-64" },
|
|
5544
|
+
{ id: 2, src: "image2.jpg", height: "h-48" },
|
|
5545
|
+
{ id: 3, src: "image3.jpg", height: "h-80" },
|
|
5546
|
+
{ id: 4, src: "image4.jpg", height: "h-56" },
|
|
5547
|
+
{ id: 5, src: "image5.jpg", height: "h-72" },
|
|
5548
|
+
{ id: 6, src: "image6.jpg", height: "h-40" },
|
|
5549
|
+
];
|
|
5550
|
+
|
|
5551
|
+
return (
|
|
5552
|
+
<Masonry cols={{ base: 2, md: 3, lg: 4 }} gap={3}>
|
|
5553
|
+
{images.map((image) => (
|
|
5554
|
+
<div key={image.id} className="break-inside-avoid mb-3">
|
|
5555
|
+
<img
|
|
5556
|
+
src={image.src}
|
|
5557
|
+
alt={`Image ${image.id}`}
|
|
5558
|
+
className={`w-full ${image.height} object-cover rounded-lg`}
|
|
5559
|
+
/>
|
|
5560
|
+
</div>
|
|
5561
|
+
))}
|
|
5562
|
+
</Masonry>
|
|
5563
|
+
);
|
|
5564
|
+
};
|
|
5565
|
+
```
|
|
5566
|
+
|
|
5567
|
+
### Card Masonry
|
|
5568
|
+
|
|
5569
|
+
```jsx
|
|
5570
|
+
import { Masonry } from "alope-ui";
|
|
5571
|
+
|
|
5572
|
+
const CardMasonryExample = () => {
|
|
5573
|
+
const cards = [
|
|
5574
|
+
{ id: 1, title: "Card 1", content: "Short content" },
|
|
5575
|
+
{
|
|
5576
|
+
id: 2,
|
|
5577
|
+
title: "Card 2",
|
|
5578
|
+
content:
|
|
5579
|
+
"This is a longer content that will make the card taller than others in the masonry layout.",
|
|
5580
|
+
},
|
|
5581
|
+
{ id: 3, title: "Card 3", content: "Medium length content here." },
|
|
5582
|
+
{ id: 4, title: "Card 4", content: "Another short one." },
|
|
5583
|
+
{
|
|
5584
|
+
id: 5,
|
|
5585
|
+
title: "Card 5",
|
|
5586
|
+
content:
|
|
5587
|
+
"This card has quite a bit more content to display, making it one of the taller cards in the layout.",
|
|
5588
|
+
},
|
|
5589
|
+
];
|
|
5590
|
+
|
|
5591
|
+
return (
|
|
5592
|
+
<Masonry cols={{ base: 1, sm: 2, lg: 3 }} gap={4}>
|
|
5593
|
+
{cards.map((card) => (
|
|
5594
|
+
<div
|
|
5595
|
+
key={card.id}
|
|
5596
|
+
className="break-inside-avoid mb-4 p-6 bg-white border rounded-lg shadow-sm"
|
|
5597
|
+
>
|
|
5598
|
+
<h3 className="font-bold text-lg mb-2">{card.title}</h3>
|
|
5599
|
+
<p className="text-gray-600">{card.content}</p>
|
|
5600
|
+
</div>
|
|
5601
|
+
))}
|
|
5602
|
+
</Masonry>
|
|
5603
|
+
);
|
|
5604
|
+
};
|
|
5605
|
+
```
|
|
5606
|
+
|
|
5607
|
+
### Pinterest-Style Layout
|
|
5608
|
+
|
|
5609
|
+
```jsx
|
|
5610
|
+
import { Masonry } from "alope-ui";
|
|
5611
|
+
|
|
5612
|
+
const PinterestExample = () => {
|
|
5613
|
+
const pins = [
|
|
5614
|
+
{ id: 1, image: "pin1.jpg", title: "Design Inspiration", height: "h-64" },
|
|
5615
|
+
{ id: 2, image: "pin2.jpg", title: "Photography", height: "h-80" },
|
|
5616
|
+
{ id: 3, image: "pin3.jpg", title: "Architecture", height: "h-56" },
|
|
5617
|
+
{ id: 4, image: "pin4.jpg", title: "Nature", height: "h-72" },
|
|
5618
|
+
{ id: 5, image: "pin5.jpg", title: "Food", height: "h-48" },
|
|
5619
|
+
{ id: 6, image: "pin6.jpg", title: "Travel", height: "h-96" },
|
|
5620
|
+
];
|
|
5621
|
+
|
|
5622
|
+
return (
|
|
5623
|
+
<Masonry cols={{ base: 2, md: 3, lg: 4, xl: 5 }} gap={{ base: 2, md: 3 }}>
|
|
5624
|
+
{pins.map((pin) => (
|
|
5625
|
+
<div
|
|
5626
|
+
key={pin.id}
|
|
5627
|
+
className="break-inside-avoid mb-3 group cursor-pointer"
|
|
5628
|
+
>
|
|
5629
|
+
<div className="relative overflow-hidden rounded-lg">
|
|
5630
|
+
<img
|
|
5631
|
+
src={pin.image}
|
|
5632
|
+
alt={pin.title}
|
|
5633
|
+
className={`w-full ${pin.height} object-cover transition-transform group-hover:scale-105`}
|
|
5634
|
+
/>
|
|
5635
|
+
<div className="absolute inset-0 bg-black bg-opacity-0 group-hover:bg-opacity-30 transition-opacity" />
|
|
5636
|
+
<div className="absolute bottom-0 left-0 right-0 p-4 text-white opacity-0 group-hover:opacity-100 transition-opacity">
|
|
5637
|
+
<h3 className="font-semibold">{pin.title}</h3>
|
|
5638
|
+
</div>
|
|
5639
|
+
</div>
|
|
5640
|
+
</div>
|
|
5641
|
+
))}
|
|
5642
|
+
</Masonry>
|
|
5643
|
+
);
|
|
5644
|
+
};
|
|
5645
|
+
```
|
|
5646
|
+
|
|
5647
|
+
### Blog Post Grid
|
|
5648
|
+
|
|
5649
|
+
```jsx
|
|
5650
|
+
import { Masonry } from "alope-ui";
|
|
5651
|
+
|
|
5652
|
+
const BlogGridExample = () => {
|
|
5653
|
+
const posts = [
|
|
5654
|
+
{
|
|
5655
|
+
id: 1,
|
|
5656
|
+
title: "Getting Started with React",
|
|
5657
|
+
excerpt: "Learn the basics of React in this comprehensive guide.",
|
|
5658
|
+
image: "post1.jpg",
|
|
5659
|
+
date: "Jan 15, 2024",
|
|
5660
|
+
},
|
|
5661
|
+
{
|
|
5662
|
+
id: 2,
|
|
5663
|
+
title: "Advanced TypeScript Patterns",
|
|
5664
|
+
excerpt:
|
|
5665
|
+
"Dive deep into advanced TypeScript patterns and best practices for building scalable applications.",
|
|
5666
|
+
image: "post2.jpg",
|
|
5667
|
+
date: "Jan 20, 2024",
|
|
5668
|
+
},
|
|
5669
|
+
{
|
|
5670
|
+
id: 3,
|
|
5671
|
+
title: "CSS Grid vs Flexbox",
|
|
5672
|
+
excerpt: "Understanding when to use Grid and when to use Flexbox.",
|
|
5673
|
+
image: "post3.jpg",
|
|
5674
|
+
date: "Jan 25, 2024",
|
|
5675
|
+
},
|
|
5676
|
+
];
|
|
5677
|
+
|
|
5678
|
+
return (
|
|
5679
|
+
<Masonry cols={{ base: 1, md: 2, lg: 3 }} gap={5}>
|
|
5680
|
+
{posts.map((post) => (
|
|
5681
|
+
<article
|
|
5682
|
+
key={post.id}
|
|
5683
|
+
className="break-inside-avoid mb-5 bg-white rounded-xl overflow-hidden shadow-lg hover:shadow-xl transition-shadow"
|
|
5684
|
+
>
|
|
5685
|
+
<img
|
|
5686
|
+
src={post.image}
|
|
5687
|
+
alt={post.title}
|
|
5688
|
+
className="w-full h-48 object-cover"
|
|
5689
|
+
/>
|
|
5690
|
+
<div className="p-6">
|
|
5691
|
+
<time className="text-sm text-gray-500">{post.date}</time>
|
|
5692
|
+
<h2 className="text-xl font-bold mt-2 mb-3">{post.title}</h2>
|
|
5693
|
+
<p className="text-gray-600">{post.excerpt}</p>
|
|
5694
|
+
<button className="mt-4 text-blue-600 hover:text-blue-800 font-medium">
|
|
5695
|
+
Read More →
|
|
5696
|
+
</button>
|
|
5697
|
+
</div>
|
|
5698
|
+
</article>
|
|
5699
|
+
))}
|
|
5700
|
+
</Masonry>
|
|
5701
|
+
);
|
|
5702
|
+
};
|
|
5703
|
+
```
|
|
5704
|
+
|
|
5705
|
+
### Complete Example
|
|
5706
|
+
|
|
5707
|
+
```jsx
|
|
5708
|
+
import { Masonry } from "alope-ui";
|
|
5709
|
+
import { Heart, MessageCircle, Share2 } from "lucide-react";
|
|
5710
|
+
|
|
5711
|
+
const CompleteExample = () => {
|
|
5712
|
+
const items = [
|
|
5713
|
+
{
|
|
5714
|
+
id: 1,
|
|
5715
|
+
type: "image",
|
|
5716
|
+
src: "nature1.jpg",
|
|
5717
|
+
title: "Mountain Landscape",
|
|
5718
|
+
likes: 234,
|
|
5719
|
+
comments: 12,
|
|
5720
|
+
height: "h-64",
|
|
5721
|
+
},
|
|
5722
|
+
{
|
|
5723
|
+
id: 2,
|
|
5724
|
+
type: "quote",
|
|
5725
|
+
text: "Design is not just what it looks like and feels like. Design is how it works.",
|
|
5726
|
+
author: "Steve Jobs",
|
|
5727
|
+
height: "h-48",
|
|
5728
|
+
},
|
|
5729
|
+
{
|
|
5730
|
+
id: 3,
|
|
5731
|
+
type: "image",
|
|
5732
|
+
src: "architecture.jpg",
|
|
5733
|
+
title: "Modern Architecture",
|
|
5734
|
+
likes: 567,
|
|
5735
|
+
comments: 23,
|
|
5736
|
+
height: "h-80",
|
|
5737
|
+
},
|
|
5738
|
+
{
|
|
5739
|
+
id: 4,
|
|
5740
|
+
type: "article",
|
|
5741
|
+
title: "The Future of Web Development",
|
|
5742
|
+
excerpt:
|
|
5743
|
+
"Exploring upcoming trends and technologies that will shape the future of web development.",
|
|
5744
|
+
readTime: "5 min read",
|
|
5745
|
+
height: "auto",
|
|
5746
|
+
},
|
|
5747
|
+
{
|
|
5748
|
+
id: 5,
|
|
5749
|
+
type: "image",
|
|
5750
|
+
src: "food.jpg",
|
|
5751
|
+
title: "Culinary Art",
|
|
5752
|
+
likes: 189,
|
|
5753
|
+
comments: 8,
|
|
5754
|
+
height: "h-56",
|
|
5755
|
+
},
|
|
5756
|
+
];
|
|
5757
|
+
|
|
5758
|
+
return (
|
|
5759
|
+
<div className="p-8 bg-gray-50 min-h-screen">
|
|
5760
|
+
<h1 className="text-3xl font-bold mb-8">Masonry Gallery</h1>
|
|
5761
|
+
|
|
5762
|
+
<Masonry cols={{ base: 1, sm: 2, md: 3, lg: 4 }} gap={{ base: 3, md: 4 }}>
|
|
5763
|
+
{items.map((item) => (
|
|
5764
|
+
<div key={item.id} className="break-inside-avoid mb-4">
|
|
5765
|
+
{item.type === "image" && (
|
|
5766
|
+
<div className="bg-white rounded-lg overflow-hidden shadow-md hover:shadow-xl transition-shadow">
|
|
5767
|
+
<img
|
|
5768
|
+
src={item.src}
|
|
5769
|
+
alt={item.title}
|
|
5770
|
+
className={`w-full ${item.height} object-cover`}
|
|
5771
|
+
/>
|
|
5772
|
+
<div className="p-4">
|
|
5773
|
+
<h3 className="font-semibold mb-3">{item.title}</h3>
|
|
5774
|
+
<div className="flex items-center gap-4 text-gray-600">
|
|
5775
|
+
<button className="flex items-center gap-1 hover:text-red-500">
|
|
5776
|
+
<Heart size={18} />
|
|
5777
|
+
<span className="text-sm">{item.likes}</span>
|
|
5778
|
+
</button>
|
|
5779
|
+
<button className="flex items-center gap-1 hover:text-blue-500">
|
|
5780
|
+
<MessageCircle size={18} />
|
|
5781
|
+
<span className="text-sm">{item.comments}</span>
|
|
5782
|
+
</button>
|
|
5783
|
+
<button className="flex items-center gap-1 hover:text-green-500">
|
|
5784
|
+
<Share2 size={18} />
|
|
5785
|
+
</button>
|
|
5786
|
+
</div>
|
|
5787
|
+
</div>
|
|
5788
|
+
</div>
|
|
5789
|
+
)}
|
|
5790
|
+
|
|
5791
|
+
{item.type === "quote" && (
|
|
5792
|
+
<div
|
|
5793
|
+
className={`bg-gradient-to-br from-purple-500 to-pink-500 ${item.height} rounded-lg p-6 text-white flex flex-col justify-center shadow-md`}
|
|
5794
|
+
>
|
|
5795
|
+
<p className="text-xl italic mb-4">"{item.text}"</p>
|
|
5796
|
+
<p className="text-sm opacity-90">— {item.author}</p>
|
|
5797
|
+
</div>
|
|
5798
|
+
)}
|
|
5799
|
+
|
|
5800
|
+
{item.type === "article" && (
|
|
5801
|
+
<div className="bg-white rounded-lg p-6 shadow-md hover:shadow-xl transition-shadow">
|
|
5802
|
+
<h3 className="font-bold text-lg mb-2">{item.title}</h3>
|
|
5803
|
+
<p className="text-gray-600 mb-4">{item.excerpt}</p>
|
|
5804
|
+
<div className="flex items-center justify-between text-sm">
|
|
5805
|
+
<span className="text-gray-500">{item.readTime}</span>
|
|
5806
|
+
<button className="text-blue-600 hover:text-blue-800 font-medium">
|
|
5807
|
+
Read More →
|
|
5808
|
+
</button>
|
|
5809
|
+
</div>
|
|
5810
|
+
</div>
|
|
5811
|
+
)}
|
|
5812
|
+
</div>
|
|
5813
|
+
))}
|
|
5814
|
+
</Masonry>
|
|
5815
|
+
</div>
|
|
5816
|
+
);
|
|
5817
|
+
};
|
|
5818
|
+
```
|
|
5819
|
+
|
|
5820
|
+
## Slider
|
|
5821
|
+
|
|
5822
|
+
The Slider component provides an interactive range input with tooltips, marks, and customizable styling options.
|
|
5823
|
+
|
|
5824
|
+
### Import
|
|
5825
|
+
|
|
5826
|
+
```jsx
|
|
5827
|
+
import { Slider } from "alope-ui";
|
|
5828
|
+
```
|
|
5829
|
+
|
|
5830
|
+
### Props
|
|
5831
|
+
|
|
5832
|
+
| Prop | Type | Default | Description |
|
|
5833
|
+
| ------------- | ------------------------------------------------------------------------- | ----------- | --------------------------- |
|
|
5834
|
+
| `label` | `string` | `undefined` | Label text for the slider |
|
|
5835
|
+
| `value` | `number` | `required` | Current slider value |
|
|
5836
|
+
| `onChange` | `(e: ChangeEvent<HTMLInputElement>) => void` | `required` | Change handler function |
|
|
5837
|
+
| `min` | `number` | `0` | Minimum value |
|
|
5838
|
+
| `max` | `number` | `100` | Maximum value |
|
|
5839
|
+
| `step` | `number` | `1` | Step increment |
|
|
5840
|
+
| `color` | `'primary' \| 'error' \| 'warning' \| 'success' \| 'info' \| 'secondary'` | `'primary'` | Color theme |
|
|
5841
|
+
| `sliderSize` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Size of the slider |
|
|
5842
|
+
| `showTooltip` | `boolean` | `true` | Show value tooltip on hover |
|
|
5843
|
+
| `showMarks` | `boolean` | `true` | Show step marks |
|
|
5844
|
+
| `prefixIcon` | `React.ReactNode` | `undefined` | Icon before slider |
|
|
5845
|
+
| `suffixIcon` | `React.ReactNode` | `undefined` | Icon after slider |
|
|
5846
|
+
| `disabled` | `boolean` | `false` | Disable slider interaction |
|
|
5847
|
+
| `id` | `string` | `undefined` | HTML id attribute |
|
|
5848
|
+
| `name` | `string` | `undefined` | HTML name attribute |
|
|
5849
|
+
|
|
5850
|
+
### Basic Slider
|
|
5851
|
+
|
|
5852
|
+
```jsx
|
|
5853
|
+
import { useState } from "react";
|
|
5854
|
+
import { Slider } from "alope-ui";
|
|
5855
|
+
|
|
5856
|
+
const BasicExample = () => {
|
|
5857
|
+
const [value, setValue] = useState(50);
|
|
5858
|
+
|
|
5859
|
+
return (
|
|
5860
|
+
<Slider
|
|
5861
|
+
label="Volume"
|
|
5862
|
+
value={value}
|
|
5863
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5864
|
+
/>
|
|
5865
|
+
);
|
|
5866
|
+
};
|
|
5867
|
+
```
|
|
5868
|
+
|
|
5869
|
+
### Range Configuration
|
|
5870
|
+
|
|
5871
|
+
```jsx
|
|
5872
|
+
import { useState } from "react";
|
|
5873
|
+
import { Slider } from "alope-ui";
|
|
5874
|
+
|
|
5875
|
+
const RangeExample = () => {
|
|
5876
|
+
const [value, setValue] = useState(25);
|
|
5877
|
+
|
|
5878
|
+
return (
|
|
5879
|
+
<Slider
|
|
5880
|
+
label="Select a value"
|
|
5881
|
+
value={value}
|
|
5882
|
+
min={0}
|
|
5883
|
+
max={100}
|
|
5884
|
+
step={5}
|
|
5885
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5886
|
+
/>
|
|
5887
|
+
);
|
|
5888
|
+
};
|
|
5889
|
+
```
|
|
5890
|
+
|
|
5891
|
+
### Colors
|
|
5892
|
+
|
|
5893
|
+
```jsx
|
|
5894
|
+
import { useState } from "react";
|
|
5895
|
+
import { Slider } from "alope-ui";
|
|
5896
|
+
|
|
5897
|
+
const ColorsExample = () => {
|
|
5898
|
+
const [value, setValue] = useState(50);
|
|
5899
|
+
|
|
5900
|
+
return (
|
|
5901
|
+
<div className="space-y-6">
|
|
5902
|
+
{/* Primary */}
|
|
5903
|
+
<Slider
|
|
5904
|
+
label="Primary"
|
|
5905
|
+
value={value}
|
|
5906
|
+
color="primary"
|
|
5907
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5908
|
+
/>
|
|
5909
|
+
|
|
5910
|
+
{/* Success */}
|
|
5911
|
+
<Slider
|
|
5912
|
+
label="Success"
|
|
5913
|
+
value={value}
|
|
5914
|
+
color="success"
|
|
5915
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5916
|
+
/>
|
|
5917
|
+
|
|
5918
|
+
{/* Error */}
|
|
5919
|
+
<Slider
|
|
5920
|
+
label="Error"
|
|
5921
|
+
value={value}
|
|
5922
|
+
color="error"
|
|
5923
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5924
|
+
/>
|
|
5925
|
+
|
|
5926
|
+
{/* Warning */}
|
|
5927
|
+
<Slider
|
|
5928
|
+
label="Warning"
|
|
5929
|
+
value={value}
|
|
5930
|
+
color="warning"
|
|
5931
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5932
|
+
/>
|
|
5933
|
+
|
|
5934
|
+
{/* Info */}
|
|
5935
|
+
<Slider
|
|
5936
|
+
label="Info"
|
|
5937
|
+
value={value}
|
|
5938
|
+
color="info"
|
|
5939
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5940
|
+
/>
|
|
5941
|
+
|
|
5942
|
+
{/* Secondary */}
|
|
5943
|
+
<Slider
|
|
5944
|
+
label="Secondary"
|
|
5945
|
+
value={value}
|
|
5946
|
+
color="secondary"
|
|
5947
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5948
|
+
/>
|
|
5949
|
+
</div>
|
|
5950
|
+
);
|
|
5951
|
+
};
|
|
5952
|
+
```
|
|
5953
|
+
|
|
5954
|
+
### Sizes
|
|
5955
|
+
|
|
5956
|
+
```jsx
|
|
5957
|
+
import { useState } from "react";
|
|
5958
|
+
import { Slider } from "alope-ui";
|
|
5959
|
+
|
|
5960
|
+
const SizesExample = () => {
|
|
5961
|
+
const [value, setValue] = useState(50);
|
|
5962
|
+
|
|
5963
|
+
return (
|
|
5964
|
+
<div className="space-y-6">
|
|
5965
|
+
{/* Small */}
|
|
5966
|
+
<Slider
|
|
5967
|
+
label="Small"
|
|
5968
|
+
value={value}
|
|
5969
|
+
sliderSize="sm"
|
|
5970
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5971
|
+
/>
|
|
5972
|
+
|
|
5973
|
+
{/* Medium */}
|
|
5974
|
+
<Slider
|
|
5975
|
+
label="Medium"
|
|
5976
|
+
value={value}
|
|
5977
|
+
sliderSize="md"
|
|
5978
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5979
|
+
/>
|
|
5980
|
+
|
|
5981
|
+
{/* Large */}
|
|
5982
|
+
<Slider
|
|
5983
|
+
label="Large"
|
|
5984
|
+
value={value}
|
|
5985
|
+
sliderSize="lg"
|
|
5986
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5987
|
+
/>
|
|
5988
|
+
|
|
5989
|
+
{/* Extra Large */}
|
|
5990
|
+
<Slider
|
|
5991
|
+
label="Extra Large"
|
|
5992
|
+
value={value}
|
|
5993
|
+
sliderSize="xl"
|
|
5994
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
5995
|
+
/>
|
|
5996
|
+
</div>
|
|
5997
|
+
);
|
|
5998
|
+
};
|
|
5999
|
+
```
|
|
6000
|
+
|
|
6001
|
+
### With Icons
|
|
6002
|
+
|
|
6003
|
+
```jsx
|
|
6004
|
+
import { useState } from "react";
|
|
6005
|
+
import { Slider } from "alope-ui";
|
|
6006
|
+
import { Volume, Volume2, VolumeX } from "lucide-react";
|
|
6007
|
+
|
|
6008
|
+
const IconExample = () => {
|
|
6009
|
+
const [volume, setVolume] = useState(50);
|
|
6010
|
+
const [brightness, setBrightness] = useState(75);
|
|
6011
|
+
|
|
6012
|
+
return (
|
|
6013
|
+
<div className="space-y-6">
|
|
6014
|
+
{/* Volume slider with icons */}
|
|
6015
|
+
<Slider
|
|
6016
|
+
label="Volume Control"
|
|
6017
|
+
value={volume}
|
|
6018
|
+
prefixIcon={<VolumeX size={20} className="text-gray-500" />}
|
|
6019
|
+
suffixIcon={<Volume2 size={20} className="text-gray-500" />}
|
|
6020
|
+
onChange={(e) => setVolume(Number(e.target.value))}
|
|
6021
|
+
/>
|
|
6022
|
+
|
|
6023
|
+
{/* Brightness slider */}
|
|
6024
|
+
<Slider
|
|
6025
|
+
label="Brightness"
|
|
6026
|
+
value={brightness}
|
|
6027
|
+
prefixIcon={<Volume size={20} className="text-gray-500" />}
|
|
6028
|
+
suffixIcon={<Volume2 size={20} className="text-gray-500" />}
|
|
6029
|
+
color="warning"
|
|
6030
|
+
onChange={(e) => setBrightness(Number(e.target.value))}
|
|
6031
|
+
/>
|
|
6032
|
+
</div>
|
|
6033
|
+
);
|
|
6034
|
+
};
|
|
6035
|
+
```
|
|
6036
|
+
|
|
6037
|
+
### Without Tooltip
|
|
6038
|
+
|
|
6039
|
+
```jsx
|
|
6040
|
+
import { useState } from "react";
|
|
6041
|
+
import { Slider } from "alope-ui";
|
|
6042
|
+
|
|
6043
|
+
const NoTooltipExample = () => {
|
|
6044
|
+
const [value, setValue] = useState(50);
|
|
6045
|
+
|
|
6046
|
+
return (
|
|
6047
|
+
<Slider
|
|
6048
|
+
label="No Tooltip"
|
|
6049
|
+
value={value}
|
|
6050
|
+
showTooltip={false}
|
|
6051
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
6052
|
+
/>
|
|
6053
|
+
);
|
|
6054
|
+
};
|
|
6055
|
+
```
|
|
6056
|
+
|
|
6057
|
+
### Without Marks
|
|
6058
|
+
|
|
6059
|
+
```jsx
|
|
6060
|
+
import { useState } from "react";
|
|
6061
|
+
import { Slider } from "alope-ui";
|
|
6062
|
+
|
|
6063
|
+
const NoMarksExample = () => {
|
|
6064
|
+
const [value, setValue] = useState(50);
|
|
6065
|
+
|
|
6066
|
+
return (
|
|
6067
|
+
<Slider
|
|
6068
|
+
label="No Marks"
|
|
6069
|
+
value={value}
|
|
6070
|
+
step={10}
|
|
6071
|
+
showMarks={false}
|
|
6072
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
6073
|
+
/>
|
|
6074
|
+
);
|
|
6075
|
+
};
|
|
6076
|
+
```
|
|
6077
|
+
|
|
6078
|
+
### Disabled State
|
|
6079
|
+
|
|
6080
|
+
```jsx
|
|
6081
|
+
import { useState } from "react";
|
|
6082
|
+
import { Slider } from "alope-ui";
|
|
6083
|
+
|
|
6084
|
+
const DisabledExample = () => {
|
|
6085
|
+
const [value, setValue] = useState(50);
|
|
6086
|
+
|
|
6087
|
+
return (
|
|
6088
|
+
<Slider
|
|
6089
|
+
label="Disabled Slider"
|
|
6090
|
+
value={value}
|
|
6091
|
+
disabled
|
|
6092
|
+
onChange={(e) => setValue(Number(e.target.value))}
|
|
6093
|
+
/>
|
|
6094
|
+
);
|
|
6095
|
+
};
|
|
6096
|
+
```
|
|
6097
|
+
|
|
6098
|
+
### Price Range Slider
|
|
6099
|
+
|
|
6100
|
+
```jsx
|
|
6101
|
+
import { useState } from "react";
|
|
6102
|
+
import { Slider } from "alope-ui";
|
|
6103
|
+
import { DollarSign } from "lucide-react";
|
|
6104
|
+
|
|
6105
|
+
const PriceRangeExample = () => {
|
|
6106
|
+
const [price, setPrice] = useState(500);
|
|
6107
|
+
|
|
6108
|
+
return (
|
|
6109
|
+
<Slider
|
|
6110
|
+
label="Price Range"
|
|
6111
|
+
value={price}
|
|
6112
|
+
min={0}
|
|
6113
|
+
max={1000}
|
|
6114
|
+
step={50}
|
|
6115
|
+
color="success"
|
|
6116
|
+
prefixIcon={<DollarSign size={20} className="text-green-600" />}
|
|
6117
|
+
onChange={(e) => setPrice(Number(e.target.value))}
|
|
6118
|
+
/>
|
|
6119
|
+
);
|
|
6120
|
+
};
|
|
6121
|
+
```
|
|
6122
|
+
|
|
6123
|
+
### Temperature Control
|
|
6124
|
+
|
|
6125
|
+
```jsx
|
|
6126
|
+
import { useState } from "react";
|
|
6127
|
+
import { Slider } from "alope-ui";
|
|
6128
|
+
import { Thermometer } from "lucide-react";
|
|
6129
|
+
|
|
6130
|
+
const TemperatureExample = () => {
|
|
6131
|
+
const [temperature, setTemperature] = useState(22);
|
|
6132
|
+
|
|
6133
|
+
const getColor = () => {
|
|
6134
|
+
if (temperature < 18) return "info";
|
|
6135
|
+
if (temperature > 26) return "error";
|
|
6136
|
+
return "success";
|
|
6137
|
+
};
|
|
6138
|
+
|
|
6139
|
+
return (
|
|
6140
|
+
<Slider
|
|
6141
|
+
label={`Temperature: ${temperature}°C`}
|
|
6142
|
+
value={temperature}
|
|
6143
|
+
min={16}
|
|
6144
|
+
max={30}
|
|
6145
|
+
step={1}
|
|
6146
|
+
color={getColor()}
|
|
6147
|
+
prefixIcon={<Thermometer size={20} />}
|
|
6148
|
+
onChange={(e) => setTemperature(Number(e.target.value))}
|
|
6149
|
+
/>
|
|
6150
|
+
);
|
|
6151
|
+
};
|
|
6152
|
+
```
|
|
6153
|
+
|
|
6154
|
+
### Complete Example
|
|
6155
|
+
|
|
6156
|
+
```jsx
|
|
6157
|
+
import { useState } from "react";
|
|
6158
|
+
import { Slider } from "alope-ui";
|
|
6159
|
+
import { Volume2, Sun, Gauge, Settings } from "lucide-react";
|
|
6160
|
+
|
|
6161
|
+
const CompleteExample = () => {
|
|
6162
|
+
const [settings, setSettings] = useState({
|
|
6163
|
+
volume: 75,
|
|
6164
|
+
brightness: 60,
|
|
6165
|
+
speed: 50,
|
|
6166
|
+
quality: 80,
|
|
6167
|
+
});
|
|
6168
|
+
|
|
6169
|
+
const updateSetting = (key, value) => {
|
|
6170
|
+
setSettings((prev) => ({ ...prev, [key]: value }));
|
|
6171
|
+
};
|
|
6172
|
+
|
|
6173
|
+
return (
|
|
6174
|
+
<div className="max-w-2xl mx-auto p-8 space-y-8">
|
|
6175
|
+
<div className="flex items-center gap-3 mb-8">
|
|
6176
|
+
<Settings size={32} className="text-primary" />
|
|
6177
|
+
<h1 className="text-2xl font-bold">System Settings</h1>
|
|
6178
|
+
</div>
|
|
6179
|
+
|
|
6180
|
+
{/* Volume Control */}
|
|
6181
|
+
<Slider
|
|
6182
|
+
label="Volume"
|
|
6183
|
+
value={settings.volume}
|
|
6184
|
+
min={0}
|
|
6185
|
+
max={100}
|
|
6186
|
+
step={5}
|
|
6187
|
+
color="primary"
|
|
6188
|
+
sliderSize="md"
|
|
6189
|
+
prefixIcon={<Volume2 size={20} className="text-gray-500" />}
|
|
6190
|
+
onChange={(e) => updateSetting("volume", Number(e.target.value))}
|
|
6191
|
+
/>
|
|
6192
|
+
|
|
6193
|
+
{/* Brightness Control */}
|
|
6194
|
+
<Slider
|
|
6195
|
+
label="Brightness"
|
|
6196
|
+
value={settings.brightness}
|
|
6197
|
+
min={0}
|
|
6198
|
+
max={100}
|
|
6199
|
+
step={10}
|
|
6200
|
+
color="warning"
|
|
6201
|
+
sliderSize="md"
|
|
6202
|
+
prefixIcon={<Sun size={20} className="text-yellow-500" />}
|
|
6203
|
+
onChange={(e) => updateSetting("brightness", Number(e.target.value))}
|
|
6204
|
+
/>
|
|
6205
|
+
|
|
6206
|
+
{/* Speed Control */}
|
|
6207
|
+
<Slider
|
|
6208
|
+
label="Playback Speed"
|
|
6209
|
+
value={settings.speed}
|
|
6210
|
+
min={25}
|
|
6211
|
+
max={200}
|
|
6212
|
+
step={25}
|
|
6213
|
+
color="info"
|
|
6214
|
+
sliderSize="md"
|
|
6215
|
+
prefixIcon={<Gauge size={20} className="text-blue-500" />}
|
|
6216
|
+
onChange={(e) => updateSetting("speed", Number(e.target.value))}
|
|
6217
|
+
/>
|
|
6218
|
+
|
|
6219
|
+
{/* Quality Control */}
|
|
6220
|
+
<Slider
|
|
6221
|
+
label="Video Quality"
|
|
6222
|
+
value={settings.quality}
|
|
6223
|
+
min={0}
|
|
6224
|
+
max={100}
|
|
6225
|
+
step={20}
|
|
6226
|
+
color="success"
|
|
6227
|
+
sliderSize="lg"
|
|
6228
|
+
onChange={(e) => updateSetting("quality", Number(e.target.value))}
|
|
6229
|
+
/>
|
|
6230
|
+
|
|
6231
|
+
{/* Settings Summary */}
|
|
6232
|
+
<div className="mt-8 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg">
|
|
6233
|
+
<h3 className="font-semibold mb-2">Current Settings:</h3>
|
|
6234
|
+
<ul className="space-y-1 text-sm">
|
|
6235
|
+
<li>Volume: {settings.volume}%</li>
|
|
6236
|
+
<li>Brightness: {settings.brightness}%</li>
|
|
6237
|
+
<li>Speed: {settings.speed}%</li>
|
|
6238
|
+
<li>Quality: {settings.quality}%</li>
|
|
6239
|
+
</ul>
|
|
6240
|
+
</div>
|
|
6241
|
+
</div>
|
|
6242
|
+
);
|
|
6243
|
+
};
|
|
6244
|
+
```
|
|
6245
|
+
|
|
6246
|
+
## DatePicker
|
|
6247
|
+
|
|
6248
|
+
The DatePicker component provides an interactive calendar interface for selecting dates with month and year navigation.
|
|
6249
|
+
|
|
6250
|
+
### Import
|
|
6251
|
+
|
|
6252
|
+
```jsx
|
|
6253
|
+
import { DatePicker } from "alope-ui";
|
|
6254
|
+
```
|
|
6255
|
+
|
|
6256
|
+
### Props
|
|
6257
|
+
|
|
6258
|
+
| Prop | Type | Default | Description |
|
|
6259
|
+
| ---------------- | ---------------------------------------------------------- | ----------- | ------------------------------ |
|
|
6260
|
+
| `selectedDate` | `Date` | `undefined` | Currently selected date |
|
|
6261
|
+
| `onDateChange` | `(date: Date) => void` | `undefined` | Callback when date is selected |
|
|
6262
|
+
| `label` | `string` | `undefined` | Label text for the input |
|
|
6263
|
+
| `color` | `'success' \| 'info' \| 'error' \| 'warning' \| 'default'` | `'default'` | Color theme |
|
|
6264
|
+
| `labelClassName` | `string` | `""` | Label CSS class |
|
|
6265
|
+
| `id` | `string` | `undefined` | HTML id attribute |
|
|
6266
|
+
| `name` | `string` | `undefined` | HTML name attribute |
|
|
6267
|
+
|
|
6268
|
+
### Basic DatePicker
|
|
6269
|
+
|
|
6270
|
+
```jsx
|
|
6271
|
+
import { useState } from "react";
|
|
6272
|
+
import { DatePicker } from "alope-ui";
|
|
6273
|
+
|
|
6274
|
+
const BasicExample = () => {
|
|
6275
|
+
const [selectedDate, setSelectedDate] = useState(new Date());
|
|
6276
|
+
|
|
6277
|
+
return (
|
|
6278
|
+
<DatePicker
|
|
6279
|
+
label="Select Date"
|
|
6280
|
+
selectedDate={selectedDate}
|
|
6281
|
+
onDateChange={setSelectedDate}
|
|
6282
|
+
/>
|
|
6283
|
+
);
|
|
6284
|
+
};
|
|
6285
|
+
```
|
|
6286
|
+
|
|
6287
|
+
### Without Initial Date
|
|
6288
|
+
|
|
6289
|
+
```jsx
|
|
6290
|
+
import { useState } from "react";
|
|
6291
|
+
import { DatePicker } from "alope-ui";
|
|
6292
|
+
|
|
6293
|
+
const NoInitialDateExample = () => {
|
|
6294
|
+
const [selectedDate, setSelectedDate] = useState(undefined);
|
|
6295
|
+
|
|
6296
|
+
return (
|
|
6297
|
+
<DatePicker
|
|
6298
|
+
label="Choose a date"
|
|
6299
|
+
selectedDate={selectedDate}
|
|
6300
|
+
onDateChange={setSelectedDate}
|
|
6301
|
+
/>
|
|
6302
|
+
);
|
|
6303
|
+
};
|
|
6304
|
+
```
|
|
6305
|
+
|
|
6306
|
+
### Custom Label Styling
|
|
6307
|
+
|
|
6308
|
+
```jsx
|
|
6309
|
+
import { useState } from "react";
|
|
6310
|
+
import { DatePicker } from "alope-ui";
|
|
6311
|
+
|
|
6312
|
+
const CustomLabelExample = () => {
|
|
6313
|
+
const [selectedDate, setSelectedDate] = useState(new Date());
|
|
6314
|
+
|
|
6315
|
+
return (
|
|
6316
|
+
<DatePicker
|
|
6317
|
+
label="Appointment Date"
|
|
6318
|
+
labelClassName="text-lg font-bold text-blue-600"
|
|
6319
|
+
selectedDate={selectedDate}
|
|
6320
|
+
onDateChange={setSelectedDate}
|
|
6321
|
+
/>
|
|
6322
|
+
);
|
|
6323
|
+
};
|
|
6324
|
+
```
|
|
6325
|
+
|
|
6326
|
+
### Colors
|
|
6327
|
+
|
|
6328
|
+
```jsx
|
|
6329
|
+
import { useState } from "react";
|
|
6330
|
+
import { DatePicker } from "alope-ui";
|
|
6331
|
+
|
|
6332
|
+
const ColorsExample = () => {
|
|
6333
|
+
const [selectedDate, setSelectedDate] = useState(new Date());
|
|
6334
|
+
|
|
6335
|
+
return (
|
|
6336
|
+
<div className="space-y-4">
|
|
6337
|
+
{/* Default */}
|
|
6338
|
+
<DatePicker
|
|
6339
|
+
label="Default"
|
|
6340
|
+
color="default"
|
|
6341
|
+
selectedDate={selectedDate}
|
|
6342
|
+
onDateChange={setSelectedDate}
|
|
6343
|
+
/>
|
|
6344
|
+
|
|
6345
|
+
{/* Success */}
|
|
6346
|
+
<DatePicker
|
|
6347
|
+
label="Success"
|
|
6348
|
+
color="success"
|
|
6349
|
+
selectedDate={selectedDate}
|
|
6350
|
+
onDateChange={setSelectedDate}
|
|
6351
|
+
/>
|
|
6352
|
+
|
|
6353
|
+
{/* Info */}
|
|
6354
|
+
<DatePicker
|
|
6355
|
+
label="Info"
|
|
6356
|
+
color="info"
|
|
6357
|
+
selectedDate={selectedDate}
|
|
6358
|
+
onDateChange={setSelectedDate}
|
|
6359
|
+
/>
|
|
6360
|
+
|
|
6361
|
+
{/* Warning */}
|
|
6362
|
+
<DatePicker
|
|
6363
|
+
label="Warning"
|
|
6364
|
+
color="warning"
|
|
6365
|
+
selectedDate={selectedDate}
|
|
6366
|
+
onDateChange={setSelectedDate}
|
|
6367
|
+
/>
|
|
6368
|
+
|
|
6369
|
+
{/* Error */}
|
|
6370
|
+
<DatePicker
|
|
6371
|
+
label="Error"
|
|
6372
|
+
color="error"
|
|
6373
|
+
selectedDate={selectedDate}
|
|
6374
|
+
onDateChange={setSelectedDate}
|
|
6375
|
+
/>
|
|
6376
|
+
</div>
|
|
6377
|
+
);
|
|
6378
|
+
};
|
|
6379
|
+
```
|
|
6380
|
+
|
|
6381
|
+
### Form Integration
|
|
6382
|
+
|
|
6383
|
+
```jsx
|
|
6384
|
+
import { useState } from "react";
|
|
6385
|
+
import { DatePicker } from "alope-ui";
|
|
6386
|
+
import { Button } from "alope-ui";
|
|
6387
|
+
|
|
6388
|
+
const FormExample = () => {
|
|
6389
|
+
const [birthDate, setBirthDate] = useState(undefined);
|
|
6390
|
+
|
|
6391
|
+
const handleSubmit = (e) => {
|
|
6392
|
+
e.preventDefault();
|
|
6393
|
+
console.log("Selected birth date:", birthDate);
|
|
6394
|
+
};
|
|
6395
|
+
|
|
6396
|
+
return (
|
|
6397
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
6398
|
+
<DatePicker
|
|
6399
|
+
label="Birth Date"
|
|
6400
|
+
id="birth-date"
|
|
6401
|
+
name="birthDate"
|
|
6402
|
+
selectedDate={birthDate}
|
|
6403
|
+
onDateChange={setBirthDate}
|
|
6404
|
+
/>
|
|
6405
|
+
|
|
6406
|
+
<Button type="submit" variant="solid" variantType="primary">
|
|
6407
|
+
Submit
|
|
6408
|
+
</Button>
|
|
6409
|
+
</form>
|
|
6410
|
+
);
|
|
6411
|
+
};
|
|
6412
|
+
```
|
|
6413
|
+
|
|
6414
|
+
### Multiple DatePickers
|
|
6415
|
+
|
|
6416
|
+
```jsx
|
|
6417
|
+
import { useState } from "react";
|
|
6418
|
+
import { DatePicker } from "alope-ui";
|
|
6419
|
+
|
|
6420
|
+
const MultipleDatePickersExample = () => {
|
|
6421
|
+
const [startDate, setStartDate] = useState(undefined);
|
|
6422
|
+
const [endDate, setEndDate] = useState(undefined);
|
|
6423
|
+
|
|
6424
|
+
return (
|
|
6425
|
+
<div className="space-y-4">
|
|
6426
|
+
<DatePicker
|
|
6427
|
+
label="Start Date"
|
|
6428
|
+
selectedDate={startDate}
|
|
6429
|
+
onDateChange={setStartDate}
|
|
6430
|
+
/>
|
|
6431
|
+
|
|
6432
|
+
<DatePicker
|
|
6433
|
+
label="End Date"
|
|
6434
|
+
selectedDate={endDate}
|
|
6435
|
+
onDateChange={setEndDate}
|
|
6436
|
+
/>
|
|
6437
|
+
|
|
6438
|
+
{startDate && endDate && (
|
|
6439
|
+
<div className="p-4 bg-blue-50 rounded-lg">
|
|
6440
|
+
<p className="text-sm">
|
|
6441
|
+
Duration: {Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24))}{" "}
|
|
6442
|
+
days
|
|
6443
|
+
</p>
|
|
6444
|
+
</div>
|
|
6445
|
+
)}
|
|
6446
|
+
</div>
|
|
6447
|
+
);
|
|
6448
|
+
};
|
|
6449
|
+
```
|
|
6450
|
+
|
|
6451
|
+
### Event Booking
|
|
6452
|
+
|
|
6453
|
+
```jsx
|
|
6454
|
+
import { useState } from "react";
|
|
6455
|
+
import { DatePicker } from "alope-ui";
|
|
6456
|
+
import { Button } from "alope-ui";
|
|
6457
|
+
import { Calendar, Clock } from "lucide-react";
|
|
6458
|
+
|
|
6459
|
+
const EventBookingExample = () => {
|
|
6460
|
+
const [eventDate, setEventDate] = useState(undefined);
|
|
6461
|
+
|
|
6462
|
+
const handleBooking = () => {
|
|
6463
|
+
if (eventDate) {
|
|
6464
|
+
alert(`Event booked for ${eventDate.toLocaleDateString()}`);
|
|
6465
|
+
}
|
|
6466
|
+
};
|
|
6467
|
+
|
|
6468
|
+
return (
|
|
6469
|
+
<div className="max-w-md mx-auto p-6 bg-white rounded-lg shadow-md">
|
|
6470
|
+
<div className="flex items-center gap-2 mb-6">
|
|
6471
|
+
<Calendar size={24} className="text-primary" />
|
|
6472
|
+
<h2 className="text-xl font-bold">Book an Event</h2>
|
|
6473
|
+
</div>
|
|
6474
|
+
|
|
6475
|
+
<DatePicker
|
|
6476
|
+
label="Event Date"
|
|
6477
|
+
selectedDate={eventDate}
|
|
6478
|
+
onDateChange={setEventDate}
|
|
6479
|
+
/>
|
|
6480
|
+
|
|
6481
|
+
{eventDate && (
|
|
6482
|
+
<div className="mt-4 p-4 bg-green-50 rounded-lg">
|
|
6483
|
+
<div className="flex items-center gap-2 text-green-700">
|
|
6484
|
+
<Clock size={16} />
|
|
6485
|
+
<p className="text-sm font-medium">
|
|
6486
|
+
Selected:{" "}
|
|
6487
|
+
{eventDate.toLocaleDateString("en-US", {
|
|
6488
|
+
weekday: "long",
|
|
6489
|
+
year: "numeric",
|
|
6490
|
+
month: "long",
|
|
6491
|
+
day: "numeric",
|
|
6492
|
+
})}
|
|
6493
|
+
</p>
|
|
6494
|
+
</div>
|
|
6495
|
+
</div>
|
|
6496
|
+
)}
|
|
6497
|
+
|
|
6498
|
+
<Button
|
|
6499
|
+
variant="solid"
|
|
6500
|
+
variantType="primary"
|
|
6501
|
+
className="w-full mt-6"
|
|
6502
|
+
onClick={handleBooking}
|
|
6503
|
+
disabled={!eventDate}
|
|
6504
|
+
>
|
|
6505
|
+
Confirm Booking
|
|
6506
|
+
</Button>
|
|
6507
|
+
</div>
|
|
6508
|
+
);
|
|
6509
|
+
};
|
|
6510
|
+
```
|
|
6511
|
+
|
|
6512
|
+
### Travel Booking
|
|
6513
|
+
|
|
6514
|
+
```jsx
|
|
6515
|
+
import { useState } from "react";
|
|
6516
|
+
import { DatePicker } from "alope-ui";
|
|
6517
|
+
import { Button } from "alope-ui";
|
|
6518
|
+
import { Plane, MapPin } from "lucide-react";
|
|
6519
|
+
|
|
6520
|
+
const TravelBookingExample = () => {
|
|
6521
|
+
const [departureDate, setDepartureDate] = useState(undefined);
|
|
6522
|
+
const [returnDate, setReturnDate] = useState(undefined);
|
|
6523
|
+
|
|
6524
|
+
const calculateNights = () => {
|
|
6525
|
+
if (departureDate && returnDate) {
|
|
6526
|
+
const nights = Math.ceil(
|
|
6527
|
+
(returnDate - departureDate) / (1000 * 60 * 60 * 24)
|
|
6528
|
+
);
|
|
6529
|
+
return nights > 0 ? nights : 0;
|
|
6530
|
+
}
|
|
6531
|
+
return 0;
|
|
6532
|
+
};
|
|
6533
|
+
|
|
6534
|
+
return (
|
|
6535
|
+
<div className="max-w-2xl mx-auto p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-lg">
|
|
6536
|
+
<div className="flex items-center gap-3 mb-8">
|
|
6537
|
+
<Plane size={32} className="text-indigo-600" />
|
|
6538
|
+
<h2 className="text-2xl font-bold text-gray-800">Flight Booking</h2>
|
|
6539
|
+
</div>
|
|
6540
|
+
|
|
6541
|
+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
6542
|
+
<DatePicker
|
|
6543
|
+
label="Departure Date"
|
|
6544
|
+
selectedDate={departureDate}
|
|
6545
|
+
onDateChange={setDepartureDate}
|
|
6546
|
+
/>
|
|
6547
|
+
|
|
6548
|
+
<DatePicker
|
|
6549
|
+
label="Return Date"
|
|
6550
|
+
selectedDate={returnDate}
|
|
6551
|
+
onDateChange={setReturnDate}
|
|
6552
|
+
/>
|
|
6553
|
+
</div>
|
|
6554
|
+
|
|
6555
|
+
{departureDate && returnDate && calculateNights() > 0 && (
|
|
6556
|
+
<div className="mt-6 p-4 bg-white rounded-lg shadow-sm">
|
|
6557
|
+
<div className="flex items-center justify-between">
|
|
6558
|
+
<div className="flex items-center gap-2">
|
|
6559
|
+
<MapPin size={20} className="text-indigo-600" />
|
|
6560
|
+
<span className="font-semibold text-gray-700">Trip Duration</span>
|
|
6561
|
+
</div>
|
|
6562
|
+
<span className="text-2xl font-bold text-indigo-600">
|
|
6563
|
+
{calculateNights()} {calculateNights() === 1 ? "night" : "nights"}
|
|
6564
|
+
</span>
|
|
6565
|
+
</div>
|
|
6566
|
+
</div>
|
|
6567
|
+
)}
|
|
6568
|
+
|
|
6569
|
+
<Button
|
|
6570
|
+
variant="solid"
|
|
6571
|
+
variantType="primary"
|
|
6572
|
+
className="w-full mt-8"
|
|
6573
|
+
disabled={!departureDate || !returnDate || calculateNights() <= 0}
|
|
6574
|
+
>
|
|
6575
|
+
Search Flights
|
|
6576
|
+
</Button>
|
|
6577
|
+
</div>
|
|
6578
|
+
);
|
|
6579
|
+
};
|
|
6580
|
+
```
|
|
6581
|
+
|
|
6582
|
+
### Complete Example
|
|
6583
|
+
|
|
6584
|
+
```jsx
|
|
6585
|
+
import { useState } from "react";
|
|
6586
|
+
import { DatePicker } from "alope-ui";
|
|
6587
|
+
import { Button } from "alope-ui";
|
|
6588
|
+
import { Calendar, User, Mail, Phone } from "lucide-react";
|
|
6589
|
+
import { TextInput } from "alope-ui";
|
|
6590
|
+
|
|
6591
|
+
const CompleteExample = () => {
|
|
6592
|
+
const [formData, setFormData] = useState({
|
|
6593
|
+
name: "",
|
|
6594
|
+
email: "",
|
|
6595
|
+
phone: "",
|
|
6596
|
+
appointmentDate: undefined,
|
|
6597
|
+
});
|
|
6598
|
+
|
|
6599
|
+
const handleSubmit = (e) => {
|
|
6600
|
+
e.preventDefault();
|
|
6601
|
+
console.log("Form submitted:", formData);
|
|
6602
|
+
alert("Appointment scheduled successfully!");
|
|
6603
|
+
};
|
|
6604
|
+
|
|
6605
|
+
const handleInputChange = (field, value) => {
|
|
6606
|
+
setFormData((prev) => ({ ...prev, [field]: value }));
|
|
6607
|
+
};
|
|
6608
|
+
|
|
6609
|
+
const isFormValid = () => {
|
|
6610
|
+
return (
|
|
6611
|
+
formData.name &&
|
|
6612
|
+
formData.email &&
|
|
6613
|
+
formData.phone &&
|
|
6614
|
+
formData.appointmentDate
|
|
6615
|
+
);
|
|
6616
|
+
};
|
|
6617
|
+
|
|
6618
|
+
return (
|
|
6619
|
+
<div className="max-w-2xl mx-auto p-8">
|
|
6620
|
+
<div className="bg-white rounded-xl shadow-lg p-8">
|
|
6621
|
+
<div className="flex items-center gap-3 mb-8">
|
|
6622
|
+
<Calendar size={32} className="text-primary" />
|
|
6623
|
+
<div>
|
|
6624
|
+
<h1 className="text-2xl font-bold text-gray-800">
|
|
6625
|
+
Schedule Appointment
|
|
6626
|
+
</h1>
|
|
6627
|
+
<p className="text-gray-600 text-sm">
|
|
6628
|
+
Fill in your details to book an appointment
|
|
6629
|
+
</p>
|
|
6630
|
+
</div>
|
|
6631
|
+
</div>
|
|
6632
|
+
|
|
6633
|
+
<form onSubmit={handleSubmit} className="space-y-6">
|
|
6634
|
+
{/* Name Input */}
|
|
6635
|
+
<div>
|
|
6636
|
+
<label className="block text-sm font-medium mb-2 text-gray-700">
|
|
6637
|
+
<div className="flex items-center gap-2">
|
|
6638
|
+
<User size={16} />
|
|
6639
|
+
Full Name
|
|
6640
|
+
</div>
|
|
6641
|
+
</label>
|
|
6642
|
+
<TextInput
|
|
6643
|
+
type="text"
|
|
6644
|
+
value={formData.name}
|
|
6645
|
+
onChange={(e) => handleInputChange("name", e.target.value)}
|
|
6646
|
+
placeholder="John Doe"
|
|
6647
|
+
required
|
|
6648
|
+
/>
|
|
6649
|
+
</div>
|
|
6650
|
+
|
|
6651
|
+
{/* Email Input */}
|
|
6652
|
+
<div>
|
|
6653
|
+
<label className="block text-sm font-medium mb-2 text-gray-700">
|
|
6654
|
+
<div className="flex items-center gap-2">
|
|
6655
|
+
<Mail size={16} />
|
|
6656
|
+
Email Address
|
|
6657
|
+
</div>
|
|
6658
|
+
</label>
|
|
6659
|
+
<TextInput
|
|
6660
|
+
type="email"
|
|
6661
|
+
value={formData.email}
|
|
6662
|
+
onChange={(e) => handleInputChange("email", e.target.value)}
|
|
6663
|
+
placeholder="john@example.com"
|
|
6664
|
+
required
|
|
6665
|
+
/>
|
|
6666
|
+
</div>
|
|
6667
|
+
|
|
6668
|
+
{/* Phone Input */}
|
|
6669
|
+
<div>
|
|
6670
|
+
<label className="block text-sm font-medium mb-2 text-gray-700">
|
|
6671
|
+
<div className="flex items-center gap-2">
|
|
6672
|
+
<Phone size={16} />
|
|
6673
|
+
Phone Number
|
|
6674
|
+
</div>
|
|
6675
|
+
</label>
|
|
6676
|
+
<TextInput
|
|
6677
|
+
type="tel"
|
|
6678
|
+
value={formData.phone}
|
|
6679
|
+
onChange={(e) => handleInputChange("phone", e.target.value)}
|
|
6680
|
+
placeholder="+1 (555) 000-0000"
|
|
6681
|
+
required
|
|
6682
|
+
/>
|
|
6683
|
+
</div>
|
|
6684
|
+
|
|
6685
|
+
{/* Date Picker */}
|
|
6686
|
+
<DatePicker
|
|
6687
|
+
label="Appointment Date"
|
|
6688
|
+
selectedDate={formData.appointmentDate}
|
|
6689
|
+
onDateChange={(date) => handleInputChange("appointmentDate", date)}
|
|
6690
|
+
/>
|
|
6691
|
+
|
|
6692
|
+
{/* Selected Date Summary */}
|
|
6693
|
+
{formData.appointmentDate && (
|
|
6694
|
+
<div className="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
|
6695
|
+
<p className="text-sm font-medium text-blue-900">
|
|
6696
|
+
Your appointment is scheduled for:
|
|
6697
|
+
</p>
|
|
6698
|
+
<p className="text-lg font-bold text-blue-700 mt-1">
|
|
6699
|
+
{formData.appointmentDate.toLocaleDateString("en-US", {
|
|
6700
|
+
weekday: "long",
|
|
6701
|
+
year: "numeric",
|
|
6702
|
+
month: "long",
|
|
6703
|
+
day: "numeric",
|
|
6704
|
+
})}
|
|
6705
|
+
</p>
|
|
6706
|
+
</div>
|
|
6707
|
+
)}
|
|
6708
|
+
|
|
6709
|
+
{/* Submit Button */}
|
|
6710
|
+
<Button
|
|
6711
|
+
type="submit"
|
|
6712
|
+
variant="solid"
|
|
6713
|
+
variantType="primary"
|
|
6714
|
+
className="w-full"
|
|
6715
|
+
disabled={!isFormValid()}
|
|
6716
|
+
>
|
|
6717
|
+
Schedule Appointment
|
|
6718
|
+
</Button>
|
|
6719
|
+
</form>
|
|
6720
|
+
</div>
|
|
6721
|
+
</div>
|
|
6722
|
+
);
|
|
6723
|
+
};
|
|
6724
|
+
```
|
|
6725
|
+
|
|
6726
|
+
## CodeInput
|
|
6727
|
+
|
|
6728
|
+
The CodeInput component provides an input interface for entering verification codes, OTPs, or PINs with individual character boxes and automatic focus management.
|
|
6729
|
+
|
|
6730
|
+
### Import
|
|
6731
|
+
|
|
6732
|
+
```jsx
|
|
6733
|
+
import { CodeInput } from "alope-ui";
|
|
6734
|
+
```
|
|
6735
|
+
|
|
6736
|
+
### Props
|
|
6737
|
+
|
|
6738
|
+
| Prop | Type | Default | Description |
|
|
6739
|
+
| ---------------- | ------------------------------ | ----------- | -------------------------------- |
|
|
6740
|
+
| `value` | `string` | `required` | Current code value |
|
|
6741
|
+
| `length` | `number` | `required` | Number of code digits/characters |
|
|
6742
|
+
| `onCodeChange` | `(code: string) => void` | `required` | Callback when code changes |
|
|
6743
|
+
| `label` | `string` | `undefined` | Label text for the input |
|
|
6744
|
+
| `inputSize` | `'sm' \| 'md' \| 'lg' \| 'xl'` | `'md'` | Size of input boxes |
|
|
6745
|
+
| `labelClassName` | `string` | `""` | Label CSS class |
|
|
6746
|
+
| `disabled` | `boolean` | `false` | Disable all inputs |
|
|
6747
|
+
| `id` | `string` | `undefined` | HTML id attribute |
|
|
6748
|
+
| `name` | `string` | `undefined` | HTML name attribute |
|
|
6749
|
+
|
|
6750
|
+
### Basic CodeInput
|
|
6751
|
+
|
|
6752
|
+
```jsx
|
|
6753
|
+
import { useState } from "react";
|
|
6754
|
+
import { CodeInput } from "alope-ui";
|
|
6755
|
+
|
|
6756
|
+
const BasicExample = () => {
|
|
6757
|
+
const [code, setCode] = useState("");
|
|
6758
|
+
|
|
6759
|
+
return (
|
|
6760
|
+
<CodeInput
|
|
6761
|
+
label="Enter Verification Code"
|
|
6762
|
+
value={code}
|
|
6763
|
+
length={6}
|
|
6764
|
+
onCodeChange={setCode}
|
|
6765
|
+
/>
|
|
6766
|
+
);
|
|
6767
|
+
};
|
|
6768
|
+
```
|
|
6769
|
+
|
|
6770
|
+
### Sizes
|
|
6771
|
+
|
|
6772
|
+
```jsx
|
|
6773
|
+
import { useState } from "react";
|
|
6774
|
+
import { CodeInput } from "alope-ui";
|
|
6775
|
+
|
|
6776
|
+
const SizesExample = () => {
|
|
6777
|
+
const [code, setCode] = useState("");
|
|
6778
|
+
|
|
6779
|
+
return (
|
|
6780
|
+
<div className="space-y-6">
|
|
6781
|
+
{/* Small */}
|
|
6782
|
+
<CodeInput
|
|
6783
|
+
label="Small"
|
|
6784
|
+
value={code}
|
|
6785
|
+
length={4}
|
|
6786
|
+
inputSize="sm"
|
|
6787
|
+
onCodeChange={setCode}
|
|
6788
|
+
/>
|
|
6789
|
+
|
|
6790
|
+
{/* Medium */}
|
|
6791
|
+
<CodeInput
|
|
6792
|
+
label="Medium"
|
|
6793
|
+
value={code}
|
|
6794
|
+
length={4}
|
|
6795
|
+
inputSize="md"
|
|
6796
|
+
onCodeChange={setCode}
|
|
6797
|
+
/>
|
|
6798
|
+
|
|
6799
|
+
{/* Large */}
|
|
6800
|
+
<CodeInput
|
|
6801
|
+
label="Large"
|
|
6802
|
+
value={code}
|
|
6803
|
+
length={4}
|
|
6804
|
+
inputSize="lg"
|
|
6805
|
+
onCodeChange={setCode}
|
|
6806
|
+
/>
|
|
6807
|
+
|
|
6808
|
+
{/* Extra Large */}
|
|
6809
|
+
<CodeInput
|
|
6810
|
+
label="Extra Large"
|
|
6811
|
+
value={code}
|
|
6812
|
+
length={4}
|
|
6813
|
+
inputSize="xl"
|
|
6814
|
+
onCodeChange={setCode}
|
|
6815
|
+
/>
|
|
6816
|
+
</div>
|
|
6817
|
+
);
|
|
6818
|
+
};
|
|
6819
|
+
```
|
|
6820
|
+
|
|
6821
|
+
### Different Lengths
|
|
6822
|
+
|
|
6823
|
+
```jsx
|
|
6824
|
+
import { useState } from "react";
|
|
6825
|
+
import { CodeInput } from "alope-ui";
|
|
6826
|
+
|
|
6827
|
+
const LengthsExample = () => {
|
|
6828
|
+
const [code4, setCode4] = useState("");
|
|
6829
|
+
const [code6, setCode6] = useState("");
|
|
6830
|
+
const [code8, setCode8] = useState("");
|
|
6831
|
+
|
|
6832
|
+
return (
|
|
6833
|
+
<div className="space-y-6">
|
|
6834
|
+
{/* 4-digit code */}
|
|
6835
|
+
<CodeInput
|
|
6836
|
+
label="4-Digit PIN"
|
|
6837
|
+
value={code4}
|
|
6838
|
+
length={4}
|
|
6839
|
+
onCodeChange={setCode4}
|
|
6840
|
+
/>
|
|
6841
|
+
|
|
6842
|
+
{/* 6-digit code */}
|
|
6843
|
+
<CodeInput
|
|
6844
|
+
label="6-Digit OTP"
|
|
6845
|
+
value={code6}
|
|
6846
|
+
length={6}
|
|
6847
|
+
onCodeChange={setCode6}
|
|
6848
|
+
/>
|
|
6849
|
+
|
|
6850
|
+
{/* 8-digit code */}
|
|
6851
|
+
<CodeInput
|
|
6852
|
+
label="8-Digit Code"
|
|
6853
|
+
value={code8}
|
|
6854
|
+
length={8}
|
|
6855
|
+
onCodeChange={setCode8}
|
|
6856
|
+
/>
|
|
6857
|
+
</div>
|
|
6858
|
+
);
|
|
6859
|
+
};
|
|
6860
|
+
```
|
|
6861
|
+
|
|
6862
|
+
### Disabled State
|
|
6863
|
+
|
|
6864
|
+
```jsx
|
|
6865
|
+
import { useState } from "react";
|
|
6866
|
+
import { CodeInput } from "alope-ui";
|
|
6867
|
+
|
|
6868
|
+
const DisabledExample = () => {
|
|
6869
|
+
const [code, setCode] = useState("123456");
|
|
6870
|
+
|
|
6871
|
+
return (
|
|
6872
|
+
<CodeInput
|
|
6873
|
+
label="Disabled Code Input"
|
|
6874
|
+
value={code}
|
|
6875
|
+
length={6}
|
|
6876
|
+
disabled
|
|
6877
|
+
onCodeChange={setCode}
|
|
6878
|
+
/>
|
|
6879
|
+
);
|
|
6880
|
+
};
|
|
6881
|
+
```
|
|
6882
|
+
|
|
6883
|
+
### OTP Verification
|
|
6884
|
+
|
|
6885
|
+
```jsx
|
|
6886
|
+
import { useState } from "react";
|
|
6887
|
+
import { CodeInput } from "alope-ui";
|
|
6888
|
+
import { Button } from "alope-ui";
|
|
6889
|
+
|
|
6890
|
+
const OTPExample = () => {
|
|
6891
|
+
const [otp, setOtp] = useState("");
|
|
6892
|
+
const [isVerifying, setIsVerifying] = useState(false);
|
|
6893
|
+
const [isVerified, setIsVerified] = useState(false);
|
|
6894
|
+
|
|
6895
|
+
const handleVerify = () => {
|
|
6896
|
+
setIsVerifying(true);
|
|
6897
|
+
// Simulate API call
|
|
6898
|
+
setTimeout(() => {
|
|
6899
|
+
setIsVerifying(false);
|
|
6900
|
+
if (otp === "123456") {
|
|
6901
|
+
setIsVerified(true);
|
|
6902
|
+
} else {
|
|
6903
|
+
alert("Invalid OTP. Please try again.");
|
|
6904
|
+
setOtp("");
|
|
6905
|
+
}
|
|
6906
|
+
}, 1500);
|
|
6907
|
+
};
|
|
6908
|
+
|
|
6909
|
+
return (
|
|
6910
|
+
<div className="max-w-md mx-auto p-8 bg-white rounded-lg shadow-lg">
|
|
6911
|
+
<h2 className="text-2xl font-bold mb-2">Verify Your Account</h2>
|
|
6912
|
+
<p className="text-gray-600 mb-6">
|
|
6913
|
+
Enter the 6-digit code sent to your email
|
|
6914
|
+
</p>
|
|
6915
|
+
|
|
6916
|
+
<CodeInput
|
|
6917
|
+
label="Verification Code"
|
|
6918
|
+
value={otp}
|
|
6919
|
+
length={6}
|
|
6920
|
+
onCodeChange={setOtp}
|
|
6921
|
+
/>
|
|
6922
|
+
|
|
6923
|
+
{isVerified && (
|
|
6924
|
+
<div className="mt-4 p-3 bg-green-100 text-green-700 rounded-lg">
|
|
6925
|
+
✓ Verification successful!
|
|
6926
|
+
</div>
|
|
6927
|
+
)}
|
|
6928
|
+
|
|
6929
|
+
<Button
|
|
6930
|
+
variant="solid"
|
|
6931
|
+
variantType="primary"
|
|
6932
|
+
className="w-full mt-6"
|
|
6933
|
+
onClick={handleVerify}
|
|
6934
|
+
disabled={otp.length !== 6 || isVerifying || isVerified}
|
|
6935
|
+
>
|
|
6936
|
+
{isVerifying ? "Verifying..." : isVerified ? "Verified" : "Verify"}
|
|
6937
|
+
</Button>
|
|
6938
|
+
|
|
6939
|
+
<button className="w-full mt-4 text-sm text-blue-600 hover:text-blue-800">
|
|
6940
|
+
Resend Code
|
|
6941
|
+
</button>
|
|
6942
|
+
</div>
|
|
6943
|
+
);
|
|
6944
|
+
};
|
|
6945
|
+
```
|
|
6946
|
+
|
|
6947
|
+
### Two-Factor Authentication
|
|
6948
|
+
|
|
6949
|
+
```jsx
|
|
6950
|
+
import { useState } from "react";
|
|
6951
|
+
import { CodeInput } from "alope-ui";
|
|
6952
|
+
import { Button } from "alope-ui";
|
|
6953
|
+
import { Shield, Clock } from "lucide-react";
|
|
6954
|
+
|
|
6955
|
+
const TwoFactorExample = () => {
|
|
6956
|
+
const [code, setCode] = useState("");
|
|
6957
|
+
const [timeLeft, setTimeLeft] = useState(60);
|
|
6958
|
+
|
|
6959
|
+
// Timer logic (simplified)
|
|
6960
|
+
React.useEffect(() => {
|
|
6961
|
+
if (timeLeft > 0) {
|
|
6962
|
+
const timer = setTimeout(() => setTimeLeft(timeLeft - 1), 1000);
|
|
6963
|
+
return () => clearTimeout(timer);
|
|
6964
|
+
}
|
|
6965
|
+
}, [timeLeft]);
|
|
6966
|
+
|
|
6967
|
+
return (
|
|
6968
|
+
<div className="max-w-md mx-auto p-8 bg-gradient-to-br from-blue-50 to-indigo-50 rounded-xl shadow-lg">
|
|
6969
|
+
<div className="flex items-center justify-center mb-6">
|
|
6970
|
+
<div className="p-4 bg-blue-600 rounded-full">
|
|
6971
|
+
<Shield size={32} className="text-white" />
|
|
6972
|
+
</div>
|
|
6973
|
+
</div>
|
|
6974
|
+
|
|
6975
|
+
<h2 className="text-2xl font-bold text-center mb-2">
|
|
6976
|
+
Two-Factor Authentication
|
|
6977
|
+
</h2>
|
|
6978
|
+
<p className="text-gray-600 text-center mb-6">
|
|
6979
|
+
Enter the 6-digit code from your authenticator app
|
|
6980
|
+
</p>
|
|
6981
|
+
|
|
6982
|
+
<CodeInput
|
|
6983
|
+
label="Authentication Code"
|
|
6984
|
+
value={code}
|
|
6985
|
+
length={6}
|
|
6986
|
+
inputSize="lg"
|
|
6987
|
+
onCodeChange={setCode}
|
|
6988
|
+
/>
|
|
6989
|
+
|
|
6990
|
+
<div className="flex items-center justify-center gap-2 mt-4 text-sm text-gray-600">
|
|
6991
|
+
<Clock size={16} />
|
|
6992
|
+
<span>Code expires in {timeLeft}s</span>
|
|
6993
|
+
</div>
|
|
6994
|
+
|
|
6995
|
+
<Button
|
|
6996
|
+
variant="solid"
|
|
6997
|
+
variantType="primary"
|
|
6998
|
+
className="w-full mt-6"
|
|
6999
|
+
disabled={code.length !== 6}
|
|
7000
|
+
>
|
|
7001
|
+
Verify & Continue
|
|
7002
|
+
</Button>
|
|
7003
|
+
</div>
|
|
7004
|
+
);
|
|
7005
|
+
};
|
|
7006
|
+
```
|
|
7007
|
+
|
|
7008
|
+
### PIN Entry
|
|
7009
|
+
|
|
7010
|
+
```jsx
|
|
7011
|
+
import { useState } from "react";
|
|
7012
|
+
import { CodeInput } from "alope-ui";
|
|
7013
|
+
import { Button } from "alope-ui";
|
|
7014
|
+
import { Lock, Eye, EyeOff } from "lucide-react";
|
|
7015
|
+
|
|
7016
|
+
const PINExample = () => {
|
|
7017
|
+
const [pin, setPin] = useState("");
|
|
7018
|
+
const [showPin, setShowPin] = useState(false);
|
|
7019
|
+
|
|
7020
|
+
const handleSubmit = () => {
|
|
7021
|
+
console.log("PIN entered:", pin);
|
|
7022
|
+
alert("PIN submitted successfully!");
|
|
7023
|
+
setPin("");
|
|
7024
|
+
};
|
|
7025
|
+
|
|
7026
|
+
return (
|
|
7027
|
+
<div className="max-w-md mx-auto p-8 bg-white rounded-lg shadow-lg">
|
|
7028
|
+
<div className="flex items-center gap-3 mb-6">
|
|
7029
|
+
<Lock size={24} className="text-gray-700" />
|
|
7030
|
+
<h2 className="text-xl font-bold">Enter Your PIN</h2>
|
|
7031
|
+
</div>
|
|
7032
|
+
|
|
7033
|
+
<CodeInput
|
|
7034
|
+
label="4-Digit PIN"
|
|
7035
|
+
value={pin}
|
|
7036
|
+
length={4}
|
|
7037
|
+
inputSize="xl"
|
|
7038
|
+
onCodeChange={setPin}
|
|
7039
|
+
type={showPin ? "text" : "password"}
|
|
7040
|
+
/>
|
|
7041
|
+
|
|
7042
|
+
<button
|
|
7043
|
+
onClick={() => setShowPin(!showPin)}
|
|
7044
|
+
className="flex items-center gap-2 mt-4 text-sm text-gray-600 hover:text-gray-800"
|
|
7045
|
+
>
|
|
7046
|
+
{showPin ? <EyeOff size={16} /> : <Eye size={16} />}
|
|
7047
|
+
{showPin ? "Hide" : "Show"} PIN
|
|
7048
|
+
</button>
|
|
7049
|
+
|
|
7050
|
+
<Button
|
|
7051
|
+
variant="solid"
|
|
7052
|
+
variantType="primary"
|
|
7053
|
+
className="w-full mt-6"
|
|
7054
|
+
onClick={handleSubmit}
|
|
7055
|
+
disabled={pin.length !== 4}
|
|
7056
|
+
>
|
|
7057
|
+
Submit PIN
|
|
7058
|
+
</Button>
|
|
7059
|
+
</div>
|
|
7060
|
+
);
|
|
7061
|
+
};
|
|
7062
|
+
```
|
|
7063
|
+
|
|
7064
|
+
### Complete Example
|
|
7065
|
+
|
|
7066
|
+
```jsx
|
|
7067
|
+
import { useState } from "react";
|
|
7068
|
+
import { CodeInput } from "alope-ui";
|
|
7069
|
+
import { Button } from "alope-ui";
|
|
7070
|
+
import { Mail, CheckCircle, AlertCircle } from "lucide-react";
|
|
7071
|
+
|
|
7072
|
+
const CompleteExample = () => {
|
|
7073
|
+
const [step, setStep] = useState(1);
|
|
7074
|
+
const [email, setEmail] = useState("");
|
|
7075
|
+
const [verificationCode, setVerificationCode] = useState("");
|
|
7076
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
7077
|
+
const [error, setError] = useState("");
|
|
7078
|
+
|
|
7079
|
+
const handleSendCode = (e) => {
|
|
7080
|
+
e.preventDefault();
|
|
7081
|
+
setIsLoading(true);
|
|
7082
|
+
setError("");
|
|
7083
|
+
|
|
7084
|
+
// Simulate API call
|
|
7085
|
+
setTimeout(() => {
|
|
7086
|
+
setIsLoading(false);
|
|
7087
|
+
setStep(2);
|
|
7088
|
+
}, 1500);
|
|
7089
|
+
};
|
|
7090
|
+
|
|
7091
|
+
const handleVerify = () => {
|
|
7092
|
+
setIsLoading(true);
|
|
7093
|
+
setError("");
|
|
7094
|
+
|
|
7095
|
+
// Simulate verification
|
|
7096
|
+
setTimeout(() => {
|
|
7097
|
+
setIsLoading(false);
|
|
7098
|
+
if (verificationCode === "123456") {
|
|
7099
|
+
setStep(3);
|
|
7100
|
+
} else {
|
|
7101
|
+
setError("Invalid verification code. Please try again.");
|
|
7102
|
+
setVerificationCode("");
|
|
7103
|
+
}
|
|
7104
|
+
}, 1500);
|
|
7105
|
+
};
|
|
7106
|
+
|
|
7107
|
+
const handleResend = () => {
|
|
7108
|
+
setVerificationCode("");
|
|
7109
|
+
setError("");
|
|
7110
|
+
alert("Verification code resent!");
|
|
7111
|
+
};
|
|
7112
|
+
|
|
7113
|
+
return (
|
|
7114
|
+
<div className="max-w-md mx-auto p-8">
|
|
7115
|
+
<div className="bg-white rounded-xl shadow-lg p-8">
|
|
7116
|
+
{/* Step 1: Enter Email */}
|
|
7117
|
+
{step === 1 && (
|
|
7118
|
+
<div>
|
|
7119
|
+
<div className="flex items-center gap-3 mb-6">
|
|
7120
|
+
<Mail size={32} className="text-blue-600" />
|
|
7121
|
+
<div>
|
|
7122
|
+
<h2 className="text-2xl font-bold">Email Verification</h2>
|
|
7123
|
+
<p className="text-gray-600 text-sm">
|
|
7124
|
+
We'll send you a verification code
|
|
7125
|
+
</p>
|
|
7126
|
+
</div>
|
|
7127
|
+
</div>
|
|
7128
|
+
|
|
7129
|
+
<form onSubmit={handleSendCode} className="space-y-4">
|
|
7130
|
+
<div>
|
|
7131
|
+
<label className="block text-sm font-medium mb-2">
|
|
7132
|
+
Email Address
|
|
7133
|
+
</label>
|
|
7134
|
+
<input
|
|
7135
|
+
type="email"
|
|
7136
|
+
value={email}
|
|
7137
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
7138
|
+
placeholder="your@email.com"
|
|
7139
|
+
className="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
7140
|
+
required
|
|
7141
|
+
/>
|
|
7142
|
+
</div>
|
|
7143
|
+
|
|
7144
|
+
<Button
|
|
7145
|
+
type="submit"
|
|
7146
|
+
variant="solid"
|
|
7147
|
+
variantType="primary"
|
|
7148
|
+
className="w-full"
|
|
7149
|
+
disabled={isLoading}
|
|
7150
|
+
>
|
|
7151
|
+
{isLoading ? "Sending..." : "Send Verification Code"}
|
|
7152
|
+
</Button>
|
|
7153
|
+
</form>
|
|
7154
|
+
</div>
|
|
7155
|
+
)}
|
|
7156
|
+
|
|
7157
|
+
{/* Step 2: Enter Code */}
|
|
7158
|
+
{step === 2 && (
|
|
7159
|
+
<div>
|
|
7160
|
+
<div className="flex items-center gap-3 mb-6">
|
|
7161
|
+
<Mail size={32} className="text-blue-600" />
|
|
7162
|
+
<div>
|
|
7163
|
+
<h2 className="text-2xl font-bold">Enter Code</h2>
|
|
7164
|
+
<p className="text-gray-600 text-sm">Sent to {email}</p>
|
|
7165
|
+
</div>
|
|
7166
|
+
</div>
|
|
7167
|
+
|
|
7168
|
+
<CodeInput
|
|
7169
|
+
label="6-Digit Verification Code"
|
|
7170
|
+
value={verificationCode}
|
|
7171
|
+
length={6}
|
|
7172
|
+
inputSize="lg"
|
|
7173
|
+
onCodeChange={setVerificationCode}
|
|
7174
|
+
/>
|
|
7175
|
+
|
|
7176
|
+
{error && (
|
|
7177
|
+
<div className="mt-4 p-3 bg-red-50 border border-red-200 rounded-lg flex items-start gap-2">
|
|
7178
|
+
<AlertCircle
|
|
7179
|
+
size={20}
|
|
7180
|
+
className="text-red-600 flex-shrink-0 mt-0.5"
|
|
7181
|
+
/>
|
|
7182
|
+
<p className="text-sm text-red-600">{error}</p>
|
|
7183
|
+
</div>
|
|
7184
|
+
)}
|
|
7185
|
+
|
|
7186
|
+
<Button
|
|
7187
|
+
variant="solid"
|
|
7188
|
+
variantType="primary"
|
|
7189
|
+
className="w-full mt-6"
|
|
7190
|
+
onClick={handleVerify}
|
|
7191
|
+
disabled={verificationCode.length !== 6 || isLoading}
|
|
7192
|
+
>
|
|
7193
|
+
{isLoading ? "Verifying..." : "Verify Code"}
|
|
7194
|
+
</Button>
|
|
7195
|
+
|
|
7196
|
+
<div className="mt-4 text-center">
|
|
7197
|
+
<button
|
|
7198
|
+
onClick={handleResend}
|
|
7199
|
+
className="text-sm text-blue-600 hover:text-blue-800"
|
|
7200
|
+
>
|
|
7201
|
+
Didn't receive code? Resend
|
|
7202
|
+
</button>
|
|
7203
|
+
</div>
|
|
7204
|
+
</div>
|
|
7205
|
+
)}
|
|
7206
|
+
|
|
7207
|
+
{/* Step 3: Success */}
|
|
7208
|
+
{step === 3 && (
|
|
7209
|
+
<div className="text-center">
|
|
7210
|
+
<div className="flex items-center justify-center mb-6">
|
|
7211
|
+
<div className="p-4 bg-green-100 rounded-full">
|
|
7212
|
+
<CheckCircle size={48} className="text-green-600" />
|
|
7213
|
+
</div>
|
|
7214
|
+
</div>
|
|
7215
|
+
<h2 className="text-2xl font-bold mb-2">Verified!</h2>
|
|
7216
|
+
<p className="text-gray-600 mb-6">
|
|
7217
|
+
Your email has been successfully verified
|
|
7218
|
+
</p>
|
|
7219
|
+
<Button
|
|
7220
|
+
variant="solid"
|
|
7221
|
+
variantType="success"
|
|
7222
|
+
className="w-full"
|
|
7223
|
+
onClick={() => {
|
|
7224
|
+
setStep(1);
|
|
7225
|
+
setEmail("");
|
|
7226
|
+
setVerificationCode("");
|
|
7227
|
+
}}
|
|
7228
|
+
>
|
|
7229
|
+
Continue
|
|
7230
|
+
</Button>
|
|
7231
|
+
</div>
|
|
7232
|
+
)}
|
|
7233
|
+
</div>
|
|
7234
|
+
</div>
|
|
7235
|
+
);
|
|
7236
|
+
};
|
|
7237
|
+
```
|
|
7238
|
+
|
|
4230
7239
|
---
|
|
4231
7240
|
|
|
4232
7241
|
## 💡 Contributing
|