hazo_ui 2.1.2 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +286 -33
- package/dist/index.cjs +205 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -13
- package/dist/index.d.ts +37 -13
- package/dist/index.js +202 -5
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,25 +12,27 @@ npm install hazo_ui
|
|
|
12
12
|
|
|
13
13
|
### Component Overview
|
|
14
14
|
|
|
15
|
-
- **[
|
|
15
|
+
- **[HazoUiMultiFilterDialog](#hazouimultifilterdialog)** - A powerful dialog component for multi-field filtering with support for text, number, combobox, boolean, and date fields. Includes operator support, validation, and visual feedback.
|
|
16
16
|
|
|
17
|
-
- **[
|
|
17
|
+
- **[HazoUiMultiSortDialog](#hazouimultisortdialog)** - A flexible dialog component for multi-field sorting with drag-and-drop reordering. Allows users to set sort priority and direction (ascending/descending) for multiple fields.
|
|
18
18
|
|
|
19
|
-
- **[
|
|
19
|
+
- **[HazoUiFlexRadio](#hazouiflexradio)** - A flexible radio button/icon selection component with support for single and multi-selection modes, customizable layouts, and react-icons integration. Perfect for settings panels, preference selection, and option groups.
|
|
20
|
+
|
|
21
|
+
- **[HazoUiFlexInput](#hazouiflexinput)** - An enhanced input component with type validation, character restrictions, and error messaging. Supports numeric, alpha, email, and mixed input types with built-in validation and formatting guides.
|
|
20
22
|
|
|
21
23
|
---
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## HazoUiMultiFilterDialog
|
|
24
26
|
|
|
25
27
|
A powerful, flexible dialog component for multi-field filtering with support for various input types. Perfect for table headers, grid views, or any interface where users need to apply multiple filters simultaneously.
|
|
26
28
|
|
|
27
|
-

|
|
28
30
|
|
|
29
|
-

|
|
30
32
|
|
|
31
|
-

|
|
32
34
|
|
|
33
|
-

|
|
34
36
|
|
|
35
37
|
#### Features
|
|
36
38
|
|
|
@@ -71,7 +73,7 @@ A powerful, flexible dialog component for multi-field filtering with support for
|
|
|
71
73
|
#### Usage
|
|
72
74
|
|
|
73
75
|
```tsx
|
|
74
|
-
import {
|
|
76
|
+
import { HazoUiMultiFilterDialog, type FilterField, type FilterConfig } from 'hazo_ui';
|
|
75
77
|
import { useState } from 'react';
|
|
76
78
|
|
|
77
79
|
function DataTable() {
|
|
@@ -145,7 +147,7 @@ function DataTable() {
|
|
|
145
147
|
|
|
146
148
|
return (
|
|
147
149
|
<div>
|
|
148
|
-
<
|
|
150
|
+
<HazoUiMultiFilterDialog
|
|
149
151
|
availableFields={availableFields}
|
|
150
152
|
onFilterChange={handleFilterChange}
|
|
151
153
|
initialFilters={filters}
|
|
@@ -239,17 +241,17 @@ When users apply filters, the `onFilterChange` callback receives an array of `Fi
|
|
|
239
241
|
|
|
240
242
|
---
|
|
241
243
|
|
|
242
|
-
##
|
|
244
|
+
## HazoUiMultiSortDialog
|
|
243
245
|
|
|
244
246
|
A powerful dialog component for multi-field sorting with drag-and-drop reordering. Allows users to select multiple fields for sorting, reorder them by priority, and set ascending/descending direction for each field.
|
|
245
247
|
|
|
246
|
-

|
|
247
249
|
|
|
248
|
-

|
|
249
251
|
|
|
250
|
-

|
|
251
253
|
|
|
252
|
-

|
|
253
255
|
|
|
254
256
|
#### Features
|
|
255
257
|
|
|
@@ -278,7 +280,7 @@ The component returns an array of sort configurations in priority order, where t
|
|
|
278
280
|
#### Usage
|
|
279
281
|
|
|
280
282
|
```tsx
|
|
281
|
-
import {
|
|
283
|
+
import { HazoUiMultiSortDialog, type SortField, type SortConfig } from 'hazo_ui';
|
|
282
284
|
import { useState } from 'react';
|
|
283
285
|
|
|
284
286
|
function DataTable() {
|
|
@@ -319,7 +321,7 @@ function DataTable() {
|
|
|
319
321
|
|
|
320
322
|
return (
|
|
321
323
|
<div>
|
|
322
|
-
<
|
|
324
|
+
<HazoUiMultiSortDialog
|
|
323
325
|
availableFields={availableFields}
|
|
324
326
|
onSortChange={handleSortChange}
|
|
325
327
|
initialSortFields={sorts}
|
|
@@ -446,7 +448,7 @@ const sortedData = applySorts(originalData, sorts);
|
|
|
446
448
|
|
|
447
449
|
---
|
|
448
450
|
|
|
449
|
-
##
|
|
451
|
+
## HazoUiFlexRadio
|
|
450
452
|
|
|
451
453
|
A flexible radio button/icon selection component with support for single and multi-selection modes, customizable layouts, and react-icons integration. Perfect for settings panels, preference selection, and option groups.
|
|
452
454
|
|
|
@@ -466,19 +468,19 @@ A flexible radio button/icon selection component with support for single and mul
|
|
|
466
468
|
#### Props
|
|
467
469
|
|
|
468
470
|
```typescript
|
|
469
|
-
interface
|
|
471
|
+
interface HazoUiFlexRadioItem {
|
|
470
472
|
label: string; // Display label for the option
|
|
471
473
|
value: string; // Unique value identifier
|
|
472
474
|
icon_selected?: string; // Icon name when selected (e.g., "FaHome")
|
|
473
475
|
icon_unselected?: string; // Icon name when unselected (e.g., "FaRegHome")
|
|
474
476
|
}
|
|
475
477
|
|
|
476
|
-
interface
|
|
478
|
+
interface HazoUiFlexRadioProps {
|
|
477
479
|
layout?: 'horizontal' | 'vertical'; // Layout direction (default: 'horizontal')
|
|
478
480
|
style?: 'radio' | 'icons'; // Display style (default: 'radio')
|
|
479
481
|
display_label?: boolean; // Show/hide labels (default: true)
|
|
480
482
|
icon_set?: string; // Icon set package name (e.g., 'fa', 'md')
|
|
481
|
-
data:
|
|
483
|
+
data: HazoUiFlexRadioItem[]; // Array of options
|
|
482
484
|
selection: 'single' | 'multi'; // Selection mode
|
|
483
485
|
value: string | string[]; // Controlled value (string for single, array for multi)
|
|
484
486
|
onChange: (value: string | string[]) => void; // Change handler
|
|
@@ -491,13 +493,13 @@ interface MultiStateRadioProps {
|
|
|
491
493
|
**Basic Single Selection (Radio Style)**
|
|
492
494
|
|
|
493
495
|
```tsx
|
|
494
|
-
import {
|
|
496
|
+
import { HazoUiFlexRadio, type HazoUiFlexRadioItem } from 'hazo_ui';
|
|
495
497
|
import { useState } from 'react';
|
|
496
498
|
|
|
497
499
|
function SettingsPanel() {
|
|
498
500
|
const [selectedOption, setSelectedOption] = useState<string>('option1');
|
|
499
501
|
|
|
500
|
-
const options:
|
|
502
|
+
const options: HazoUiFlexRadioItem[] = [
|
|
501
503
|
{ label: 'Option 1', value: 'option1' },
|
|
502
504
|
{ label: 'Option 2', value: 'option2' },
|
|
503
505
|
{ label: 'Option 3', value: 'option3' },
|
|
@@ -505,7 +507,7 @@ function SettingsPanel() {
|
|
|
505
507
|
];
|
|
506
508
|
|
|
507
509
|
return (
|
|
508
|
-
<
|
|
510
|
+
<HazoUiFlexRadio
|
|
509
511
|
data={options}
|
|
510
512
|
value={selectedOption}
|
|
511
513
|
onChange={setSelectedOption}
|
|
@@ -521,13 +523,13 @@ function SettingsPanel() {
|
|
|
521
523
|
**Icon Style with React Icons**
|
|
522
524
|
|
|
523
525
|
```tsx
|
|
524
|
-
import {
|
|
526
|
+
import { HazoUiFlexRadio, type HazoUiFlexRadioItem } from 'hazo_ui';
|
|
525
527
|
import { useState } from 'react';
|
|
526
528
|
|
|
527
529
|
function IconSelector() {
|
|
528
530
|
const [selectedIcon, setSelectedIcon] = useState<string>('home');
|
|
529
531
|
|
|
530
|
-
const iconOptions:
|
|
532
|
+
const iconOptions: HazoUiFlexRadioItem[] = [
|
|
531
533
|
{
|
|
532
534
|
label: 'Home',
|
|
533
535
|
value: 'home',
|
|
@@ -549,7 +551,7 @@ function IconSelector() {
|
|
|
549
551
|
];
|
|
550
552
|
|
|
551
553
|
return (
|
|
552
|
-
<
|
|
554
|
+
<HazoUiFlexRadio
|
|
553
555
|
data={iconOptions}
|
|
554
556
|
value={selectedIcon}
|
|
555
557
|
onChange={setSelectedIcon}
|
|
@@ -566,13 +568,13 @@ function IconSelector() {
|
|
|
566
568
|
**Multi-Selection Mode**
|
|
567
569
|
|
|
568
570
|
```tsx
|
|
569
|
-
import {
|
|
571
|
+
import { HazoUiFlexRadio, type HazoUiFlexRadioItem } from 'hazo_ui';
|
|
570
572
|
import { useState } from 'react';
|
|
571
573
|
|
|
572
574
|
function MultiSelectExample() {
|
|
573
575
|
const [selectedOptions, setSelectedOptions] = useState<string[]>(['option1', 'option3']);
|
|
574
576
|
|
|
575
|
-
const options:
|
|
577
|
+
const options: HazoUiFlexRadioItem[] = [
|
|
576
578
|
{ label: 'Option 1', value: 'option1' },
|
|
577
579
|
{ label: 'Option 2', value: 'option2' },
|
|
578
580
|
{ label: 'Option 3', value: 'option3' },
|
|
@@ -580,7 +582,7 @@ function MultiSelectExample() {
|
|
|
580
582
|
];
|
|
581
583
|
|
|
582
584
|
return (
|
|
583
|
-
<
|
|
585
|
+
<HazoUiFlexRadio
|
|
584
586
|
data={options}
|
|
585
587
|
value={selectedOptions}
|
|
586
588
|
onChange={setSelectedOptions}
|
|
@@ -596,13 +598,13 @@ function MultiSelectExample() {
|
|
|
596
598
|
**Vertical Layout with Icons Only (No Labels)**
|
|
597
599
|
|
|
598
600
|
```tsx
|
|
599
|
-
import {
|
|
601
|
+
import { HazoUiFlexRadio, type HazoUiFlexRadioItem } from 'hazo_ui';
|
|
600
602
|
import { useState } from 'react';
|
|
601
603
|
|
|
602
604
|
function VerticalIconSelector() {
|
|
603
605
|
const [selected, setSelected] = useState<string>('favorite');
|
|
604
606
|
|
|
605
|
-
const options:
|
|
607
|
+
const options: HazoUiFlexRadioItem[] = [
|
|
606
608
|
{
|
|
607
609
|
label: 'Favorite',
|
|
608
610
|
value: 'favorite',
|
|
@@ -618,7 +620,7 @@ function VerticalIconSelector() {
|
|
|
618
620
|
];
|
|
619
621
|
|
|
620
622
|
return (
|
|
621
|
-
<
|
|
623
|
+
<HazoUiFlexRadio
|
|
622
624
|
data={options}
|
|
623
625
|
value={selected}
|
|
624
626
|
onChange={setSelected}
|
|
@@ -667,6 +669,257 @@ The component supports the following react-icons packages:
|
|
|
667
669
|
|
|
668
670
|
---
|
|
669
671
|
|
|
672
|
+
## HazoUiFlexInput
|
|
673
|
+
|
|
674
|
+
An enhanced input component with type validation, character restrictions, and error messaging. Extends shadcn Input component with additional validation props for numeric, alpha, email, and mixed input types.
|
|
675
|
+
|
|
676
|
+
#### Features
|
|
677
|
+
|
|
678
|
+
- **Multiple Input Types**: Supports mixed (text), numeric, alpha (letters only), and email input types
|
|
679
|
+
- **Real-time Character Filtering**: Automatically prevents invalid characters from being entered (e.g., numbers in alpha fields)
|
|
680
|
+
- **Validation on Blur**: Validates input when the field loses focus and displays error messages
|
|
681
|
+
- **Numeric Constraints**: Min/max value validation and decimal precision control
|
|
682
|
+
- **Length Constraints**: Configurable minimum and maximum character lengths
|
|
683
|
+
- **Regex Validation**: Custom regex pattern support for complex validation rules
|
|
684
|
+
- **Format Guide**: Optional helper text displayed below the input
|
|
685
|
+
- **Error Messaging**: Clear error messages displayed when validation fails
|
|
686
|
+
- **Controlled & Uncontrolled**: Supports both controlled and uncontrolled usage patterns
|
|
687
|
+
- **TypeScript Support**: Fully typed with TypeScript interfaces
|
|
688
|
+
- **Accessible**: Built with accessibility in mind using shadcn/ui components
|
|
689
|
+
|
|
690
|
+
#### Input Types
|
|
691
|
+
|
|
692
|
+
1. **Mixed (text)**
|
|
693
|
+
- Allows any characters
|
|
694
|
+
- Supports length constraints (min/max)
|
|
695
|
+
- Supports regex validation
|
|
696
|
+
|
|
697
|
+
2. **Numeric**
|
|
698
|
+
- Only allows numbers, decimal point, and minus sign
|
|
699
|
+
- Supports min/max value constraints
|
|
700
|
+
- Configurable decimal precision (including integers with 0 decimals)
|
|
701
|
+
- Automatically filters out non-numeric characters
|
|
702
|
+
|
|
703
|
+
3. **Alpha**
|
|
704
|
+
- Only allows letters and spaces
|
|
705
|
+
- Automatically filters out numbers and special characters
|
|
706
|
+
- Supports length constraints
|
|
707
|
+
|
|
708
|
+
4. **Email**
|
|
709
|
+
- Validates email format on blur
|
|
710
|
+
- Uses standard email regex pattern
|
|
711
|
+
|
|
712
|
+
#### Props
|
|
713
|
+
|
|
714
|
+
```typescript
|
|
715
|
+
interface HazoUiFlexInputProps extends Omit<InputProps, "type"> {
|
|
716
|
+
input_type?: "mixed" | "numeric" | "email" | "alpha"; // Input type (default: "mixed")
|
|
717
|
+
text_len_min?: number; // Minimum character length
|
|
718
|
+
text_len_max?: number; // Maximum character length
|
|
719
|
+
num_min?: number; // Minimum numeric value
|
|
720
|
+
num_max?: number; // Maximum numeric value
|
|
721
|
+
regex?: string | RegExp; // Custom regex pattern
|
|
722
|
+
num_decimals?: number; // Number of decimal places allowed
|
|
723
|
+
format_guide?: string; // Helper text displayed below input
|
|
724
|
+
format_guide_info?: boolean; // Show format guide (default: false)
|
|
725
|
+
}
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
#### Usage
|
|
729
|
+
|
|
730
|
+
**Basic Mixed Input**
|
|
731
|
+
|
|
732
|
+
```tsx
|
|
733
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
734
|
+
import { useState } from 'react';
|
|
735
|
+
|
|
736
|
+
function BasicForm() {
|
|
737
|
+
const [value, setValue] = useState<string>("");
|
|
738
|
+
|
|
739
|
+
return (
|
|
740
|
+
<HazoUiFlexInput
|
|
741
|
+
input_type="mixed"
|
|
742
|
+
placeholder="Enter text..."
|
|
743
|
+
value={value}
|
|
744
|
+
onChange={(e) => setValue(e.target.value)}
|
|
745
|
+
/>
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
**Numeric Input with Constraints**
|
|
751
|
+
|
|
752
|
+
```tsx
|
|
753
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
754
|
+
import { useState } from 'react';
|
|
755
|
+
|
|
756
|
+
function PriceInput() {
|
|
757
|
+
const [price, setPrice] = useState<string>("");
|
|
758
|
+
|
|
759
|
+
return (
|
|
760
|
+
<HazoUiFlexInput
|
|
761
|
+
input_type="numeric"
|
|
762
|
+
placeholder="Enter price (0-100)..."
|
|
763
|
+
num_min={0}
|
|
764
|
+
num_max={100}
|
|
765
|
+
num_decimals={2}
|
|
766
|
+
format_guide="Enter a number between 0 and 100 with up to 2 decimal places"
|
|
767
|
+
format_guide_info={true}
|
|
768
|
+
value={price}
|
|
769
|
+
onChange={(e) => setPrice(e.target.value)}
|
|
770
|
+
/>
|
|
771
|
+
);
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
**Integer Input (No Decimals)**
|
|
776
|
+
|
|
777
|
+
```tsx
|
|
778
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
779
|
+
import { useState } from 'react';
|
|
780
|
+
|
|
781
|
+
function AgeInput() {
|
|
782
|
+
const [age, setAge] = useState<string>("");
|
|
783
|
+
|
|
784
|
+
return (
|
|
785
|
+
<HazoUiFlexInput
|
|
786
|
+
input_type="numeric"
|
|
787
|
+
placeholder="Enter age (1-120)..."
|
|
788
|
+
num_min={1}
|
|
789
|
+
num_max={120}
|
|
790
|
+
num_decimals={0}
|
|
791
|
+
format_guide="Enter a whole number between 1 and 120"
|
|
792
|
+
format_guide_info={true}
|
|
793
|
+
value={age}
|
|
794
|
+
onChange={(e) => setAge(e.target.value)}
|
|
795
|
+
/>
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
**Alpha Input (Letters Only)**
|
|
801
|
+
|
|
802
|
+
```tsx
|
|
803
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
804
|
+
import { useState } from 'react';
|
|
805
|
+
|
|
806
|
+
function NameInput() {
|
|
807
|
+
const [name, setName] = useState<string>("");
|
|
808
|
+
|
|
809
|
+
return (
|
|
810
|
+
<HazoUiFlexInput
|
|
811
|
+
input_type="alpha"
|
|
812
|
+
placeholder="Enter name (letters only)..."
|
|
813
|
+
format_guide="Only letters and spaces are allowed"
|
|
814
|
+
format_guide_info={true}
|
|
815
|
+
value={name}
|
|
816
|
+
onChange={(e) => setName(e.target.value)}
|
|
817
|
+
/>
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
```
|
|
821
|
+
|
|
822
|
+
**Email Input with Validation**
|
|
823
|
+
|
|
824
|
+
```tsx
|
|
825
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
826
|
+
import { useState } from 'react';
|
|
827
|
+
|
|
828
|
+
function EmailForm() {
|
|
829
|
+
const [email, setEmail] = useState<string>("");
|
|
830
|
+
|
|
831
|
+
return (
|
|
832
|
+
<HazoUiFlexInput
|
|
833
|
+
input_type="email"
|
|
834
|
+
placeholder="Enter email address..."
|
|
835
|
+
format_guide="Enter a valid email address (e.g., user@example.com)"
|
|
836
|
+
format_guide_info={true}
|
|
837
|
+
value={email}
|
|
838
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
839
|
+
/>
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
**Mixed Input with Length Constraints**
|
|
845
|
+
|
|
846
|
+
```tsx
|
|
847
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
848
|
+
import { useState } from 'react';
|
|
849
|
+
|
|
850
|
+
function UsernameInput() {
|
|
851
|
+
const [username, setUsername] = useState<string>("");
|
|
852
|
+
|
|
853
|
+
return (
|
|
854
|
+
<HazoUiFlexInput
|
|
855
|
+
input_type="mixed"
|
|
856
|
+
placeholder="Enter username (5-20 characters)..."
|
|
857
|
+
text_len_min={5}
|
|
858
|
+
text_len_max={20}
|
|
859
|
+
format_guide="Must be between 5 and 20 characters"
|
|
860
|
+
format_guide_info={true}
|
|
861
|
+
value={username}
|
|
862
|
+
onChange={(e) => setUsername(e.target.value)}
|
|
863
|
+
/>
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
```
|
|
867
|
+
|
|
868
|
+
**Input with Regex Validation**
|
|
869
|
+
|
|
870
|
+
```tsx
|
|
871
|
+
import { HazoUiFlexInput } from 'hazo_ui';
|
|
872
|
+
import { useState } from 'react';
|
|
873
|
+
|
|
874
|
+
function PhoneInput() {
|
|
875
|
+
const [phone, setPhone] = useState<string>("");
|
|
876
|
+
|
|
877
|
+
return (
|
|
878
|
+
<HazoUiFlexInput
|
|
879
|
+
input_type="mixed"
|
|
880
|
+
placeholder="Enter phone number (XXX-XXX-XXXX)..."
|
|
881
|
+
regex={/^\d{3}-\d{3}-\d{4}$/}
|
|
882
|
+
format_guide="Format: XXX-XXX-XXXX (e.g., 123-456-7890)"
|
|
883
|
+
format_guide_info={true}
|
|
884
|
+
value={phone}
|
|
885
|
+
onChange={(e) => setPhone(e.target.value)}
|
|
886
|
+
/>
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
#### Validation Behavior
|
|
892
|
+
|
|
893
|
+
- **Character Filtering**: For `numeric` and `alpha` types, invalid characters are automatically filtered out as the user types
|
|
894
|
+
- **Validation Timing**: Validation occurs when the input loses focus (onBlur event)
|
|
895
|
+
- **Error Display**: Error messages appear below the input in red text when validation fails
|
|
896
|
+
- **Format Guide**: Optional helper text can be displayed below the input (set `format_guide_info={true}`)
|
|
897
|
+
- **Error Priority**: If both an error message and format guide are present, only the error message is shown
|
|
898
|
+
|
|
899
|
+
#### Expected Output
|
|
900
|
+
|
|
901
|
+
The component behaves like a standard input element:
|
|
902
|
+
|
|
903
|
+
```typescript
|
|
904
|
+
// onChange receives a standard React.ChangeEvent<HTMLInputElement>
|
|
905
|
+
onChange={(e) => {
|
|
906
|
+
const value = e.target.value; // Current input value as string
|
|
907
|
+
// Handle value change
|
|
908
|
+
}}
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
#### Error Messages
|
|
912
|
+
|
|
913
|
+
The component provides default error messages for common validation failures:
|
|
914
|
+
|
|
915
|
+
- **Numeric**: "Must be a valid number", "Must be at least X", "Must be at most X", "Maximum X decimal places allowed"
|
|
916
|
+
- **Alpha**: "Only letters are allowed"
|
|
917
|
+
- **Email**: "Must be a valid email address"
|
|
918
|
+
- **Mixed**: "Must be at least X characters", "Must be at most X characters"
|
|
919
|
+
- **Regex**: "Invalid format" (or custom message via `format_guide`)
|
|
920
|
+
|
|
921
|
+
---
|
|
922
|
+
|
|
670
923
|
## Styling
|
|
671
924
|
|
|
672
925
|
Both components use Tailwind CSS and follow shadcn/ui design patterns. Make sure your project has Tailwind CSS configured with the following CSS variables:
|