ui-lab-registry 0.3.42 → 0.3.44
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/dist/components/Slider/index.d.ts.map +1 -1
- package/dist/components/Slider/index.js +2 -13
- package/dist/components/Slider/index.js.map +1 -1
- package/dist/generated-data.d.ts.map +1 -1
- package/dist/generated-data.js +259 -134
- package/dist/generated-data.js.map +1 -1
- package/dist/generated-styles.d.ts.map +1 -1
- package/dist/generated-styles.js +478 -586
- package/dist/generated-styles.js.map +1 -1
- package/dist/generated-styles.json +478 -586
- package/dist/registry.js +2 -2
- package/dist/registry.js.map +1 -1
- package/package.json +2 -2
- package/src/components/Group/metadata.json +3 -2
- package/src/components/Slider/index.tsx +1 -13
- package/src/generated-data.ts +259 -134
- package/src/generated-styles.ts +478 -586
- package/src/registry.ts +2 -2
package/dist/generated-data.js
CHANGED
|
@@ -1042,7 +1042,7 @@ export const generatedAPI = {
|
|
|
1042
1042
|
"name": "title",
|
|
1043
1043
|
"type": "ReactNode",
|
|
1044
1044
|
"required": false,
|
|
1045
|
-
"description": "Header
|
|
1045
|
+
"description": "Header content rendered in preset mode"
|
|
1046
1046
|
},
|
|
1047
1047
|
{
|
|
1048
1048
|
"name": "isExpanded",
|
|
@@ -1086,7 +1086,7 @@ export const generatedAPI = {
|
|
|
1086
1086
|
"name": "styles",
|
|
1087
1087
|
"type": "ExpandStylesProp",
|
|
1088
1088
|
"required": false,
|
|
1089
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or
|
|
1089
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1090
1090
|
}
|
|
1091
1091
|
],
|
|
1092
1092
|
"examples": [
|
|
@@ -1103,7 +1103,6 @@ export const generatedAPI = {
|
|
|
1103
1103
|
"name": "direction",
|
|
1104
1104
|
"type": "row | column",
|
|
1105
1105
|
"required": false,
|
|
1106
|
-
"defaultValue": "row",
|
|
1107
1106
|
"description": "Direction of the flex container",
|
|
1108
1107
|
"enumValues": [
|
|
1109
1108
|
"row",
|
|
@@ -1114,7 +1113,6 @@ export const generatedAPI = {
|
|
|
1114
1113
|
"name": "wrap",
|
|
1115
1114
|
"type": "wrap | nowrap",
|
|
1116
1115
|
"required": false,
|
|
1117
|
-
"defaultValue": "nowrap",
|
|
1118
1116
|
"description": "Whether items wrap to the next line when they overflow",
|
|
1119
1117
|
"enumValues": [
|
|
1120
1118
|
"wrap",
|
|
@@ -1125,7 +1123,6 @@ export const generatedAPI = {
|
|
|
1125
1123
|
"name": "gap",
|
|
1126
1124
|
"type": "xs | sm | md | lg | xl",
|
|
1127
1125
|
"required": false,
|
|
1128
|
-
"defaultValue": "md",
|
|
1129
1126
|
"description": "Gap between flex items",
|
|
1130
1127
|
"enumValues": [
|
|
1131
1128
|
"xs",
|
|
@@ -1139,7 +1136,6 @@ export const generatedAPI = {
|
|
|
1139
1136
|
"name": "justify",
|
|
1140
1137
|
"type": "flex-start | flex-end | center | space-between | space-around | space-evenly",
|
|
1141
1138
|
"required": false,
|
|
1142
|
-
"defaultValue": "flex-start",
|
|
1143
1139
|
"description": "Alignment of items along the main axis",
|
|
1144
1140
|
"enumValues": [
|
|
1145
1141
|
"flex-start",
|
|
@@ -1154,7 +1150,6 @@ export const generatedAPI = {
|
|
|
1154
1150
|
"name": "align",
|
|
1155
1151
|
"type": "flex-start | flex-end | center | stretch | baseline",
|
|
1156
1152
|
"required": false,
|
|
1157
|
-
"defaultValue": "stretch",
|
|
1158
1153
|
"description": "Alignment of items along the cross axis",
|
|
1159
1154
|
"enumValues": [
|
|
1160
1155
|
"flex-start",
|
|
@@ -1303,12 +1298,11 @@ export const generatedAPI = {
|
|
|
1303
1298
|
"name": "styles",
|
|
1304
1299
|
"type": "GalleryStylesProp",
|
|
1305
1300
|
"required": false,
|
|
1306
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or
|
|
1301
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1307
1302
|
}
|
|
1308
1303
|
],
|
|
1309
1304
|
"subComponents": {
|
|
1310
1305
|
"Gallery.Item": {
|
|
1311
|
-
"description": "A single media or content tile in the gallery grid",
|
|
1312
1306
|
"props": [
|
|
1313
1307
|
{
|
|
1314
1308
|
"name": "href",
|
|
@@ -1349,12 +1343,11 @@ export const generatedAPI = {
|
|
|
1349
1343
|
"name": "styles",
|
|
1350
1344
|
"type": "GalleryItemStylesProp",
|
|
1351
1345
|
"required": false,
|
|
1352
|
-
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or
|
|
1346
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1353
1347
|
}
|
|
1354
1348
|
]
|
|
1355
1349
|
},
|
|
1356
1350
|
"Gallery.View": {
|
|
1357
|
-
"description": "Expanded full-screen view overlay for a selected gallery item",
|
|
1358
1351
|
"props": [
|
|
1359
1352
|
{
|
|
1360
1353
|
"name": "aspectRatio",
|
|
@@ -1367,18 +1360,17 @@ export const generatedAPI = {
|
|
|
1367
1360
|
"name": "styles",
|
|
1368
1361
|
"type": "GalleryViewStylesProp",
|
|
1369
1362
|
"required": false,
|
|
1370
|
-
"description": "Classes applied to the root slot. Accepts a string, cn()-compatible array, or
|
|
1363
|
+
"description": "Classes applied to the root slot. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1371
1364
|
}
|
|
1372
1365
|
]
|
|
1373
1366
|
},
|
|
1374
1367
|
"Gallery.Body": {
|
|
1375
|
-
"description": "Container for the gallery item's visible content",
|
|
1376
1368
|
"props": [
|
|
1377
1369
|
{
|
|
1378
1370
|
"name": "styles",
|
|
1379
1371
|
"type": "GalleryBodyStylesProp",
|
|
1380
1372
|
"required": false,
|
|
1381
|
-
"description": "Classes applied to the root slot. Accepts a string, cn()-compatible array, or
|
|
1373
|
+
"description": "Classes applied to the root slot. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1382
1374
|
}
|
|
1383
1375
|
]
|
|
1384
1376
|
}
|
|
@@ -1559,19 +1551,6 @@ export const generatedAPI = {
|
|
|
1559
1551
|
"sm"
|
|
1560
1552
|
]
|
|
1561
1553
|
},
|
|
1562
|
-
{
|
|
1563
|
-
"name": "variant",
|
|
1564
|
-
"type": "primary | secondary | outline | ghost",
|
|
1565
|
-
"required": false,
|
|
1566
|
-
"defaultValue": "primary",
|
|
1567
|
-
"description": "Controls the shared visual style applied to group items",
|
|
1568
|
-
"enumValues": [
|
|
1569
|
-
"primary",
|
|
1570
|
-
"secondary",
|
|
1571
|
-
"outline",
|
|
1572
|
-
"ghost"
|
|
1573
|
-
]
|
|
1574
|
-
},
|
|
1575
1554
|
{
|
|
1576
1555
|
"name": "isDisabled",
|
|
1577
1556
|
"type": "boolean",
|
|
@@ -1649,6 +1628,13 @@ export const generatedAPI = {
|
|
|
1649
1628
|
"type": "InputStylesProp",
|
|
1650
1629
|
"required": false,
|
|
1651
1630
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
1631
|
+
},
|
|
1632
|
+
{
|
|
1633
|
+
"name": "hide-controls",
|
|
1634
|
+
"type": "boolean",
|
|
1635
|
+
"required": false,
|
|
1636
|
+
"defaultValue": "false",
|
|
1637
|
+
"description": "Hides the spinner controls for number inputs"
|
|
1652
1638
|
}
|
|
1653
1639
|
],
|
|
1654
1640
|
"examples": [
|
|
@@ -2881,17 +2867,23 @@ export const generatedAPI = {
|
|
|
2881
2867
|
},
|
|
2882
2868
|
"path": {
|
|
2883
2869
|
"props": [
|
|
2870
|
+
{
|
|
2871
|
+
"name": "children",
|
|
2872
|
+
"type": "ReactNode",
|
|
2873
|
+
"required": true,
|
|
2874
|
+
"description": "Path items rendered inside the ordered list."
|
|
2875
|
+
},
|
|
2884
2876
|
{
|
|
2885
2877
|
"name": "className",
|
|
2886
2878
|
"type": "string",
|
|
2887
2879
|
"required": false,
|
|
2888
|
-
"description": "Additional CSS class
|
|
2880
|
+
"description": "Additional CSS class names applied to the path root."
|
|
2889
2881
|
},
|
|
2890
2882
|
{
|
|
2891
2883
|
"name": "separator",
|
|
2892
2884
|
"type": "ReactNode",
|
|
2893
2885
|
"required": false,
|
|
2894
|
-
"description": "Custom separator
|
|
2886
|
+
"description": "Custom separator rendered between path items."
|
|
2895
2887
|
},
|
|
2896
2888
|
{
|
|
2897
2889
|
"name": "styles",
|
|
@@ -2903,37 +2895,49 @@ export const generatedAPI = {
|
|
|
2903
2895
|
"subComponents": {
|
|
2904
2896
|
"Path.Item": {
|
|
2905
2897
|
"props": [
|
|
2898
|
+
{
|
|
2899
|
+
"name": "children",
|
|
2900
|
+
"type": "ReactNode",
|
|
2901
|
+
"required": true,
|
|
2902
|
+
"description": "Content rendered inside the path item."
|
|
2903
|
+
},
|
|
2906
2904
|
{
|
|
2907
2905
|
"name": "href",
|
|
2908
2906
|
"type": "string",
|
|
2909
2907
|
"required": false,
|
|
2910
|
-
"description": "URL this path item
|
|
2908
|
+
"description": "URL this path item navigates to."
|
|
2911
2909
|
},
|
|
2912
2910
|
{
|
|
2913
2911
|
"name": "onPress",
|
|
2914
2912
|
"type": "(() => void)",
|
|
2915
2913
|
"required": false,
|
|
2916
|
-
"description": "Called when the
|
|
2914
|
+
"description": "Called when the item is activated."
|
|
2917
2915
|
},
|
|
2918
2916
|
{
|
|
2919
2917
|
"name": "isCurrent",
|
|
2920
2918
|
"type": "boolean",
|
|
2921
2919
|
"required": false,
|
|
2922
2920
|
"defaultValue": "false",
|
|
2923
|
-
"description": "Whether this
|
|
2921
|
+
"description": "Whether this item represents the current page."
|
|
2924
2922
|
},
|
|
2925
2923
|
{
|
|
2926
2924
|
"name": "isDisabled",
|
|
2927
2925
|
"type": "boolean",
|
|
2928
2926
|
"required": false,
|
|
2929
2927
|
"defaultValue": "false",
|
|
2930
|
-
"description": "Whether the item is non-interactive"
|
|
2928
|
+
"description": "Whether the item is non-interactive."
|
|
2931
2929
|
},
|
|
2932
2930
|
{
|
|
2933
2931
|
"name": "className",
|
|
2934
2932
|
"type": "string",
|
|
2935
2933
|
"required": false,
|
|
2936
|
-
"description": "Additional CSS class names"
|
|
2934
|
+
"description": "Additional CSS class names applied to the item root."
|
|
2935
|
+
},
|
|
2936
|
+
{
|
|
2937
|
+
"name": "styles",
|
|
2938
|
+
"type": "PathItemStylesProp",
|
|
2939
|
+
"required": false,
|
|
2940
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
2937
2941
|
}
|
|
2938
2942
|
]
|
|
2939
2943
|
}
|
|
@@ -3280,7 +3284,7 @@ export const generatedAPI = {
|
|
|
3280
3284
|
},
|
|
3281
3285
|
{
|
|
3282
3286
|
"name": "defaultValue",
|
|
3283
|
-
"type": "string
|
|
3287
|
+
"type": "string",
|
|
3284
3288
|
"required": false,
|
|
3285
3289
|
"description": "Default display text shown in the trigger when nothing is selected"
|
|
3286
3290
|
},
|
|
@@ -3734,6 +3738,12 @@ export const generatedAPI = {
|
|
|
3734
3738
|
"type": "ReactNode",
|
|
3735
3739
|
"required": false,
|
|
3736
3740
|
"description": "Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts."
|
|
3741
|
+
},
|
|
3742
|
+
{
|
|
3743
|
+
"name": "hide-controls",
|
|
3744
|
+
"type": "boolean",
|
|
3745
|
+
"required": false,
|
|
3746
|
+
"description": "Hides the spinner controls for number inputs"
|
|
3737
3747
|
}
|
|
3738
3748
|
]
|
|
3739
3749
|
}
|
|
@@ -3753,81 +3763,58 @@ export const generatedAPI = {
|
|
|
3753
3763
|
},
|
|
3754
3764
|
"slider": {
|
|
3755
3765
|
"props": [
|
|
3756
|
-
{
|
|
3757
|
-
"name": "size",
|
|
3758
|
-
"type": "sm | md | lg",
|
|
3759
|
-
"required": false,
|
|
3760
|
-
"defaultValue": "md",
|
|
3761
|
-
"description": "Size of the slider track and thumb",
|
|
3762
|
-
"enumValues": [
|
|
3763
|
-
"sm",
|
|
3764
|
-
"md",
|
|
3765
|
-
"lg"
|
|
3766
|
-
]
|
|
3767
|
-
},
|
|
3768
3766
|
{
|
|
3769
3767
|
"name": "disabled",
|
|
3770
3768
|
"type": "boolean",
|
|
3771
3769
|
"required": false,
|
|
3772
|
-
"
|
|
3773
|
-
|
|
3774
|
-
{
|
|
3775
|
-
"name": "className",
|
|
3776
|
-
"type": "string",
|
|
3777
|
-
"required": false,
|
|
3778
|
-
"description": "Additional CSS class for the slider container"
|
|
3779
|
-
},
|
|
3780
|
-
{
|
|
3781
|
-
"name": "style",
|
|
3782
|
-
"type": "CSSProperties",
|
|
3783
|
-
"required": false,
|
|
3784
|
-
"description": "Inline styles for the slider container"
|
|
3770
|
+
"defaultValue": "false",
|
|
3771
|
+
"description": "Whether the slider is disabled."
|
|
3785
3772
|
},
|
|
3786
3773
|
{
|
|
3787
3774
|
"name": "min",
|
|
3788
3775
|
"type": "number",
|
|
3789
3776
|
"required": false,
|
|
3790
3777
|
"defaultValue": "0",
|
|
3791
|
-
"description": "Minimum value of the slider range"
|
|
3778
|
+
"description": "Minimum value of the slider range."
|
|
3792
3779
|
},
|
|
3793
3780
|
{
|
|
3794
3781
|
"name": "max",
|
|
3795
3782
|
"type": "number",
|
|
3796
3783
|
"required": false,
|
|
3797
3784
|
"defaultValue": "100",
|
|
3798
|
-
"description": "Maximum value of the slider range"
|
|
3785
|
+
"description": "Maximum value of the slider range."
|
|
3799
3786
|
},
|
|
3800
3787
|
{
|
|
3801
3788
|
"name": "step",
|
|
3802
3789
|
"type": "number",
|
|
3803
3790
|
"required": false,
|
|
3804
3791
|
"defaultValue": "1",
|
|
3805
|
-
"description": "Step increment between values"
|
|
3792
|
+
"description": "Step increment between values."
|
|
3806
3793
|
},
|
|
3807
3794
|
{
|
|
3808
3795
|
"name": "defaultValue",
|
|
3809
3796
|
"type": "number | number[]",
|
|
3810
3797
|
"required": false,
|
|
3811
|
-
"description": "Initial value
|
|
3798
|
+
"description": "Initial value or values for uncontrolled usage."
|
|
3812
3799
|
},
|
|
3813
3800
|
{
|
|
3814
3801
|
"name": "value",
|
|
3815
3802
|
"type": "number | number[]",
|
|
3816
3803
|
"required": false,
|
|
3817
|
-
"description": "Controlled value
|
|
3804
|
+
"description": "Controlled value or values for the slider thumbs."
|
|
3818
3805
|
},
|
|
3819
3806
|
{
|
|
3820
3807
|
"name": "onValueChange",
|
|
3821
3808
|
"type": "((value: number[]) => void)",
|
|
3822
3809
|
"required": false,
|
|
3823
|
-
"description": "Called when the value changes"
|
|
3810
|
+
"description": "Called when the slider value changes."
|
|
3824
3811
|
},
|
|
3825
3812
|
{
|
|
3826
3813
|
"name": "orientation",
|
|
3827
3814
|
"type": "horizontal | vertical",
|
|
3828
3815
|
"required": false,
|
|
3829
3816
|
"defaultValue": "horizontal",
|
|
3830
|
-
"description": "Orientation of the slider track",
|
|
3817
|
+
"description": "Orientation of the slider track.",
|
|
3831
3818
|
"enumValues": [
|
|
3832
3819
|
"horizontal",
|
|
3833
3820
|
"vertical"
|
|
@@ -3837,13 +3824,13 @@ export const generatedAPI = {
|
|
|
3837
3824
|
"name": "aria-label",
|
|
3838
3825
|
"type": "string",
|
|
3839
3826
|
"required": false,
|
|
3840
|
-
"description": "Accessible label for the slider"
|
|
3827
|
+
"description": "Accessible label for the slider."
|
|
3841
3828
|
},
|
|
3842
3829
|
{
|
|
3843
3830
|
"name": "aria-labelledby",
|
|
3844
3831
|
"type": "string",
|
|
3845
3832
|
"required": false,
|
|
3846
|
-
"description": "ID of an element that labels the slider"
|
|
3833
|
+
"description": "ID of an element that labels the slider."
|
|
3847
3834
|
},
|
|
3848
3835
|
{
|
|
3849
3836
|
"name": "styles",
|
|
@@ -3852,6 +3839,87 @@ export const generatedAPI = {
|
|
|
3852
3839
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
3853
3840
|
}
|
|
3854
3841
|
],
|
|
3842
|
+
"subComponents": {
|
|
3843
|
+
"Root": {
|
|
3844
|
+
"props": [
|
|
3845
|
+
{
|
|
3846
|
+
"name": "disabled",
|
|
3847
|
+
"type": "boolean",
|
|
3848
|
+
"required": false,
|
|
3849
|
+
"defaultValue": "false",
|
|
3850
|
+
"description": "Whether the slider is disabled."
|
|
3851
|
+
},
|
|
3852
|
+
{
|
|
3853
|
+
"name": "min",
|
|
3854
|
+
"type": "number",
|
|
3855
|
+
"required": false,
|
|
3856
|
+
"defaultValue": "0",
|
|
3857
|
+
"description": "Minimum value of the slider range."
|
|
3858
|
+
},
|
|
3859
|
+
{
|
|
3860
|
+
"name": "max",
|
|
3861
|
+
"type": "number",
|
|
3862
|
+
"required": false,
|
|
3863
|
+
"defaultValue": "100",
|
|
3864
|
+
"description": "Maximum value of the slider range."
|
|
3865
|
+
},
|
|
3866
|
+
{
|
|
3867
|
+
"name": "step",
|
|
3868
|
+
"type": "number",
|
|
3869
|
+
"required": false,
|
|
3870
|
+
"defaultValue": "1",
|
|
3871
|
+
"description": "Step increment between values."
|
|
3872
|
+
},
|
|
3873
|
+
{
|
|
3874
|
+
"name": "defaultValue",
|
|
3875
|
+
"type": "number | number[]",
|
|
3876
|
+
"required": false,
|
|
3877
|
+
"description": "Initial value or values for uncontrolled usage."
|
|
3878
|
+
},
|
|
3879
|
+
{
|
|
3880
|
+
"name": "value",
|
|
3881
|
+
"type": "number | number[]",
|
|
3882
|
+
"required": false,
|
|
3883
|
+
"description": "Controlled value or values for the slider thumbs."
|
|
3884
|
+
},
|
|
3885
|
+
{
|
|
3886
|
+
"name": "onValueChange",
|
|
3887
|
+
"type": "((value: number[]) => void)",
|
|
3888
|
+
"required": false,
|
|
3889
|
+
"description": "Called when the slider value changes."
|
|
3890
|
+
},
|
|
3891
|
+
{
|
|
3892
|
+
"name": "orientation",
|
|
3893
|
+
"type": "horizontal | vertical",
|
|
3894
|
+
"required": false,
|
|
3895
|
+
"defaultValue": "horizontal",
|
|
3896
|
+
"description": "Orientation of the slider track.",
|
|
3897
|
+
"enumValues": [
|
|
3898
|
+
"horizontal",
|
|
3899
|
+
"vertical"
|
|
3900
|
+
]
|
|
3901
|
+
},
|
|
3902
|
+
{
|
|
3903
|
+
"name": "aria-label",
|
|
3904
|
+
"type": "string",
|
|
3905
|
+
"required": false,
|
|
3906
|
+
"description": "Accessible label for the slider."
|
|
3907
|
+
},
|
|
3908
|
+
{
|
|
3909
|
+
"name": "aria-labelledby",
|
|
3910
|
+
"type": "string",
|
|
3911
|
+
"required": false,
|
|
3912
|
+
"description": "ID of an element that labels the slider."
|
|
3913
|
+
},
|
|
3914
|
+
{
|
|
3915
|
+
"name": "styles",
|
|
3916
|
+
"type": "SliderStylesProp",
|
|
3917
|
+
"required": false,
|
|
3918
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
3919
|
+
}
|
|
3920
|
+
]
|
|
3921
|
+
}
|
|
3922
|
+
},
|
|
3855
3923
|
"examples": [
|
|
3856
3924
|
{
|
|
3857
3925
|
"title": "Basic Slider",
|
|
@@ -4034,7 +4102,7 @@ export const generatedAPI = {
|
|
|
4034
4102
|
"type": "sm | md | lg",
|
|
4035
4103
|
"required": false,
|
|
4036
4104
|
"defaultValue": "md",
|
|
4037
|
-
"description": "Size of the textarea",
|
|
4105
|
+
"description": "Size of the textarea.",
|
|
4038
4106
|
"enumValues": [
|
|
4039
4107
|
"sm",
|
|
4040
4108
|
"md",
|
|
@@ -4046,7 +4114,7 @@ export const generatedAPI = {
|
|
|
4046
4114
|
"type": "boolean",
|
|
4047
4115
|
"required": false,
|
|
4048
4116
|
"defaultValue": "false",
|
|
4049
|
-
"description": "Whether to apply error styling"
|
|
4117
|
+
"description": "Whether to apply error styling."
|
|
4050
4118
|
},
|
|
4051
4119
|
{
|
|
4052
4120
|
"name": "resizable",
|
|
@@ -4060,27 +4128,84 @@ export const generatedAPI = {
|
|
|
4060
4128
|
"type": "boolean",
|
|
4061
4129
|
"required": false,
|
|
4062
4130
|
"defaultValue": "false",
|
|
4063
|
-
"description": "Whether to display a character count below the textarea"
|
|
4131
|
+
"description": "Whether to display a character count below the textarea."
|
|
4064
4132
|
},
|
|
4065
4133
|
{
|
|
4066
4134
|
"name": "maxCharacters",
|
|
4067
4135
|
"type": "number",
|
|
4068
4136
|
"required": false,
|
|
4069
|
-
"description": "Maximum number of characters allowed"
|
|
4137
|
+
"description": "Maximum number of characters allowed."
|
|
4070
4138
|
},
|
|
4071
4139
|
{
|
|
4072
4140
|
"name": "maxHeight",
|
|
4073
4141
|
"type": "string",
|
|
4074
4142
|
"required": false,
|
|
4075
|
-
"description": "Maximum height before the custom scrollbar activates"
|
|
4143
|
+
"description": "Maximum height before the custom scrollbar activates."
|
|
4076
4144
|
},
|
|
4077
4145
|
{
|
|
4078
4146
|
"name": "styles",
|
|
4079
|
-
"type": "
|
|
4147
|
+
"type": "TextareaStylesProp",
|
|
4080
4148
|
"required": false,
|
|
4081
4149
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
4082
4150
|
}
|
|
4083
4151
|
],
|
|
4152
|
+
"subComponents": {
|
|
4153
|
+
"TextArea": {
|
|
4154
|
+
"props": [
|
|
4155
|
+
{
|
|
4156
|
+
"name": "size",
|
|
4157
|
+
"type": "sm | md | lg",
|
|
4158
|
+
"required": false,
|
|
4159
|
+
"defaultValue": "md",
|
|
4160
|
+
"description": "Size of the textarea.",
|
|
4161
|
+
"enumValues": [
|
|
4162
|
+
"sm",
|
|
4163
|
+
"md",
|
|
4164
|
+
"lg"
|
|
4165
|
+
]
|
|
4166
|
+
},
|
|
4167
|
+
{
|
|
4168
|
+
"name": "error",
|
|
4169
|
+
"type": "boolean",
|
|
4170
|
+
"required": false,
|
|
4171
|
+
"defaultValue": "false",
|
|
4172
|
+
"description": "Whether to apply error styling."
|
|
4173
|
+
},
|
|
4174
|
+
{
|
|
4175
|
+
"name": "resizable",
|
|
4176
|
+
"type": "boolean",
|
|
4177
|
+
"required": false,
|
|
4178
|
+
"defaultValue": "true",
|
|
4179
|
+
"description": "Whether the textarea can be manually resized by the user. When enabled, `className` may include Tailwind `resize`, `resize-x`, `resize-y`, or `resize-none` to select the resize axis."
|
|
4180
|
+
},
|
|
4181
|
+
{
|
|
4182
|
+
"name": "showCharacterCount",
|
|
4183
|
+
"type": "boolean",
|
|
4184
|
+
"required": false,
|
|
4185
|
+
"defaultValue": "false",
|
|
4186
|
+
"description": "Whether to display a character count below the textarea."
|
|
4187
|
+
},
|
|
4188
|
+
{
|
|
4189
|
+
"name": "maxCharacters",
|
|
4190
|
+
"type": "number",
|
|
4191
|
+
"required": false,
|
|
4192
|
+
"description": "Maximum number of characters allowed."
|
|
4193
|
+
},
|
|
4194
|
+
{
|
|
4195
|
+
"name": "maxHeight",
|
|
4196
|
+
"type": "string",
|
|
4197
|
+
"required": false,
|
|
4198
|
+
"description": "Maximum height before the custom scrollbar activates."
|
|
4199
|
+
},
|
|
4200
|
+
{
|
|
4201
|
+
"name": "styles",
|
|
4202
|
+
"type": "TextareaStylesProp",
|
|
4203
|
+
"required": false,
|
|
4204
|
+
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
4205
|
+
}
|
|
4206
|
+
]
|
|
4207
|
+
}
|
|
4208
|
+
},
|
|
4084
4209
|
"examples": [
|
|
4085
4210
|
{
|
|
4086
4211
|
"title": "Basic TextArea",
|
|
@@ -4236,25 +4361,25 @@ export const generatedAPI = {
|
|
|
4236
4361
|
}
|
|
4237
4362
|
};
|
|
4238
4363
|
export const generatedStyles = {
|
|
4239
|
-
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .root {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground, currentColor);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover, var(--foreground-400));\n }\n\n
|
|
4364
|
+
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .root {\n @apply inline-block relative cursor-pointer;\n display: inline-block;\n color: var(--foreground, currentColor);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover, var(--foreground-400));\n }\n\n &[data-focus-visible=\"true\"] {\n outline: 2px solid var(--focus-visible, var(--focus-ring));\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background, var(--background-600));\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .preview {\n }\n}\n",
|
|
4240
4365
|
"badge": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2;\n height: fit-content;\n width: fit-content;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .pill {\n border-radius: 9999px;\n }\n\n .icon {\n @apply flex items-center shrink-0;\n }\n\n .dismiss {\n @apply ml-1 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-xs, 0.25rem);\n background: transparent;\n border: none;\n color: var(--dismiss-foreground, var(--foreground-400));\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background, color-mix(in srgb, var(--background-700) 80%, var(--background-900)));\n }\n\n .dismiss[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background, var(--background-700));\n transform: scale(0.95);\n }\n\n .dismiss[data-focus-visible=\"true\"] {\n box-shadow: 0 0 0 1.5px var(--dismiss-focus-visible, var(--focus-visible));\n }\n}\n",
|
|
4241
4366
|
"banner": "@reference \"tailwindcss\";\n\n@layer components {\n .banner {\n @apply flex w-full items-start gap-4;\n font-family: inherit;\n font-weight: var(--font-weight-medium, 500);\n line-height: var(--leading-normal, 1.5);\n background-color: var(--background, var(--background-900));\n color: var(--foreground, var(--foreground-200));\n border: var(--border-width-base, 1px) solid var(--border, var(--background-700));\n border-radius: var(--radius-sm, 0.375rem);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .banner:hover {\n background-color: var(--background-hover, var(--background));\n border-color: var(--border-hover, var(--border));\n }\n\n .banner[data-pressed=\"true\"] {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--border-pressed, var(--border-hover, var(--border)));\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .iconContainer {\n @apply flex shrink-0 items-center justify-center self-start;\n }\n\n .icon {\n @apply mr-4 h-5 w-5;\n color: var(--icon-color, currentColor);\n }\n\n .dismiss {\n @apply flex h-8 w-8 shrink-0 items-center justify-center p-0 cursor-pointer;\n background-color: transparent;\n color: currentColor;\n border: none;\n border-radius: var(--radius-sm, 0.375rem);\n transition: background-color 0.15s ease-out;\n\n &:hover {\n background-color: var(--dismiss-hover-background, transparent);\n }\n\n &[data-pressed=\"true\"] {\n background-color: var(--dismiss-pressed-background, transparent);\n }\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n }\n\n .title {\n font-weight: var(--font-weight-semibold, 600);\n font-size: inherit;\n line-height: var(--leading-tight, 1.25);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium, 500);\n font-size: inherit;\n line-height: var(--leading-normal, 1.5);\n @apply my-0;\n }\n}\n\n\n.banner.sm {\n @apply px-3 py-2;\n}\n\n.banner.md {\n @apply px-4 py-3;\n}\n\n.banner.lg {\n @apply px-6 py-4;\n}\n",
|
|
4242
|
-
"button": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--background-pressed-border, var(--background-hover-border, var(--background-border)));\n }\n\n &:focus-visible {\n
|
|
4367
|
+
"button": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--background-pressed-border, var(--background-hover-border, var(--background-border)));\n }\n\n &:focus-visible {\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
|
|
4243
4368
|
"card": "@reference \"tailwindcss\";\n\n@layer components {\n .card {\n @apply overflow-hidden;\n background-color: var(--background, var(--background-800));\n border: var(--border-width-base, 1px) solid var(--background-border, var(--border));\n border-radius: var(--radius-sm, 0.375rem);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .card:hover {\n background-color: var(--background-hover, var(--background));\n border-color: var(--background-hover-border, var(--background-border, var(--border)));\n }\n\n .card[data-pressed=\"true\"] {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--background-pressed-border, var(--background-hover-border, var(--background-border, var(--border))));\n }\n\n .card[data-focused=\"true\"] {\n outline: 2px solid var(--focus-visible, var(--focus-ring));\n outline-offset: 2px;\n }\n\n .header {\n @apply p-4;\n border-bottom: var(--border-width-base, 1px) solid var(--background-border, var(--border));\n }\n\n .body {\n @apply px-4 py-2;\n }\n\n .footer {\n @apply px-2 py-2;\n background-color: var(--background, var(--background-800));\n border-top: var(--border-width-base, 1px) solid var(--background-border, var(--border));\n }\n}\n",
|
|
4244
|
-
"checkbox": "@reference \"tailwindcss\";\n\n@layer components {\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .container {\n @apply relative inline-flex items-center justify-center;\n }\n\n .checkbox {\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-xs, 0.25rem);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-
|
|
4369
|
+
"checkbox": "@reference \"tailwindcss\";\n\n@layer components {\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .container {\n @apply relative inline-flex items-center justify-center;\n }\n\n .checkbox {\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-xs, 0.25rem);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background-indeterminate);\n border-color: var(--background-indeterminate-border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity, 0.6);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n .checkmark,\n .indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--icon-foreground);\n pointer-events: none;\n }\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-disabled=\"true\"] {\n @apply opacity-60 cursor-not-allowed;\n }\n }\n\n .label-sm {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .label-md {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .label-lg {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--helper-text-foreground);\n\n &[data-error=\"true\"] {\n color: var(--helper-text-error-foreground);\n }\n }\n}\n",
|
|
4245
4370
|
"code": "@reference \"tailwindcss\";\n\n@layer components {\n .code {\n --border-color: var(--background-700);\n --header-bg: mix(var(--background-900) 90%, transparent);\n --scroll-track-bg: mix(var(--background-950) 50%, transparent);\n\n max-height: 52.5rem;\n border-radius: var(--radius-sm);\n border: 1px solid var(--border-color);\n @apply flex w-full min-w-0 flex-col overflow-hidden;\n }\n\n .header {\n flex: none;\n background-color: var(--header-bg);\n @apply flex items-center justify-between px-3 py-1.5;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n border-bottom: 1px solid var(--border-color);\n color: var(--foreground-400);\n }\n\n\n .body {\n @apply relative flex min-h-0 flex-1 flex-col;\n flex: 1;\n }\n\n .viewport { @apply overflow-hidden; }\n\n .viewport :global(pre) {\n background: transparent;\n @apply m-0 p-0;\n width: fit-content;\n }\n\n .viewport :global(code) {\n color: var(--foreground-300);\n white-space: pre;\n }\n\n .viewport::-webkit-scrollbar {\n width: 0.5rem;\n }\n\n .viewport::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .viewport::-webkit-scrollbar-thumb {\n background: var(--background-700);\n border-radius: 9999px;\n }\n\n .viewport::-webkit-scrollbar-thumb:hover {\n background: var(--background-600);\n }\n\n .scroll-track {\n flex: none;\n @apply w-full;\n overflow-x: auto;\n background-color: var(--scroll-track-bg);\n backdrop-filter: blur(4px);\n }\n\n .expand-button {\n @apply flex w-full items-center gap-3 px-4 py-2 cursor-pointer;\n color: var(--foreground-300);\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n transition: background-color 0.15s ease-out;\n border-top: 1px solid var(--border-color);\n background: transparent;\n border-left: none;\n border-right: none;\n border-bottom: none;\n font-family: inherit;\n }\n\n .expand-button:hover { background-color: var(--background-800); }\n\n .expand-icon { @apply shrink-0; color: var(--foreground-400); }\n\n .copy-button {\n @apply absolute right-2 top-2 flex items-center justify-center p-1 cursor-pointer;\n border-radius: var(--radius-sm);\n color: var(--foreground-400);\n opacity: 0;\n transition: opacity 0.15s ease-out, background-color 0.15s ease-out, color 0.15s ease-out;\n background: transparent;\n border: none;\n z-index: 1;\n }\n\n .copy-button:hover { background-color: var(--background-800); color: var(--foreground-300); }\n\n .copy-button:focus, .body:hover .copy-button { opacity: 1; }\n}\n",
|
|
4246
4371
|
"color": "@reference \"tailwindcss\";\n\n@layer components {\n .color {\n --background: color-mix(in srgb, var(--background-800) 30%, transparent);\n --background-border: var(--background-700);\n --focus-visible: var(--accent-500);\n\n display: flex;\n flex-direction: column;\n gap: 1rem;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n width: 260px;\n }\n\n .color[data-disabled=\"true\"] {\n opacity: 0.5;\n pointer-events: none;\n }\n\n .controls {\n @apply pb-3 px-3 space-y-3;\n }\n\n .input-group {\n width: 100%;\n }\n\n .input-group > div:first-child {\n flex: 1;\n min-width: 0;\n }\n\n .input {\n width: 100%;\n }\n\n .format {\n flex-shrink: 0;\n width: 85px;\n }\n\n .color[data-size=\"sm\"] .format {\n width: 75px;\n }\n\n .canvas {\n position: relative;\n width: 96%;\n @apply mx-auto mt-2;\n cursor: crosshair;\n touch-action: none;\n display: flex;\n flex-direction: column;\n min-height: 160px;\n }\n\n .canvas[data-focus-visible=\"true\"] {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n\n .canvas-inner {\n position: relative;\n width: 100%;\n flex: 1;\n overflow: hidden;\n }\n\n .canvas-gradient-hue {\n position: absolute;\n inset: 0;\n overflow: hidden;\n }\n\n .canvas-gradient-saturation {\n position: absolute;\n inset: 0;\n background: linear-gradient(to right, rgb(255, 255, 255), transparent);\n }\n\n .canvas-gradient-brightness {\n position: absolute;\n inset: 0;\n background: linear-gradient(to top, rgb(0, 0, 0), transparent);\n }\n\n .canvas-pointer {\n --pointer-border: color-mix(in srgb, var(--foreground-200) 50%, transparent);\n\n position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid var(--pointer-border);\n box-shadow: 0 0 0 1px rgb(0 0 0 / 0.3), 0 2px 4px rgb(0 0 0 / 0.3);\n pointer-events: none;\n transform: translate(-50%, -50%);\n z-index: 10;\n }\n\n .hue-slider {\n display: flex;\n align-items: center;\n height: 16px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .hue-track {\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: var(--radius-full);\n background: linear-gradient(\n to right,\n hsl(0, 100%, 50%),\n hsl(60, 100%, 50%),\n hsl(120, 100%, 50%),\n hsl(180, 100%, 50%),\n hsl(240, 100%, 50%),\n hsl(300, 100%, 50%),\n hsl(360, 100%, 50%)\n );\n border: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .hue-thumb {\n --thumb-border: white;\n --thumb-background: white;\n\n position: absolute;\n width: 12px;\n height: 12px;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n box-shadow: 0 2px 4px rgb(0 0 0 / 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .hue-slider[data-focus-visible=\"true\"] .hue-thumb {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n\n .hue-thumb:hover {\n box-shadow: 0 2px 6px rgb(0 0 0 / 0.4);\n }\n\n .hue-thumb:active {\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);\n }\n\n .opacity-slider {\n display: flex;\n align-items: center;\n height: 12px;\n border-radius: var(--radius-full);\n position: relative;\n cursor: pointer;\n touch-action: none;\n overflow: hidden;\n }\n\n .opacity-track {\n --checkerboard-dark: var(--background-800);\n --checkerboard-light: var(--background-700);\n\n position: relative;\n width: 100%;\n height: 100%;\n border-radius: var(--radius-full);\n border: var(--border-width-base, 1px) solid var(--background-border);\n overflow: hidden;\n }\n\n .opacity-thumb {\n --thumb-border: white;\n --thumb-background: white;\n\n position: absolute;\n width: 10px;\n height: 10px;\n border-radius: var(--radius-full);\n border: 2px solid var(--thumb-border);\n box-shadow: 0 2px 4px rgb(0 0 0 / 0.3);\n top: 50%;\n transform: translateY(-50%) translateX(-50%);\n background: var(--thumb-background);\n pointer-events: none;\n }\n\n .opacity-slider[data-focus-visible=\"true\"] .opacity-thumb {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n\n .opacity-thumb:hover {\n box-shadow: 0 2px 6px rgb(0 0 0 / 0.4);\n }\n\n .opacity-thumb:active {\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.3);\n }\n\n .recent-colors {\n display: flex;\n gap: 0.5rem;\n overflow-x: auto;\n padding-bottom: 0.25rem;\n }\n\n .recent-color-swatch {\n flex-shrink: 0;\n width: 32px;\n height: 32px;\n border-radius: var(--radius-sm);\n border: var(--border-width-base, 1px) solid var(--background-border);\n cursor: pointer;\n background: none;\n padding: 0;\n outline: none;\n }\n\n .recent-color-swatch:hover {\n transform: scale(1.1);\n box-shadow: 0 0 0 2px var(--focus-visible);\n }\n\n .recent-color-swatch:active {\n transform: scale(0.95);\n }\n\n .recent-color-swatch:focus-visible {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n\n .preview-swatch {\n --checkerboard-dark: var(--background-700);\n --checkerboard-light: var(--background-800);\n\n position: relative;\n width: 36px;\n height: 36px;\n border-radius: var(--radius-sm);\n border: var(--border-width-base, 1px) solid var(--background-border);\n box-shadow: 0 1px 3px rgb(0 0 0 / 0.1);\n overflow: hidden;\n flex-shrink: 0;\n }\n\n .preview-swatch::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 6px,\n var(--checkerboard-light) 6px,\n var(--checkerboard-light) 12px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n\n .preview-swatch::after {\n content: \"\";\n position: absolute;\n inset: 0;\n background-color: var(--preview-color, transparent);\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 2;\n }\n\n .preview {\n --checkerboard-dark: var(--background-700);\n --checkerboard-light: var(--background-800);\n\n position: relative;\n width: 64px;\n height: 64px;\n border-radius: var(--radius-sm);\n border: var(--border-width-base, 1px) solid var(--background-border);\n box-shadow: 0 2px 8px rgb(0 0 0 / 0.2);\n overflow: hidden;\n }\n\n .preview::before {\n content: \"\";\n position: absolute;\n inset: 0;\n background-image: repeating-linear-gradient(\n 45deg,\n var(--checkerboard-dark),\n var(--checkerboard-dark) 10px,\n var(--checkerboard-light) 10px,\n var(--checkerboard-light) 20px\n );\n border-radius: var(--radius-sm);\n pointer-events: none;\n z-index: 1;\n }\n}\n",
|
|
4247
4372
|
"command": "@reference \"tailwindcss\";\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-color);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-1.5;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .input {\n border-color: transparent;\n background: transparent;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n\n /* List Section */\n .list {\n @apply py-0.5 px-2 space-y-2;\n background-color: var(--background-list);\n }\n\n .list :global([role=\"listbox\"]) {\n @apply flex w-full flex-col;\n }\n\n .item {\n @apply flex items-center justify-between rounded-sm px-2 py-0.5 cursor-pointer;\n border-radius: 0.375rem;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n color: var(--foreground);\n }\n\n .item:hover {\n background-color: var(--background-hover);\n }\n\n .item[data-highlighted=\"true\"] {\n background-color: var(--background-pressed);\n }\n\n .item-content {\n @apply flex min-w-0 flex-1 items-center gap-2.5;\n flex: 1;\n }\n\n .item-icon {\n @apply flex h-6 w-6 shrink-0 items-center justify-center;\n color: var(--foreground);\n }\n\n .item-labels {\n flex: 1;\n @apply min-w-0;\n }\n\n .item-label {\n font-size: var(--text-sm);\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n color: var(--foreground-muted);\n font-size: 0.875rem;\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .hint-wrapper {\n @apply flex items-center;\n }\n\n .category-header {\n @apply px-2 py-1.5 mt-2 first:mt-0;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n color: var(--foreground-muted);\n }\n\n /* Empty State */\n .empty {\n padding: 1.5rem 1rem;\n text-align: center;\n font-size: 0.875rem;\n color: var(--foreground-muted);\n }\n\n /* Footer */\n .footer {\n @apply flex w-full items-center gap-2 px-1.5 py-2;\n background-color: var(--background-footer);\n border-top: 1px solid var(--border-color);\n justify-content: flex-between;\n }\n\n /* Animations */\n @keyframes fade-in-zoom-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n}\n",
|
|
4248
4373
|
"confirm": "@reference \"tailwindcss\";\n\n@layer components {\n .confirm {\n --overlay-background: mix(var(--background-950) 50%, transparent);\n --header-foreground: var(--foreground-100);\n --description-foreground: var(--foreground-300);\n --error-foreground: var(--foreground-danger);\n --countdown-foreground: var(--foreground-400);\n --label-foreground: var(--foreground-300);\n --input-background: var(--background-800);\n --input-border-color: var(--background-700);\n --input-foreground: var(--foreground-100);\n --input-focus-visible: var(--accent-500);\n }\n\n .container {\n @apply flex flex-col;\n }\n\n .card {\n @apply max-w-[28rem];\n }\n\n .body {\n @apply flex flex-col gap-4;\n }\n\n .body-compact {\n @apply gap-3;\n }\n\n .dialog-overlay {\n @apply fixed inset-0 z-50 flex items-center justify-center;\n background-color: var(--overlay-background);\n }\n\n .dialog-card {\n @apply max-w-[28rem];\n margin: 0 1rem;\n }\n\n .header {\n @apply flex items-start gap-3;\n }\n\n .header-content {\n @apply flex-1;\n }\n\n .header-title {\n @apply font-semibold;\n color: var(--header-foreground);\n }\n\n .description {\n font-size: var(--text-sm);\n color: var(--description-foreground);\n }\n\n .error-message {\n font-size: var(--text-sm);\n color: var(--error-foreground);\n }\n\n .warning-box {\n @apply p-3 rounded-sm;\n border: var(--border-width-base, 1px) solid var(--warning-border-color);\n background-color: var(--warning-background);\n color: var(--warning-foreground);\n font-size: var(--text-sm);\n }\n\n .warning-box-low {\n --warning-background: var(--warning-background-low);\n --warning-border-color: var(--warning-border-color-low);\n --warning-foreground: var(--warning-foreground-low);\n }\n\n .warning-box-medium {\n --warning-background: var(--warning-background-medium);\n --warning-border-color: var(--warning-border-color-medium);\n --warning-foreground: var(--warning-foreground-medium);\n }\n\n .warning-box-high {\n --warning-background: var(--warning-background-high);\n --warning-border-color: var(--warning-border-color-high);\n --warning-foreground: var(--warning-foreground-high);\n }\n\n .warning-box-critical {\n --warning-background: var(--warning-background-critical);\n --warning-border-color: var(--warning-border-color-critical);\n --warning-foreground: var(--warning-foreground-critical);\n }\n\n .countdown-text {\n font-size: var(--text-sm);\n color: var(--countdown-foreground);\n }\n\n .input-label {\n font-size: var(--text-sm);\n margin-left: 0.25rem;\n color: var(--label-foreground);\n }\n\n .input {\n @apply w-full mt-2 px-3 py-2 rounded-sm transition-all duration-200;\n background-color: var(--input-background);\n border: var(--border-width-base, 1px) solid var(--input-border-color);\n color: var(--input-foreground);\n font-size: var(--text-sm);\n\n &:focus-visible {\n outline: 2px solid var(--input-focus-visible);\n outline-offset: 2px;\n }\n }\n\n .actions {\n @apply flex gap-2;\n }\n\n .actions-inline {\n @apply flex-row;\n }\n\n .actions-dialog {\n @apply flex-row justify-end;\n }\n}\n",
|
|
4249
4374
|
"date": "@reference \"tailwindcss\";\n\n@layer components {\n .calendar {\n --disabled-opacity: 0.5;\n\n @apply inline-flex flex-col overflow-hidden gap-0;\n border-radius: var(--radius-md);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n }\n\n .day-headers {\n @apply grid gap-2 px-4 pt-3 pb-1;\n grid-template-columns: repeat(7, 1fr);\n background: var(--day-headers-background);\n border-top: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-md) var(--radius-md) 0 0;\n }\n\n .day-header {\n @apply flex items-center justify-center;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n color: var(--day-header-color);\n }\n\n .header {\n @apply flex items-center justify-between gap-4 pl-2 pr-1.5 py-1.5;\n color: var(--header-color);\n }\n\n .month-year {\n @apply ml-2;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n text-align: center;\n }\n\n .nav-button {\n @apply inline-flex min-h-8 min-w-8 items-center justify-center cursor-pointer;\n border-radius: var(--radius-sm);\n background-color: transparent;\n color: var(--nav-button-color);\n border: 1px solid transparent;\n font-size: var(--text-sm);\n font-weight: 500;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .nav-button:hover { background-color: var(--nav-button-background-hover); }\n\n .nav-button:focus-visible {\n background: var(--nav-button-background-hover);\n border-radius: 0px;\n outline: 0px solid var(--accent-500);\n }\n\n .grid {\n @apply grid gap-1 px-4 pb-4;\n grid-template-columns: repeat(7, 1fr); /* 7 days only */\n background: var(--grid-background);\n border-radius: 0 0 var(--radius-sm) var(--radius-sm);\n }\n\n .day-cell {\n --cell-background: transparent;\n\n @apply flex min-h-8 items-center justify-center px-2.5 py-2 cursor-pointer;\n border-radius: var(--radius-base);\n background-color: var(--cell-background);\n color: var(--cell-text);\n border: 2px solid transparent;\n font-size: var(--text-sm);\n font-weight: 400;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .week-header {\n display: none;\n }\n\n .week-number {\n display: none;\n }\n}\n\n/* Variant states - these are outside @layer */\n.day-cell[data-selected=\"true\"] {\n font-weight: 500;\n}\n\n.day-cell[data-today=\"true\"] {\n border-color: transparent;\n}\n\n.day-cell[data-disabled=\"true\"],\n.day-cell[data-out-of-range=\"true\"] {\n opacity: var(--disabled-opacity);\n}\n\n.day-cell[data-disabled=\"true\"] { cursor: not-allowed; }\n\n.day-cell[data-focus-visible=\"true\"]:not([data-disabled=\"true\"]) { outline: 2px solid var(--focus-ring); outline-offset: 2px; }\n",
|
|
4250
4375
|
"divider": "@reference \"tailwindcss\";\n\n@layer components {\n .divider {\n --divider-background: var(--background);\n }\n}\n",
|
|
4251
|
-
"expand": "@reference \"tailwindcss\";\n\n@layer components {\n .expand {\n --disabled-opacity: 0.6;\n\n @apply flex flex-col;\n }\n\n .expand[data-disabled] {\n opacity: var(--disabled-opacity);\n
|
|
4252
|
-
"flex": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full
|
|
4376
|
+
"expand": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n position: relative;\n display: block;\n width: 100%;\n }\n\n .expand {\n --expand-disabled-opacity: 0.6;\n --expand-trigger-padding-x: 0.75rem;\n --expand-trigger-padding-y: 0.5rem;\n --expand-divider-spacing: 0.5rem;\n\n @apply flex w-full flex-col;\n }\n\n .expand[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--expand-disabled-opacity);\n }\n\n .trigger {\n @apply flex w-full items-stretch justify-between border-0 p-0 text-left;\n\n appearance: none;\n color: var(--foreground);\n background-color: var(--background);\n cursor: pointer;\n border-radius: 0;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n outline: none;\n box-shadow: none;\n transition:\n background-color 200ms var(--ease-smooth-settle),\n opacity 200ms var(--ease-smooth-settle)\n }\n\n .trigger:focus,\n .trigger:focus-visible {\n outline: none;\n box-shadow: none;\n }\n\n .trigger[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--expand-disabled-opacity);\n }\n\n .title {\n @apply flex min-w-0 flex-1 items-center overflow-hidden;\n\n padding: var(--expand-trigger-padding-y) 0 var(--expand-trigger-padding-y)\n var(--expand-trigger-padding-x);\n font-weight: var(--font-weight-medium);\n border-radius: 0;\n color: inherit;\n background-color: transparent;\n }\n\n .trigger:not([data-disabled=\"true\"]):has(.icon:hover) .title {\n background-color: transparent;\n }\n\n .icon {\n @apply flex shrink-0 items-center justify-center;\n\n padding: var(--expand-trigger-padding-y) var(--expand-trigger-padding-x);\n color: inherit;\n border-radius: 0;\n }\n\n @media (hover: hover) {\n .trigger:not([data-disabled=\"true\"]):hover .title,\n .trigger:not([data-disabled=\"true\"]):hover .icon {\n background-color: var(--background-hover);\n }\n\n .trigger:not([data-disabled=\"true\"]):has(.icon:hover) .title {\n background-color: transparent;\n }\n\n .trigger:not([data-disabled=\"true\"]) .icon:hover {\n border-radius: 0;\n }\n }\n\n .icon > * {\n transition: transform 250ms var(--ease-smooth-settle);\n }\n\n .icon[data-selected=\"true\"] > * {\n transform: rotate(180deg);\n }\n\n .expand:has(.content[data-from=\"above\"]) {\n flex-direction: column-reverse;\n }\n\n .expand:has(.content[data-from=\"above\"]) .icon > * {\n transform: rotate(180deg);\n }\n\n .expand:has(.content[data-from=\"above\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(0deg);\n }\n\n .expand:has(.content[data-from=\"left\"]) {\n @apply flex-row-reverse items-start;\n }\n\n .expand:has(.content[data-from=\"left\"]) .trigger {\n @apply w-auto flex-col;\n }\n\n .expand:has(.content[data-from=\"left\"]) .icon > * {\n transform: rotate(-90deg);\n }\n\n .expand:has(.content[data-from=\"left\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(90deg);\n }\n\n .expand:has(.content[data-from=\"right\"]) {\n @apply flex-row items-start;\n }\n\n .expand:has(.content[data-from=\"right\"]) .trigger {\n @apply w-auto flex-col;\n }\n\n .expand:has(.content[data-from=\"right\"]) .icon > * {\n transform: rotate(90deg);\n }\n\n .expand:has(.content[data-from=\"right\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(-90deg);\n }\n\n .content {\n display: grid;\n overflow: hidden;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms var(--ease-smooth-settle);\n }\n\n .content[data-selected=\"true\"] {\n grid-template-rows: 1fr;\n }\n\n .content[data-from=\"left\"],\n .content[data-from=\"right\"] {\n grid-template-rows: 1fr;\n grid-template-columns: 0fr;\n transition: grid-template-columns 300ms var(--ease-smooth-settle);\n }\n\n .content[data-from=\"left\"][data-selected=\"true\"],\n .content[data-from=\"right\"][data-selected=\"true\"] {\n grid-template-columns: 1fr;\n }\n\n .content-inner {\n @apply min-h-0 overflow-hidden;\n\n min-width: 0;\n color: var(--foreground-content);\n background-color: var(--background-content);\n }\n\n .divider {\n margin-top: var(--expand-divider-spacing);\n }\n}\n",
|
|
4377
|
+
"flex": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full;\n }\n\n /* Direction variants */\n .flex.row { flex-direction: row; }\n .flex.column { flex-direction: column; }\n\n /* Wrap variants */\n .flex.wrap { flex-wrap: wrap; }\n .flex.nowrap { flex-wrap: nowrap; }\n\n /* Gap variants */\n .flex.gap-xs { gap: var(--spacing-xs); }\n .flex.gap-sm { gap: var(--spacing-sm); }\n .flex.gap-md { gap: var(--spacing-md); }\n .flex.gap-lg { gap: var(--spacing-lg); }\n .flex.gap-xl { gap: var(--spacing-xl); }\n\n /* Justify-content variants */\n .flex.justify-flex-start { justify-content: flex-start; }\n .flex.justify-flex-end { justify-content: flex-end; }\n .flex.justify-center { justify-content: center; }\n .flex.justify-space-between { justify-content: space-between; }\n .flex.justify-space-around { justify-content: space-around; }\n .flex.justify-space-evenly { justify-content: space-evenly; }\n\n /* Align-items variants */\n .flex.align-flex-start { align-items: flex-start; }\n .flex.align-flex-end { align-items: flex-end; }\n .flex.align-center { align-items: center; }\n .flex.align-stretch { align-items: stretch; }\n .flex.align-baseline { align-items: baseline; }\n\n /* Container query parent - establishes containment context */\n .container-query-parent {\n container-type: inline-size;\n container-name: flex-parent;\n @apply w-full;\n }\n\n /* Container query responsive behavior - use .flex.container-responsive for specificity parity with base variants */\n @container flex-parent (width < 400px) {\n .flex.container-responsive {\n flex-direction: column;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (400px <= width < 500px) {\n .flex.container-responsive {\n flex-wrap: wrap;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (500px <= width < 900px) {\n .flex.container-responsive {\n gap: var(--spacing-md);\n }\n }\n\n @container flex-parent (width >= 900px) {\n .flex.container-responsive {\n gap: var(--spacing-lg);\n }\n }\n}\n",
|
|
4253
4378
|
"frame": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n --frame-radius: var(--radius-sm, 24px);\n --frame-stroke-width: var(--border-width-base, 1px);\n }\n\n .shape {\n rx: var(--frame-radius);\n }\n\n .stroke {\n stroke-width: var(--frame-stroke-width);\n vector-effect: non-scaling-stroke;\n }\n\n}\n",
|
|
4254
|
-
"gallery": "@reference \"tailwindcss\";\n\n@layer components {\n .item {\n @apply
|
|
4379
|
+
"gallery": "@reference \"tailwindcss\";\n\n@layer components {\n .item-scope {\n @apply block min-w-0;\n }\n\n .item {\n --border-width: var(--border-width-base, 1px);\n --radius: var(--radius-sm, 0.375rem);\n --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n --view-width: 200px;\n\n @apply flex flex-col overflow-hidden no-underline cursor-pointer w-full;\n\n color: inherit;\n background: var(--background);\n border: var(--border-width) solid var(--background-border);\n border-radius: var(--radius);\n transition:\n border-color var(--transition),\n transform var(--transition),\n background-color var(--transition),\n box-shadow var(--transition);\n }\n\n .item:focus {\n outline: none;\n }\n\n .item[data-disabled=\"true\"] {\n @apply cursor-not-allowed;\n }\n\n .item[data-hovered=\"true\"] {\n border-color: var(--background-hover-border);\n }\n\n .item[data-pressed=\"true\"] {\n border-color: var(--background-pressed-border, var(--background-hover-border));\n }\n\n .item[data-orientation=\"horizontal\"] {\n @apply flex-row;\n }\n\n .item[data-orientation=\"horizontal\"] .view {\n width: var(--view-width);\n }\n\n .view {\n --aspect-ratio: 16/9;\n --background: transparent;\n\n @apply relative overflow-hidden;\n\n aspect-ratio: var(--aspect-ratio);\n background: var(--background);\n }\n\n .view > img,\n .view > video {\n @apply w-full h-full object-cover;\n }\n\n .body {\n --gap: calc(var(--spacing, 0.25rem) * 1);\n --padding: calc(var(--spacing, 0.25rem) * 3);\n --title-color: var(--foreground);\n --description-color: var(--foreground-muted, var(--foreground));\n\n @apply flex flex-col self-start min-w-0;\n\n gap: var(--gap);\n padding: var(--padding);\n }\n\n .item[data-orientation=\"horizontal\"] .body {\n flex: 1;\n align-self: stretch;\n }\n\n .body > :first-child {\n color: var(--title-color);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .body > :not(:first-child) {\n color: var(--description-color);\n font-size: var(--text-sm, 0.875rem);\n }\n}\n",
|
|
4255
4380
|
"grid": "@reference \"tailwindcss\";\n\n@layer components {\n .grid {\n --background: transparent;\n --foreground: inherit;\n\n @apply grid w-full;\n background-color: var(--background);\n color: var(--foreground);\n grid-template-columns: var(--grid-tpl, repeat(3, 1fr));\n grid-template-rows: var(--grid-rows, auto);\n gap: var(--grid-gap, calc(var(--spacing, 0.25rem) * 4));\n justify-items: var(--grid-ji, stretch);\n align-items: var(--grid-ai, stretch);\n justify-content: var(--grid-jc, start);\n align-content: var(--grid-ac, start);\n grid-auto-flow: var(--grid-flow, row);\n }\n\n .container {\n container-type: inline-size;\n container-name: grid-ctx;\n }\n\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-sm, 1fr);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-md, var(--grid-tpl-sm, 1fr));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-cols {\n grid-template-columns: var(--grid-tpl-xl, var(--grid-tpl-lg, var(--grid-tpl-md, var(--grid-tpl-sm, 1fr))));\n }\n }\n\n .grid.responsive-gap {\n gap: var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 2));\n }\n\n @media (min-width: 640px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4))));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-gap {\n gap: var(--grid-gap-xl, var(--grid-gap-lg, var(--grid-gap-md, var(--grid-gap-sm, calc(var(--spacing, 0.25rem) * 4)))));\n }\n }\n\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-sm, auto);\n }\n\n @media (min-width: 640px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-md, var(--grid-rows-sm, auto));\n }\n }\n\n @media (min-width: 1024px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto)));\n }\n }\n\n @media (min-width: 1280px) {\n .grid.responsive-rows {\n grid-template-rows: var(--grid-rows-xl, var(--grid-rows-lg, var(--grid-rows-md, var(--grid-rows-sm, auto))));\n }\n }\n\n .grid.has-row-gap { row-gap: var(--grid-row-gap); }\n .grid.has-col-gap { column-gap: var(--grid-col-gap); }\n\n @container grid-ctx (width < 400px) {\n .container .grid {\n grid-template-columns: 1fr;\n gap: calc(var(--spacing, 0.25rem) * 2);\n }\n }\n}\n",
|
|
4256
|
-
"group": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --layout-radius-size: calc(var(--spacing) * 1.5);\n --layout-padding-size: var(--layout-radius-size);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-border-width: var(--border-width-base, 1px);\n --background-inner-radius: calc(var(--background-radius) - var(--background-border-width));\n --layout-text-height: calc(0.8em * var(--leading-tight, 1.25));\n --layout-vertical-spacing: calc(var(--spacing) * 4);\n --layout-border-height: calc(var(--background-border-width) * 2);\n --layout-padding-height: calc(var(--layout-padding-size) * 2);\n --layout-control-height: calc(\n var(--layout-text-height) +\n var(--layout-vertical-spacing) +\n var(--layout-border-height)\n );\n --item-height: max(\n calc(\n var(--layout-control-height) -\n var(--layout-padding-height) -\n var(--layout-border-height)\n ),\n 0px\n );\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground, currentColor);\n background-color: var(--background, transparent);\n border: var(--background-border-width) solid var(--background-border, transparent);\n border-radius: var(--background-radius);\n padding: var(--layout-padding-size);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--layout-control-height);\n\n .item.divider {\n margin-block: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n height: 100%;\n }\n }\n\n &.vertical {\n @apply flex-col;\n\n .item .button {\n @apply w-full;\n }\n\n .item.divider {\n margin-inline: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n width: 100%;\n }\n }\n\n &.none {\n --layout-padding-size: 0px;\n @apply gap-0;\n }\n\n &.xs {\n --layout-radius-size: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n\n &.sm {\n --layout-radius-size: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n
|
|
4257
|
-
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n pointer-events: none;\n }\n\n .icon-left {\n @apply relative;\n }\n\n .icon-right {\n @apply relative;\n }\n\n .container {\n --adornment-offset: calc(var(--spacing, 0.25rem) * 1.5);\n\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-
|
|
4381
|
+
"group": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --layout-radius-size: calc(var(--spacing) * 1.5);\n --layout-padding-size: var(--layout-radius-size);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-border-width: var(--border-width-base, 1px);\n --background-inner-radius: calc(var(--background-radius) - var(--background-border-width));\n --layout-text-height: calc(0.8em * var(--leading-tight, 1.25));\n --layout-vertical-spacing: calc(var(--spacing) * 4);\n --layout-border-height: calc(var(--background-border-width) * 2);\n --layout-padding-height: calc(var(--layout-padding-size) * 2);\n --layout-control-height: calc(\n var(--layout-text-height) +\n var(--layout-vertical-spacing) +\n var(--layout-border-height)\n );\n --item-height: max(\n calc(\n var(--layout-control-height) -\n var(--layout-padding-height) -\n var(--layout-border-height)\n ),\n 0px\n );\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground, currentColor);\n background-color: var(--background, transparent);\n border: var(--background-border-width) solid var(--background-border, transparent);\n border-radius: var(--background-radius);\n padding: var(--layout-padding-size);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--layout-control-height);\n\n .item.divider {\n margin-block: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n height: 100%;\n }\n }\n\n &.vertical {\n @apply flex-col;\n\n .item .button {\n @apply w-full;\n }\n\n .item.divider {\n margin-inline: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n width: 100%;\n }\n }\n\n &.none {\n --layout-padding-size: 0px;\n @apply gap-0;\n }\n\n &.xs {\n --layout-radius-size: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n\n &.sm {\n --layout-radius-size: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n }\n\n .item {\n @apply flex items-stretch;\n position: relative;\n isolation: isolate;\n border-radius: var(--group-item-radius, 0);\n overflow: visible;\n\n &.grow {\n flex: 1;\n }\n\n &.divider {\n @apply p-0 shrink-0 flex-none;\n\n > [role=\"separator\"] {\n flex: 0 0 auto;\n }\n }\n }\n\n :is(.button, .input, .select, .expand) {\n height: 100%;\n min-height: var(--item-height);\n position: relative;\n isolation: isolate;\n overflow: visible;\n }\n\n .button {\n @apply flex box-border;\n width: auto;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n\n &[data-selected=\"true\"] {\n @apply relative;\n background-color: var(--button-selected-background, var(--background-800));\n color: var(--button-selected-foreground, var(--foreground-100));\n }\n }\n\n .input {\n @apply flex flex-1 items-stretch overflow-visible;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n\n > [data-ring=\"true\"] {\n border-radius: inherit;\n }\n\n input {\n @apply h-full px-2;\n }\n }\n\n .select {\n @apply flex items-stretch p-0 bg-transparent border-none;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n }\n\n .expand {\n @apply flex items-stretch;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n }\n\n .expand :global(.expand-scope),\n .expand :global(.expand) {\n @apply flex w-full h-full;\n }\n\n .expand :global(.expand) {\n @apply flex-col;\n border-radius: inherit;\n }\n\n .expand :global(.trigger) {\n @apply min-h-0;\n border-radius: inherit;\n }\n\n .trigger {}\n\n .group {\n .item :is(.button, .select) {\n border: none;\n }\n\n .button[data-selected=\"true\"] {\n font-weight: 500;\n }\n\n .input {\n --border-width-base: 0px;\n --background-border: transparent;\n --background-focused-border: transparent;\n }\n\n &.none {\n .item:not(.divider) {\n overflow: hidden;\n }\n\n :is(.button, .trigger, .select) {\n border-radius: 0;\n --background-radius: 0;\n --background-inner-radius: 0;\n }\n\n .input {\n --radius-sm: 0;\n }\n\n .item:first-child {\n --group-item-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n }\n\n .item:last-child {\n --group-item-radius: 0 var(--background-inner-radius) var(--background-inner-radius) 0;\n }\n\n &.horizontal {\n .item:first-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-left-radius: var(--background-inner-radius);\n border-bottom-left-radius: var(--background-inner-radius);\n }\n\n .item:last-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-right-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n\n .item:last-child .trigger .icon-section {\n border-top-right-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n }\n\n &.vertical {\n .item:first-child {\n --group-item-radius: var(--background-inner-radius) var(--background-inner-radius) 0 0;\n }\n\n .item:last-child {\n --group-item-radius: 0 0 var(--background-inner-radius) var(--background-inner-radius);\n }\n\n .item:first-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-left-radius: var(--background-inner-radius);\n border-top-right-radius: var(--background-inner-radius);\n }\n\n .item:last-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-bottom-left-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n }\n }\n\n &:is(.xs, .sm) {\n .item {\n --group-item-radius: var(--background-inner-radius);\n }\n\n :is(.button, .trigger, .select) {\n border-radius: var(--background-inner-radius);\n }\n\n .input {\n --radius-sm: var(--background-inner-radius);\n }\n }\n }\n\n .group [data-ring=\"true\"] {\n --ring-shadow: none;\n --ring-border: transparent;\n --ring-border-visible: transparent;\n }\n\n .group :global(.focus-indicator) {\n display: none;\n }\n\n .group [data-focus-indicator=\"local\"] {\n display: none;\n }\n\n :is(.button[data-focus-visible=\"true\"], .trigger[data-focus-visible=\"true\"]) {\n @apply outline-none;\n box-shadow: none;\n }\n}\n",
|
|
4382
|
+
"input": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n @apply flex w-full;\n position: relative;\n overflow: visible;\n }\n\n .input {\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n pointer-events: none;\n }\n\n .icon-left {\n @apply relative;\n }\n\n .icon-right {\n @apply relative;\n }\n\n .container {\n --adornment-offset: calc(var(--spacing, 0.25rem) * 1.5);\n\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n &[data-variant=\"ghost\"] {\n --ring-shadow: none;\n --ring-border: transparent;\n --ring-border-visible: transparent;\n\n background-color: transparent;\n border-color: transparent;\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n align-self: stretch;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2.5;\n }\n\n .end-adornments {\n padding-right: var(--adornment-offset);\n\n &:has(.controls) {\n padding-right: 0;\n }\n\n &:has([data-hint]) {\n padding-right: 0;\n }\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-foreground);\n }\n\n .action:hover {\n background-color: var(--action-background-hover);\n color: var(--action-foreground-hover);\n }\n\n .hint {\n @apply inline-flex items-center justify-center whitespace-nowrap;\n flex-shrink: 0;\n margin-inline-start: calc(var(--spacing, 0.25rem) * 0.5);\n margin-inline-end: var(--adornment-offset);\n font-size: var(--text-sm);\n line-height: 1;\n color: var(--foreground);\n background-color: var(--background);\n pointer-events: auto;\n }\n\n .controls {\n @apply flex w-7.5 flex-col;\n align-self: stretch;\n border-left: 1px solid var(--background-border);\n pointer-events: auto;\n }\n\n .controls[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--controls-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &+.spin-button {\n border-top: 1px solid var(--background-border);\n }\n\n &:hover:not(:disabled) {\n background-color: var(--controls-hover-background);\n color: var(--controls-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--controls-active-background);\n color: var(--controls-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n }\n}\n",
|
|
4258
4383
|
"label": "@reference \"tailwindcss\";\n\n@layer components {\n .label {\n --background: transparent;\n --foreground: var(--foreground-primary);\n --foreground-disabled: var(--foreground-secondary);\n --foreground-error: var(--danger-600);\n\n display: block;\n font-family: inherit;\n font-size: var(--text-sm);\n color: var(--foreground);\n transition: color 150ms ease;\n\n &[data-size=\"sm\"] { font-size: var(--text-sm); }\n &[data-size=\"lg\"] { font-size: var(--text-md); }\n\n &[data-disabled] {\n color: var(--foreground-disabled);\n opacity: 0.6;\n cursor: not-allowed;\n }\n\n &[data-error] {\n color: var(--foreground-error);\n }\n }\n\n .required-indicator {\n margin-left: 0.25rem;\n color: var(--required-color);\n }\n\n .helper-text {\n --helper-foreground: var(--foreground-secondary);\n --helper-foreground-error: var(--danger-600);\n\n display: block;\n font-size: var(--text-sm);\n margin-top: 0.25rem;\n transition: color 150ms ease;\n color: var(--helper-foreground);\n\n &[data-error] {\n color: var(--helper-foreground-error);\n }\n }\n}\n",
|
|
4259
4384
|
"list": "@reference \"tailwindcss\";\n\n@layer components {\n .container {\n @apply mx-auto;\n max-width: 28rem;\n font-family: var(--font-sans, system-ui, -apple-system, sans-serif);\n color: var(--foreground);\n }\n\n .header {\n @apply flex items-center justify-between;\n padding-left: 1.25rem;\n padding-right: 1.25rem;\n padding-top: 1rem;\n padding-bottom: 1rem;\n backdrop-filter: blur(12px);\n z-index: 10;\n }\n\n .sticky {\n position: sticky;\n top: 0;\n }\n\n .container[data-spacing=\"sm\"] .header {\n padding-left: 0.75rem;\n padding-right: 0.75rem;\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n }\n\n .header > :first-child {\n font-weight: var(--font-weight-semibold);\n font-size: 1.125rem;\n color: var(--header-title-foreground);\n }\n\n .header > :last-child {\n color: var(--header-subtitle-foreground);\n }\n\n .item {\n @apply flex flex-row items-center gap-3 px-2 py-1 cursor-pointer;\n background-color: var(--item-background, transparent);\n }\n\n .item[data-focus-visible=\"true\"] {\n box-shadow:\n inset 0 0 0 1px var(--item-focus-visible-background, var(--focus-visible-background)),\n 0 0 0 2px var(--item-focus-visible, var(--focus-visible));\n border-radius: var(--item-radius, var(--radius-sm, 0.375rem));\n outline: none;\n }\n\n .item:hover {\n background-color: var(--item-background-hover, var(--background-hover, var(--highlight-background, transparent)));\n }\n\n .container[data-keyboard-mode=\"true\"] .item[data-highlighted=\"true\"] {\n background-color: var(--item-background-highlighted, var(--background-highlighted, var(--highlight-background, transparent)));\n }\n\n .container[data-spacing=\"sm\"] .item {\n padding: 0.5rem 0.75rem;\n gap: 0.375rem;\n }\n\n .checkbox,\n .control,\n .media {\n @apply flex items-center justify-center flex-shrink-0;\n }\n\n .control {\n margin-left: auto;\n }\n\n .media {\n @apply h-8 w-8;\n }\n\n .title {\n font-size: var(--text-sm);\n font-weight: var(--font-weight-medium);\n color: var(--foreground);\n @apply truncate;\n }\n\n .desc {\n font-size: var(--text-sm);\n color: var(--desc-foreground);\n @apply truncate;\n }\n\n .actionGroup {\n @apply flex items-center;\n padding-left: 0.25rem;\n padding-right: 0.25rem;\n }\n\n .actionGroup[data-justify=\"space-between\"] { justify-content: space-between; }\n .actionGroup[data-justify=\"flex-start\"] { justify-content: flex-start; }\n .actionGroup[data-justify=\"flex-end\"] { justify-content: flex-end; }\n\n .actions {\n align-items: center;\n gap: 0.25rem;\n margin-left: auto;\n flex-shrink: 0;\n @apply p-1.5 hidden group-hover:flex group-focus-within:flex;\n }\n\n .action {\n @apply flex items-center justify-center;\n border-radius: 0.25rem;\n color: var(--action-foreground);\n @apply p-2;\n }\n\n .action:hover {\n background-color: var(--action-background-hover, var(--item-background-hover, var(--background-hover, transparent)));\n color: var(--action-foreground-hover, var(--action-color, inherit));\n }\n\n .footer {\n @apply flex p-6 pb-12;\n }\n\n .footer[data-align=\"center\"] { justify-content: center; }\n .footer[data-align=\"flex-start\"] { justify-content: flex-start; }\n .footer[data-align=\"flex-end\"] { justify-content: flex-end; }\n\n .container[data-spacing=\"sm\"] .footer {\n padding: 0.375rem 0.75rem;\n padding-bottom: 0.375rem;\n }\n}\n",
|
|
4260
4385
|
"mask": "@reference \"tailwindcss\";\n\n@layer components {\n .mask {\n @apply relative h-full w-full;\n }\n}\n\n.mask[style*=\"mask-image\"],\n.mask[style*=\"-webkit-mask-image\"] {\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n}\n\n.mask[style*=\"--mask-clip-path\"] {\n clip-path: var(--mask-clip-path);\n}\n\n.mask-gradient {\n background: var(--mask-gradient);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n color: transparent;\n}\n",
|
|
@@ -4262,24 +4387,24 @@ export const generatedStyles = {
|
|
|
4262
4387
|
"modal": "@reference \"tailwindcss\";\n\n@layer components {\n .overlay {\n --disabled-opacity: 0.5;\n }\n\n .backdrop {\n @apply absolute inset-0 cursor-pointer;\n background-color: var(--backdrop-background);\n backdrop-filter: blur(4px);\n }\n\n .modal {\n @apply relative flex w-full flex-col overflow-hidden;\n z-index: 1;\n max-height: 90vh;\n margin: 1rem;\n color: var(--foreground);\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n pointer-events: auto;\n overflow: hidden;\n\n &[data-focus-visible=\"true\"] {\n outline: none;\n box-shadow: 0 0 0 1.5px var(--focus-visible);\n }\n }\n\n .header {\n @apply flex shrink-0 items-center justify-between gap-2 px-6 py-4;\n border-bottom: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .title {\n @apply m-0;\n font-size: 1.125rem;\n font-weight: var(--font-weight-semibold);\n color: var(--title-foreground, var(--foreground));\n }\n\n .spacer {\n flex: 1;\n }\n\n .close {\n @apply ml-auto flex items-center justify-center cursor-pointer;\n background: none;\n border: none;\n color: var(--close-foreground, var(--foreground));\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &[data-hovered=\"true\"] {\n color: var(--close-hover-foreground, var(--close-foreground, var(--foreground)));\n }\n\n &[data-pressed=\"true\"] {\n transform: scale(0.92);\n }\n\n &[data-focus-visible=\"true\"] {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n border-radius: var(--radius-xs, 0.25rem);\n }\n }\n\n .close-icon {\n @apply h-5 w-5;\n }\n\n .content {\n flex: 1;\n min-height: 0;\n overflow-y: auto;\n color: var(--foreground);\n }\n\n .content::-webkit-scrollbar {\n width: 6px;\n }\n\n .content::-webkit-scrollbar-track {\n background: transparent;\n }\n\n .content::-webkit-scrollbar-thumb {\n background: var(--scrollbar-thumb-background, var(--background-border));\n border-radius: 3px;\n transition: background 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n }\n\n .content::-webkit-scrollbar-thumb:hover {\n background: var(--scrollbar-thumb-hover-background, var(--close-foreground, var(--foreground)));\n }\n\n .footer {\n @apply flex shrink-0 items-center justify-between gap-4 px-6 py-4;\n background-color: var(--footer-background, var(--background));\n border-top: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n /* Size variants */\n .modal[data-size=\"fit\"] {\n width: fit-content;\n }\n\n .modal[data-size=\"auto\"] {\n max-width: min(90vw, 28rem);\n }\n\n /* Media queries for smaller screens */\n @media (max-width: 640px) {\n .modal {\n margin: 1rem;\n }\n\n .content {\n max-height: calc(100vh - 10rem);\n }\n }\n}\n",
|
|
4263
4388
|
"page": "@reference \"tailwindcss\";\n\n@layer components {\n .page {\n --padding: var(--page-padding-md, 1rem);\n\n @apply flex flex-col w-full relative;\n }\n\n .page[data-centered=\"true\"] {\n @apply items-center;\n }\n\n .page[data-fullscreen=\"false\"] {\n margin-left: auto;\n margin-right: auto;\n }\n\n .padding-none { --padding: 0; padding: 0; }\n\n .padding-sm { --padding: var(--page-padding-sm, 0.5rem); padding: var(--padding); }\n\n .padding-md { --padding: var(--page-padding-md, 1rem); padding: var(--padding); }\n\n .padding-lg { --padding: var(--page-padding-lg, 1.5rem); padding: var(--padding); }\n\n .padding-xl { --padding: var(--page-padding-xl, 2rem); padding: var(--padding); }\n}\n",
|
|
4264
4389
|
"panel": "@reference \"tailwindcss\";\n\n@layer components {\n .panel {\n @apply flex h-full w-full min-h-0 min-w-0 flex-row;\n background: inherit;\n }\n\n .panel[data-stacked=\"true\"] { flex-direction: column; }\n\n .header,\n .footer {\n @apply shrink-0;\n background: inherit;\n }\n\n .sticky {\n position: sticky;\n top: 0;\n z-index: 10;\n }\n\n .content {\n @apply flex min-h-0 min-w-0;\n flex: 1;\n overflow: auto;\n }\n\n .fixed {\n position: fixed;\n bottom: 0;\n left: 0;\n right: 0;\n z-index: 5;\n }\n\n /* Sidebar */\n .sidebar {\n @apply shrink-0 overflow-hidden;\n overflow: hidden;\n transition: width 0.2s ease;\n border-right: var(--border-width-base) solid var(--panel-border-color);\n }\n\n .sidebar[data-side=\"right\"] {\n border-right: none;\n border-left: var(--border-width-base) solid var(--panel-border-color);\n }\n\n /* Toggle */\n .toggle {\n @apply flex items-center;\n }\n\n /* Group */\n .group {\n @apply flex w-full h-full;\n background: inherit;\n }\n\n .group[data-direction=\"vertical\"] { flex-direction: column; }\n\n /* Resize handle */\n .resize {\n @apply relative shrink-0;\n cursor: col-resize;\n background: transparent;\n width: 10px;\n }\n\n .resize::before {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 50%;\n width: 1px;\n background: var(--panel-divider-color, #374151);\n transform: translateX(-50%);\n transition: width 0.15s ease;\n }\n\n .resize[data-direction=\"vertical\"] {\n cursor: row-resize;\n height: 10px;\n }\n\n .resize[data-direction=\"vertical\"]::before {\n top: 50%;\n bottom: auto;\n left: 0;\n right: 0;\n width: auto;\n height: 1px;\n transform: translateY(-50%);\n }\n\n .resize:hover::before,\n .resize[data-resizing=\"true\"]::before { width: 2px; }\n\n .resize[data-direction=\"vertical\"]:hover::before,\n .resize[data-direction=\"vertical\"][data-resizing=\"true\"]::before {\n width: auto;\n height: 2px;\n }\n\n /* Spacing variants */\n .spacingNone,\n .spacing-none { gap: 0; }\n\n .spacingSm,\n .spacing-sm { gap: var(--spacing-sm, 0.5rem); }\n\n .spacingMd,\n .spacing-md { gap: var(--spacing-md, 1rem); }\n\n .spacingLg,\n .spacing-lg { gap: var(--spacing-lg, 1.5rem); }\n\n /* Compact variant */\n .compact {\n gap: calc(var(--spacing-sm, 0.5rem) / 2);\n }\n\n /* Responsive stacking (mobile) */\n @media (max-width: 767px) {\n .stacked { flex-direction: column; }\n }\n}\n",
|
|
4265
|
-
"path": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n
|
|
4390
|
+
"path": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n @apply block;\n }\n\n .list {\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n list-style: none;\n }\n\n .list[data-separator=\"custom\"] .item:not(:last-child)::after {\n content: none;\n }\n\n .item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n .item:not(:last-child)::after {\n content: \"/\";\n margin-inline-start: 0.5rem;\n color: var(--separator-foreground);\n pointer-events: none;\n user-select: none;\n }\n\n .separator {\n @apply m-0 flex items-center p-0;\n list-style: none;\n color: var(--separator-foreground);\n pointer-events: none;\n user-select: none;\n }\n\n .link {\n @apply relative cursor-pointer px-2 py-1;\n border: 0;\n background-color: transparent;\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: var(--leading-normal, 1.5);\n text-decoration: none;\n transition:\n color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n outline: none;\n }\n\n button.link {\n font: inherit;\n }\n\n .link:focus,\n .link:focus-visible {\n outline: none;\n }\n\n .link[data-hovered=\"true\"]:not([data-disabled=\"true\"]):not([data-selected=\"true\"]) {\n background-color: var(--background-hover);\n color: var(--foreground-hover);\n }\n\n .link[data-pressed=\"true\"]:not([data-disabled=\"true\"]):not([data-selected=\"true\"]) {\n background-color: var(--background-pressed);\n }\n\n .link[data-selected=\"true\"] {\n color: var(--foreground-selected);\n cursor: default;\n }\n\n .link[data-selected=\"true\"][data-hovered=\"true\"] {\n background-color: transparent;\n }\n\n .link[data-disabled=\"true\"] {\n color: var(--foreground-disabled);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n .link[data-disabled=\"true\"][data-hovered=\"true\"] {\n background-color: transparent;\n }\n}\n",
|
|
4266
4391
|
"popover": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--background);\n --frame-stroke-color: var(--border);\n --frame-radius: 8px;\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n}\n",
|
|
4267
4392
|
"progress": "@reference \"tailwindcss\";\n\n@layer components {\n .progress {\n @apply relative w-full overflow-hidden;\n border-radius: var(--radius-full, 9999px);\n background-color: var(--background);\n }\n\n .progress.sm { height: 0.25rem; }\n .progress.md { height: 0.5rem; }\n .progress.lg { height: 0.75rem; }\n\n .fill {\n @apply h-full;\n border-radius: var(--radius-full, 9999px);\n background-color: var(--fill-background);\n transition: width 300ms var(--ease-snappy-pop);\n }\n\n .fill[data-animated=\"true\"] {\n animation: pulse 2s var(--ease-gentle-ease) infinite;\n }\n\n .fill[data-indeterminate=\"true\"] {\n width: 33.333%;\n animation: progress-indeterminate 1.5s var(--ease-gentle-ease) infinite;\n }\n\n .wrapper {\n @apply w-full;\n }\n\n .wrapper[data-has-label=\"true\"] {\n @apply space-y-1;\n }\n\n .label-row {\n @apply flex items-center justify-between;\n font-size: var(--text-sm);\n color: var(--foreground);\n }\n\n .label {\n user-select: none;\n }\n\n .value {\n font-variant-numeric: tabular-nums;\n }\n\n @keyframes pulse {\n 0%, 100% {\n opacity: 1;\n }\n 50% {\n opacity: 0.5;\n }\n }\n\n @keyframes progress-indeterminate {\n 0% { transform: translateX(-100%); }\n 100% { transform: translateX(400%); }\n }\n}\n",
|
|
4268
|
-
"radio": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n }\n\n .radio-input {\n @apply absolute inset-0 h-full w-full cursor-pointer opacity-0;\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--background);\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-error=\"true\"][data-selected=\"true\"] {\n border-color: var(--background-selected-border);\n }\n\n &[data-focus-visible=\"true\"] {\n
|
|
4393
|
+
"radio": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n position: relative;\n overflow: visible;\n }\n\n .radio-surface {\n @apply inline-flex shrink-0;\n border-radius: 9999px;\n }\n\n .radio-input {\n @apply absolute inset-0 h-full w-full cursor-pointer opacity-0;\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--background);\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-error=\"true\"][data-selected=\"true\"] {\n border-color: var(--background-selected-border);\n }\n\n &[data-focus-visible=\"true\"] {\n outline: none;\n }\n }\n\n .radio-item:active .radio {\n transform: scale(0.92);\n }\n\n .radio-dot {\n border-radius: 9999px;\n background-color: var(--dot-color);\n transform: scale(0);\n transform-origin: center;\n transition: transform 200ms var(--ease-snappy-pop), background-color 200ms var(--ease-snappy-pop);\n }\n\n .radio[data-selected=\"true\"] .radio-dot {\n background-color: var(--dot-selected-color);\n transform: scale(1);\n }\n\n @media (hover: hover) {\n .radio-item:not([data-disabled=\"true\"]):hover .radio {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n opacity: 0.9;\n }\n }\n\n .radio-item[data-disabled=\"true\"] .radio {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .radio-label {\n @apply cursor-pointer;\n color: var(--foreground);\n font-size: inherit;\n font-weight: var(--font-weight-medium, 500);\n line-height: inherit;\n transition: color 200ms var(--ease-snappy-pop);\n user-select: none;\n\n &[data-disabled=\"true\"] {\n color: var(--foreground-disabled, var(--foreground));\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n }\n\n .radio-description {\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n margin-top: 0.125rem;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-error=\"true\"] {\n color: var(--foreground-error, var(--foreground));\n }\n }\n\n .helper-text {\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n margin-top: 0.5rem;\n margin-left: 2rem;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-error=\"true\"] {\n color: var(--foreground-error, var(--foreground));\n }\n }\n\n .radio.sm {\n @apply h-4 w-4;\n }\n\n .radio.sm .radio-dot {\n width: 0.375rem;\n height: 0.375rem;\n }\n\n .radio.md {\n @apply h-5 w-5;\n }\n\n .radio.md .radio-dot {\n width: 0.625rem;\n height: 0.625rem;\n }\n\n .radio.lg {\n @apply h-6 w-6;\n }\n\n .radio.lg .radio-dot {\n width: 0.75rem;\n height: 0.75rem;\n }\n}\n",
|
|
4269
4394
|
"scroll": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n min-height: 0;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n }\n\n .horizontal {\n --scrollbar-height: 12px;\n }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .vertical[data-inline=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inline=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n background-color: var(--track-background);\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs, 0.25rem) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover {\n background-color: var(--thumb-background-hover);\n }\n\n .root[data-pressed] .thumb {\n background-color: var(--thumb-background-pressed);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-pressed] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-pressed] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
|
|
4270
|
-
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n --trigger-padding-inline: calc(var(--spacing) * 2);\n --trigger-padding-block: calc(var(--spacing) * 1.75);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n font-size: var(--foreground-size);\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n }\n\n &[data-open=\"true\"] {\n background-color: var(--background-hover);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not([data-disabled]):hover .icon-section,\n &:not([data-disabled]):hover .value-section:not(:empty) {\n background-color: var(--background-hover);\n }\n }\n\n &[data-focus-visible=\"true\"] {\n
|
|
4271
|
-
"slider": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n\n @apply relative flex w-full items-center;\n touch-action: none;\n user-select: none;\n }\n\n .
|
|
4272
|
-
"switch": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border-color);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--foreground);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--background-active);\n border-color: var(--border-color-active);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--foreground-active);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-color-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-color-pressed);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n
|
|
4395
|
+
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n @apply flex w-full;\n position: relative;\n overflow: visible;\n }\n\n .select {\n --disabled-opacity: 0.5;\n --trigger-padding-inline: calc(var(--spacing) * 2);\n --trigger-padding-block: calc(var(--spacing) * 1.75);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n font-size: var(--foreground-size);\n\n @apply p-0 gap-0 w-full flex-row items-center;\n position: relative;\n overflow: visible;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n }\n\n &[data-open=\"true\"] {\n background-color: var(--background-hover);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not([data-disabled]):hover .icon-section,\n &:not([data-disabled]):hover .value-section:not(:empty) {\n background-color: var(--background-hover);\n }\n }\n\n &[data-focus-visible=\"true\"] {\n @apply outline-none;\n }\n }\n\n .trigger-compact {\n @apply flex-none w-auto;\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n border-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n font-size: var(--foreground-size);\n\n &:only-child {\n border-radius: var(--background-inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n border-radius: 0 var(--background-inner-radius) var(--background-inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --item-padding-inline: calc(var(--spacing) * 1.5);\n --item-padding-block: var(--spacing);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item,\n .sub-trigger {\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n border-radius: var(--background-inner-radius);\n font-size: var(--foreground-size);\n font-weight: var(--font-weight-medium);\n color: var(--foreground);\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n }\n\n .item {\n --item-padding-inline: var(--trigger-padding-inline);\n --item-padding-block: calc(var(--trigger-padding-block) * 1.15);\n\n padding: var(--item-padding-block) var(--item-padding-inline);\n\n &[data-selected=\"true\"] {\n color: var(--foreground);\n }\n\n &[data-highlighted=\"true\"] {\n background-color: var(--background-highlighted);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--foreground-size);\n font-weight: var(--font-weight-medium);\n color: var(--foreground-muted);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--icon-foreground); }\n .item-indicator { color: var(--indicator-foreground); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--background-border);\n }\n\n .placeholder {\n color: var(--foreground-muted);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--background-inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n &:focus-within {\n @apply outline-none;\n z-index: 1;\n }\n }\n\n .search-trigger :global(.focus-indicator) {\n display: none;\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n }\n\n .input {\n padding: var(--trigger-padding-block) calc(var(--trigger-padding-inline) * 1.5);\n padding-right: calc(var(--trigger-padding-inline) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-focused], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--trigger-padding-inline) * 1.5);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--trigger-padding-inline);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .sub-trigger {\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n\n &[data-highlighted=\"true\"],\n &[data-open=\"true\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--background-highlighted);\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
|
|
4396
|
+
"slider": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n --slider-track-size: 0.375rem;\n --slider-thumb-size: 1rem;\n\n @apply relative flex w-full items-center;\n min-inline-size: 12rem;\n min-height: 2rem;\n touch-action: none;\n user-select: none;\n }\n\n .track {\n @apply relative flex grow items-center;\n flex-grow: 1;\n height: var(--slider-track-size);\n overflow: visible;\n border-radius: var(--radius-xs, 0.25rem);\n background-color: var(--background);\n }\n\n .range {\n @apply absolute;\n border-radius: var(--radius-xs, 0.25rem);\n background-color: var(--background);\n transition: background-color 200ms var(--ease-snappy-pop);\n }\n\n .thumb {\n @apply absolute block;\n top: 50%;\n width: var(--slider-thumb-size);\n height: var(--slider-thumb-size);\n transform: translate(-50%, -50%);\n border-radius: var(--radius-full, 9999px);\n outline: none;\n background-color: var(--background);\n transition:\n background-color 200ms var(--ease-snappy-pop),\n transform 200ms var(--ease-snappy-pop);\n }\n\n .slider[data-orientation=\"horizontal\"] .range {\n top: 0;\n height: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] {\n justify-content: center;\n min-height: 10rem;\n min-inline-size: auto;\n width: fit-content;\n }\n\n .slider[data-orientation=\"vertical\"] .track {\n width: var(--slider-track-size);\n height: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] .range {\n left: 0;\n width: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] .thumb {\n left: 50%;\n top: auto;\n transform: translate(-50%, 50%);\n }\n\n .slider[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .slider[data-disabled=\"true\"] .range {\n background-color: var(--background-disabled, var(--background));\n }\n\n .thumb[data-disabled=\"true\"] {\n cursor: not-allowed;\n background-color: var(--background-disabled, var(--background));\n }\n\n .thumb[data-pressed=\"true\"] {\n transform: translate(-50%, -50%) scale(1.08);\n }\n\n .slider[data-orientation=\"vertical\"] .thumb[data-pressed=\"true\"] {\n transform: translate(-50%, 50%) scale(1.08);\n }\n}\n",
|
|
4397
|
+
"switch": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n border-radius: var(--radius);\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border-color);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--foreground);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--background-active);\n border-color: var(--border-color-active);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--foreground-active);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-color-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-color-pressed);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n\n .switch-sm {\n --width: 1.75rem;\n --height: 1rem;\n --thumb-size: 0.625rem;\n --thumb-offset: 0.1875rem;\n }\n}\n",
|
|
4273
4398
|
"table": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply w-full;\n }\n\n .container {\n @apply overflow-x-auto rounded-md;\n border: 1px solid var(--table-border, var(--background-800));\n }\n\n .table {\n @apply w-full text-sm;\n background-color: var(--table-background, transparent);\n color: var(--table-foreground, currentColor);\n }\n\n .thead {\n @apply contents;\n }\n\n .headerRow {\n @apply contents;\n }\n\n .headerCell {\n @apply px-4 py-3 text-left font-semibold;\n background-color: var(--table-header-background, var(--background-900));\n color: var(--table-header-foreground, var(--foreground-200));\n border-bottom: 1px solid var(--table-border, var(--background-800));\n }\n\n .tbody {\n @apply contents;\n }\n\n .bodyRow {\n @apply contents;\n\n &[data-interactive=\"true\"] {\n @apply cursor-pointer;\n\n & td {\n @apply transition-colors;\n }\n\n &:hover td {\n background-color: var(--table-body-background-hover, var(--background-900));\n }\n }\n }\n\n .interactive {\n @apply cursor-pointer;\n }\n\n .cell {\n @apply px-4 py-3;\n background-color: var(--table-cell-background, transparent);\n color: var(--table-cell-foreground, var(--foreground-300));\n border-bottom: 1px solid var(--table-border, var(--background-800));\n\n &:last-child {\n border-bottom: none;\n }\n }\n\n .emptyState {\n @apply px-4 py-8 text-center;\n color: var(--table-empty-foreground, var(--foreground-400));\n display: table-cell !important;\n }\n\n .filterBar {\n @apply mb-4 rounded-sm border p-4;\n background-color: var(--table-filter-background, var(--background-900));\n border-color: var(--table-filter-border, var(--background-800));\n }\n\n .filterGrid {\n @apply grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3;\n }\n\n .filterLabel {\n @apply mb-2 block text-sm font-medium;\n color: var(--table-filter-label-color, var(--foreground-300));\n }\n\n .filterInput {\n @apply w-full rounded-md border px-3 py-2 transition-all;\n background-color: var(--table-filter-input-background, var(--background-950));\n border-color: var(--table-filter-input-border, var(--background-700));\n color: var(--table-filter-input-foreground, var(--foreground-50));\n\n &::placeholder {\n color: var(--table-filter-input-placeholder, var(--foreground-400));\n }\n\n &:hover {\n border-color: var(--table-filter-input-border-hover, var(--background-600));\n }\n\n &:focus {\n outline: none;\n border-color: var(--table-filter-input-border-focus, var(--accent-500));\n box-shadow: 0 0 0 2px var(--table-filter-input-ring, rgba(99, 102, 241, 0.2));\n }\n }\n}\n",
|
|
4274
|
-
"tabs": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0 0 4px;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--border-color);\n align-items: stretch;\n padding: 0 0 0 4px;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--background);\n box-sizing: border-box;\n border-radius: var(--radius-sm);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-fallback {\n z-index: -1;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2
|
|
4275
|
-
"textarea": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n
|
|
4399
|
+
"tabs": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0 0 4px;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--border-color);\n align-items: stretch;\n padding: 0 0 0 4px;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--background);\n box-sizing: border-box;\n border-radius: var(--radius-sm);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-fallback {\n z-index: -1;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 px-2 py-1.5 cursor-pointer select-none;\n height: 100%;\n background-color: var(--background);\n border: none;\n color: var(--foreground);\n outline: none;\n box-shadow: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]):not([data-selected=\"true\"]) {\n &:hover {\n background-color: var(--background-hover);\n color: var(--foreground-hover);\n }\n\n &:active {\n background-color: var(--background-pressed);\n color: var(--foreground-pressed);\n }\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n color: var(--foreground-selected);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n .list & {\n background-color: var(--background-selected);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--underline-border);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--underline-border);\n }\n }\n\n &:focus,\n &:focus-visible {\n outline: none !important;\n box-shadow: none !important;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: var(--background);\n background-clip: padding-box;\n border-radius: var(--radius-sm);\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"] &:not([data-disabled]):not([data-selected=\"true\"]):hover {\n background-color: var(--background-hover);\n }\n\n .list[data-variant=\"underline\"] &:not([data-disabled]):not([data-selected=\"true\"]):active {\n background-color: var(--background-pressed);\n }\n\n .list[data-variant=\"underline\"] &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n }\n\n .list[data-variant=\"underline\"] &:focus,\n .list[data-variant=\"underline\"] &:focus-visible {\n outline: none !important;\n box-shadow: none !important;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n border-left-color: var(--underline-border);\n border-bottom: none;\n }\n }\n\n .icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0 0 4px;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
4400
|
+
"textarea": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n\n @apply block w-full px-3 py-2;\n box-sizing: border-box;\n resize: none;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n background-color: var(--background);\n color: var(--foreground);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n outline: none;\n transition:\n background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n color 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &:hover:not([data-disabled]),\n &[data-hovered=\"true\"]:not([data-disabled]) {\n background-color: var(--background-hover);\n }\n\n &[data-disabled=\"true\"] {\n background-color: var(--background-disabled);\n color: var(--foreground-disabled);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-resize-axis=\"x\"],\n &[data-resize-axis=\"both\"] {\n padding-inline-end: calc(var(--padding-inline) + 1rem);\n }\n\n &[data-resize-axis=\"y\"],\n &[data-resize-axis=\"both\"] {\n padding-block-end: calc(var(--padding-block) + 1rem);\n }\n\n &[data-scroll=\"true\"] {\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: hidden;\n\n &[data-disabled=\"true\"] {\n background-color: transparent;\n opacity: 1;\n }\n\n &[data-error=\"true\"] {\n border-color: transparent;\n }\n }\n }\n\n .size-sm {\n min-height: 5rem;\n --padding-inline: 0.5rem;\n --padding-block: 0.25rem;\n font-size: var(--text-sm);\n @apply px-2 py-1;\n }\n\n .size-md {\n min-height: 6rem;\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n font-size: var(--text-sm);\n @apply px-3 py-2;\n }\n\n .size-lg {\n min-height: 8rem;\n --padding-inline: 1rem;\n --padding-block: 0.75rem;\n font-size: var(--text-md);\n @apply px-4 py-3;\n }\n\n .container {\n @apply w-full;\n }\n\n .surface {\n @apply relative w-full;\n }\n\n .scroll-wrapper {\n @apply w-full overflow-hidden;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n background-color: var(--background);\n\n &:hover:not([data-disabled=\"true\"]),\n &[data-hovered=\"true\"]:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n }\n\n &[data-disabled=\"true\"] {\n background-color: var(--background-disabled);\n opacity: var(--disabled-opacity);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n }\n\n .resize-handle {\n position: absolute;\n z-index: 1;\n touch-action: none;\n user-select: none;\n\n &::before,\n &::after {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--handle-background);\n transition: background-color 150ms;\n }\n\n &:hover::before,\n &:hover::after {\n background-color: var(--handle-hover-background);\n }\n }\n\n .resize-handle-both {\n right: 3px;\n bottom: 3px;\n width: 1.5rem;\n height: 1.5rem;\n cursor: nwse-resize;\n\n &::before {\n right: 0.15rem;\n bottom: 0.35rem;\n width: 0.5rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n\n &::after {\n right: 0.2rem;\n bottom: 0.6rem;\n width: 1rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n }\n\n .resize-handle-x {\n top: 50%;\n right: 0;\n width: 0.75rem;\n height: 2rem;\n cursor: ew-resize;\n transform: translateY(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 0.125rem;\n height: 1.5rem;\n transform: translate(-50%, -50%);\n }\n\n &::after {\n display: none;\n }\n }\n\n .resize-handle-y {\n left: 50%;\n bottom: 0;\n width: 2rem;\n height: 0.75rem;\n cursor: ns-resize;\n transform: translateX(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 1.5rem;\n height: 0.125rem;\n transform: translate(-50%, -50%);\n }\n\n &::after {\n display: none;\n }\n }\n\n .character-count {\n @apply mt-1;\n color: var(--count-foreground);\n font-size: var(--text-sm);\n transition: color 0.15s var(--ease-snappy-pop);\n }\n\n .character-count[data-over-limit=\"true\"] {\n color: var(--count-over-limit-foreground);\n }\n}\n",
|
|
4276
4401
|
"toast": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply flex w-full max-w-[28rem] items-start gap-3 px-4 py-2.5 select-none;\n background: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n box-shadow: var(--background-shadow);\n font-family: inherit;\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-normal, 1.5);\n touch-action: pan-y;\n }\n\n .icon-wrap {\n @apply mr-4 mt-2 h-5 w-5 shrink-0;\n }\n\n .icon {\n @apply h-5 w-5;\n color: var(--foreground);\n }\n\n .content {\n @apply min-w-0 flex-1;\n }\n\n .title {\n @apply m-0;\n --foreground: inherit;\n font-weight: var(--font-weight-semibold);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n color: var(--foreground);\n }\n\n .description {\n @apply mt-1 mb-0;\n --foreground: inherit;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-normal, 1.5);\n color: var(--foreground);\n }\n\n .close {\n @apply flex shrink-0 items-center justify-center p-2 cursor-pointer;\n --foreground: currentColor;\n --background-hover: transparent;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm, 0.375rem);\n color: var(--foreground);\n opacity: 0.6;\n transition: opacity 0.15s var(--ease-settle-in);\n\n &[data-focus-visible=\"true\"] {\n box-shadow: 0 0 0 var(--border-width-base, 1px) var(--focus-visible);\n outline: none;\n }\n\n &[data-hovered=\"true\"] {\n opacity: 1;\n background: var(--background-hover);\n }\n }\n\n .close-icon {\n @apply h-4 w-4;\n }\n}\n",
|
|
4277
4402
|
"tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n display: contents;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--background);\n --frame-stroke-color: var(--background-border);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-open=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .content[data-instant=\"true\"] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1 whitespace-nowrap;\n color: var(--foreground);\n }\n\n .frame[data-hint=\"true\"] {\n @apply pr-1;\n }\n\n .hint {\n @apply flex-shrink-0;\n }\n}\n"
|
|
4278
4403
|
};
|
|
4279
4404
|
export const generatedSourceCode = {
|
|
4280
4405
|
"anchor": {
|
|
4281
|
-
"tsx": "import * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface AnchorStyleSlots {\n root?: StyleValue;\n underline?: StyleValue;\n preview?: StyleValue;\n}\n\ntype AnchorStylesProp = StylesProp<AnchorStyleSlots>;\n\nconst resolveAnchorBaseStyles = createStylesResolver(['root', 'underline', 'preview'] as const);\nconst ANCHOR_PREVIEW_DISPLAY_NAME = \"Anchor.Preview\";\nconst ANCHOR_UNDERLINE_DISPLAY_NAME = \"Anchor.Underline\";\n\nconst DASHED_DIMENSIONS = {\n sm: { thickness: 1, dashLength: 8, gapLength: 4 },\n md: { thickness: 2, dashLength: 8, gapLength: 4 },\n lg: { thickness: 4, dashLength: 10, gapLength: 6 },\n} as const;\n\nconst DOTTED_DIMENSIONS = {\n sm: { thickness: 1, radius: 0.5, spacing: 6 },\n md: { thickness: 2, radius: 1, spacing: 8 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getPath(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size];\n const totalLength = dashLength + gapLength;\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${totalLength}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${dashLength}' height='${thickness}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${totalLength}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${thickness}' height='${dashLength}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\nfunction getDottedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, radius, spacing } = DOTTED_DIMENSIONS[size];\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${spacing}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${spacing}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\ntype CompoundComponentType = {\n displayName?: string;\n name?: string;\n render?: {\n displayName?: string;\n name?: string;\n };\n};\n\nfunction matchesCompoundComponent(\n childType: React.JSXElementConstructor<any> | string | undefined,\n component: React.JSXElementConstructor<any>,\n displayName: string,\n): boolean {\n if (!childType) {\n return false;\n }\n\n if (childType === component) {\n return true;\n }\n\n if (typeof childType === \"string\") {\n return false;\n }\n\n const componentType = childType as CompoundComponentType;\n\n return (\n componentType.displayName === displayName ||\n componentType.name === displayName ||\n componentType.render?.displayName === displayName ||\n componentType.render?.name === displayName\n );\n}\n\n// --- Sub-components ---\n\nexport interface AnchorPreviewProps\n extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nexport function AnchorPreview({ children }: AnchorPreviewProps) {\n return null;\n}\nAnchorPreview.displayName = ANCHOR_PREVIEW_DISPLAY_NAME;\n\ninterface AnchorUnderlineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controls the line style of the underline */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n}\n\nconst AnchorUnderline = React.forwardRef<HTMLDivElement, AnchorUnderlineProps>(\n ({ className, variant = \"solid\", style, ...props }, ref) => {\n const getMaskStyles = (): React.CSSProperties => {\n if (variant === \"solid\") return {}\n\n const orientation = \"horizontal\";\n const size = \"sm\";\n\n const svgDataUri = variant === \"dashed\" ? getPath(orientation, size) : getDottedMaskSvg(orientation, size);\n const maskRepeat = \"repeat-x\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n return (\n <span\n ref={ref}\n className={cn(css.underline, className)}\n style={{ ...getMaskStyles(), ...style }}\n {...props}\n />\n );\n }\n);\nAnchorUnderline.displayName = ANCHOR_UNDERLINE_DISPLAY_NAME;\n\n// --- Main Anchor Component ---\n\nexport interface AnchorProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n children?: React.ReactNode;\n /** Additional CSS class for the anchor element */\n className?: string;\n /** URL the anchor navigates to */\n href?: string;\n /** Browsing context for the link (e.g. \"_blank\") */\n target?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: AnchorStylesProp;\n /** Preview content to show in a tooltip on hover. Use this in server components instead of <Anchor.Preview>. */\n preview?: React.ReactNode;\n}\n\nconst AnchorRoot = React.forwardRef<HTMLAnchorElement | HTMLSpanElement, AnchorProps>(\n ({ className, children, href, target = \"_blank\", styles, preview: previewProp, ...props }, ref) => {\n let previewContent: React.ReactNode = previewProp ?? null;\n let hasUnderline = false;\n\n const childrenArray = React.Children.toArray(children);\n const resolved = resolveAnchorBaseStyles(styles);\n\n let filteredChildren: React.ReactNode[] = [];\n\n // Extract preview content and filter it out from rendered children\n React.Children.forEach(childrenArray, (child) => {\n if (React.isValidElement(child)) {\n if (matchesCompoundComponent(child.type, AnchorPreview, ANCHOR_PREVIEW_DISPLAY_NAME)) {\n if (!previewProp) previewContent = (child.props as any).children;\n // Don't add to filteredChildren\n } else if (matchesCompoundComponent(child.type, AnchorUnderline, ANCHOR_UNDERLINE_DISPLAY_NAME)) {\n hasUnderline = true;\n // Clone AnchorUnderline to inject resolved.underline\n const underlineChild = child as React.ReactElement<AnchorUnderlineProps>;\n filteredChildren.push(React.cloneElement(underlineChild, {\n className: cn(underlineChild.props.className, resolved.underline),\n }));\n } else {\n filteredChildren.push(child);\n }\n } else {\n filteredChildren.push(child);\n }\n });\n\n // Inject default underline if none provided\n if (!hasUnderline) {\n filteredChildren.push(<AnchorUnderline key=\"__default_underline\" className={resolved.underline} />);\n }\n\n const triggerElement = href ? (\n <a\n ref={ref as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n className={cn(\"anchor\", css.root, className, resolved.root)}\n {...props}\n >\n {filteredChildren}\n </a>\n ) : (\n <span\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\"anchor\", css.root, className, resolved.root)}\n {...props}\n >\n {filteredChildren}\n </span>\n );\n\n // If no preview content, render trigger directly without a tooltip wrapper.\n if (!previewContent) {\n return triggerElement;\n }\n\n return (\n <Tooltip\n content={previewContent}\n showArrow\n position=\"top\"\n className={cn(\"preview\", css.preview)}\n styles={{ content: resolved.preview }}\n >\n {triggerElement}\n </Tooltip>\n );\n },\n);\nAnchorRoot.displayName = \"Anchor\";\n\n// Compound component with attached sub-components\nconst Anchor = Object.assign(AnchorRoot, {\n Preview: AnchorPreview,\n Underline: AnchorUnderline,\n});\n\nexport { Anchor };\n",
|
|
4282
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .root {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground, currentColor);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover, var(--foreground-400));\n }\n\n
|
|
4406
|
+
"tsx": "import * as React from \"react\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { mergeProps } from \"@react-aria/utils\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Anchor.module.css\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface AnchorStyleSlots {\n root?: StyleValue;\n underline?: StyleValue;\n preview?: StyleValue;\n}\n\ntype AnchorStylesProp = StylesProp<AnchorStyleSlots>;\n\nconst resolveAnchorBaseStyles = createStylesResolver(['root', 'underline', 'preview'] as const);\n\nfunction resolveAnchorStyles(styles: AnchorStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveAnchorBaseStyles(styles);\n return resolveAnchorBaseStyles(styles);\n}\nconst ANCHOR_PREVIEW_DISPLAY_NAME = \"Anchor.Preview\";\nconst ANCHOR_UNDERLINE_DISPLAY_NAME = \"Anchor.Underline\";\n\nconst DASHED_DIMENSIONS = {\n sm: { thickness: 1, dashLength: 8, gapLength: 4 },\n md: { thickness: 2, dashLength: 8, gapLength: 4 },\n lg: { thickness: 4, dashLength: 10, gapLength: 6 },\n} as const;\n\nconst DOTTED_DIMENSIONS = {\n sm: { thickness: 1, radius: 0.5, spacing: 6 },\n md: { thickness: 2, radius: 1, spacing: 8 },\n lg: { thickness: 4, radius: 2, spacing: 12 },\n} as const;\n\nfunction getPath(orientation: Orientation, size: Size): string {\n const { thickness, dashLength, gapLength } = DASHED_DIMENSIONS[size];\n const totalLength = dashLength + gapLength;\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${totalLength}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${dashLength}' height='${thickness}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${totalLength}' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='${thickness}' height='${dashLength}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\nfunction getDottedMaskSvg(orientation: Orientation, size: Size): string {\n const { thickness, radius, spacing } = DOTTED_DIMENSIONS[size];\n\n if (orientation === \"horizontal\") {\n return `%3Csvg width='${spacing}' height='${thickness}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n }\n return `%3Csvg width='${thickness}' height='${spacing}' xmlns='http://www.w3.org/2000/svg'%3E%3Ccircle cx='${radius}' cy='${radius}' r='${radius}' fill='%23ffffff'/%3E%3C/svg%3E`;\n}\n\ntype CompoundComponentType = {\n displayName?: string;\n name?: string;\n render?: {\n displayName?: string;\n name?: string;\n };\n};\n\nfunction matchesCompoundComponent(\n childType: React.JSXElementConstructor<any> | string | undefined,\n component: React.JSXElementConstructor<any>,\n displayName: string,\n): boolean {\n if (!childType) {\n return false;\n }\n\n if (childType === component) {\n return true;\n }\n\n if (typeof childType === \"string\") {\n return false;\n }\n\n const componentType = childType as CompoundComponentType;\n\n return (\n componentType.displayName === displayName ||\n componentType.name === displayName ||\n componentType.render?.displayName === displayName ||\n componentType.render?.name === displayName\n );\n}\n\n// --- Sub-components ---\n\nexport interface AnchorPreviewProps\n extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n}\n\nexport function AnchorPreview({ children }: AnchorPreviewProps) {\n return null;\n}\nAnchorPreview.displayName = ANCHOR_PREVIEW_DISPLAY_NAME;\n\ninterface AnchorUnderlineProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Controls the line style of the underline */\n variant?: \"solid\" | \"dashed\" | \"dotted\";\n}\n\nconst AnchorUnderline = React.forwardRef<HTMLDivElement, AnchorUnderlineProps>(\n ({ className, variant = \"solid\", style, ...props }, ref) => {\n const getMaskStyles = (): React.CSSProperties => {\n if (variant === \"solid\") return {}\n\n const orientation = \"horizontal\";\n const size = \"sm\";\n\n const svgDataUri = variant === \"dashed\" ? getPath(orientation, size) : getDottedMaskSvg(orientation, size);\n const maskRepeat = \"repeat-x\";\n const encodedSvg = `url(\"data:image/svg+xml,${svgDataUri}\")`;\n\n return {\n WebkitMaskImage: encodedSvg,\n maskImage: encodedSvg,\n WebkitMaskRepeat: maskRepeat,\n maskRepeat: maskRepeat,\n } as React.CSSProperties;\n };\n\n return (\n <span\n ref={ref}\n className={cn(css.underline, className)}\n style={{ ...getMaskStyles(), ...style }}\n {...props}\n />\n );\n }\n);\nAnchorUnderline.displayName = ANCHOR_UNDERLINE_DISPLAY_NAME;\n\n// --- Main Anchor Component ---\n\nexport interface AnchorProps\n extends Omit<React.HTMLAttributes<HTMLElement>, \"onChange\"> {\n children?: React.ReactNode;\n /** Additional CSS class for the anchor element */\n className?: string;\n /** URL the anchor navigates to */\n href?: string;\n /** Browsing context for the link (e.g. \"_blank\") */\n target?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: AnchorStylesProp;\n /** Preview content to show in a tooltip on hover. Use this in server components instead of <Anchor.Preview>. */\n preview?: React.ReactNode;\n}\n\nconst AnchorRoot = React.forwardRef<HTMLAnchorElement | HTMLSpanElement, AnchorProps>(\n ({ className, children, href, target = \"_blank\", styles, preview: previewProp, ...props }, ref) => {\n const rootRef = React.useRef<HTMLAnchorElement | HTMLSpanElement>(null);\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({});\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef: rootRef,\n containerRef: rootRef,\n surfaceSelector: '[data-anchor-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"self\",\n });\n const mergedRef = useMergeRefs(rootRef, ref);\n\n let previewContent: React.ReactNode = previewProp ?? null;\n let hasUnderline = false;\n\n const childrenArray = React.Children.toArray(children);\n const resolved = resolveAnchorStyles(styles);\n\n let filteredChildren: React.ReactNode[] = [];\n\n // Extract preview content and filter it out from rendered children\n React.Children.forEach(childrenArray, (child) => {\n if (React.isValidElement(child)) {\n if (matchesCompoundComponent(child.type, AnchorPreview, ANCHOR_PREVIEW_DISPLAY_NAME)) {\n if (!previewProp) previewContent = (child.props as any).children;\n // Don't add to filteredChildren\n } else if (matchesCompoundComponent(child.type, AnchorUnderline, ANCHOR_UNDERLINE_DISPLAY_NAME)) {\n hasUnderline = true;\n // Clone AnchorUnderline to inject resolved.underline\n const underlineChild = child as React.ReactElement<AnchorUnderlineProps>;\n filteredChildren.push(React.cloneElement(underlineChild, {\n className: cn(underlineChild.props.className, resolved.underline),\n }));\n } else {\n filteredChildren.push(child);\n }\n } else {\n filteredChildren.push(child);\n }\n });\n\n // Inject default underline if none provided\n if (!hasUnderline) {\n filteredChildren.push(<AnchorUnderline key=\"__default_underline\" className={resolved.underline} />);\n }\n\n const { onChange, onChangeCapture, ...otherProps } = props as any;\n const mergedFocusProps = mergeProps(focusProps, hoverProps) as any;\n const { onChange: _, onChangeCapture: __, ...safeFocusProps } = mergedFocusProps;\n\n const triggerElement = href ? (\n <a\n ref={mergedRef as React.Ref<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={target === \"_blank\" ? \"noopener noreferrer\" : undefined}\n className={cn(\"anchor\", css.root, className, resolved.root, scopeProps.className)}\n data-anchor-focus-surface=\"true\"\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n {...(otherProps as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n {...(safeFocusProps as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n >\n <span {...indicatorProps} data-focus-indicator=\"local\" aria-hidden=\"true\" />\n {filteredChildren}\n </a>\n ) : (\n <span\n ref={mergedRef as React.Ref<HTMLSpanElement>}\n className={cn(\"anchor\", css.root, className, resolved.root, scopeProps.className)}\n data-anchor-focus-surface=\"true\"\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n {...(otherProps as React.HTMLAttributes<HTMLSpanElement>)}\n {...(safeFocusProps as React.HTMLAttributes<HTMLSpanElement>)}\n >\n <span {...indicatorProps} data-focus-indicator=\"local\" aria-hidden=\"true\" />\n {filteredChildren}\n </span>\n );\n\n // If no preview content, render trigger directly without a tooltip wrapper.\n if (!previewContent) {\n return triggerElement;\n }\n\n return (\n <Tooltip\n content={previewContent}\n showArrow\n position=\"top\"\n className={cn(\"preview\", css.preview)}\n styles={{ content: resolved.preview }}\n >\n {triggerElement}\n </Tooltip>\n );\n },\n);\nAnchorRoot.displayName = \"Anchor\";\n\n// Compound component with attached sub-components\nconst Anchor = Object.assign(AnchorRoot, {\n Preview: AnchorPreview,\n Underline: AnchorUnderline,\n});\n\nexport { Anchor };\n",
|
|
4407
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .root {\n @apply inline-block relative cursor-pointer;\n display: inline-block;\n color: var(--foreground, currentColor);\n text-decoration: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &:hover .underline {\n background: var(--underline-background-hover, var(--foreground-400));\n }\n\n &[data-focus-visible=\"true\"] {\n outline: 2px solid var(--focus-visible, var(--focus-ring));\n outline-offset: 2px;\n border-radius: 2px;\n }\n }\n\n .underline {\n @apply absolute left-0 right-0 bottom-0 h-px;\n background: var(--underline-background, var(--background-600));\n transform-origin: right;\n transform: scaleX(1);\n transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .preview {\n }\n}\n",
|
|
4283
4408
|
"cssTypes": "export const anchor: string;\nexport const preview: string;\nexport const root: string;\nexport const underline: string;"
|
|
4284
4409
|
},
|
|
4285
4410
|
"badge": {
|
|
@@ -4293,8 +4418,8 @@ export const generatedSourceCode = {
|
|
|
4293
4418
|
"cssTypes": "declare const styles: {\n banner: string;\n content: string;\n dismiss: string;\n note: string;\n info: string;\n success: string;\n warning: string;\n danger: string;\n sm: string;\n md: string;\n lg: string;\n iconContainer: string;\n icon: string;\n title: string;\n body: string;\n};\n\nexport default styles;\n"
|
|
4294
4419
|
},
|
|
4295
4420
|
"button": {
|
|
4296
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Button.module.css\";\n\ntype ButtonSize = (string & {});\ntype ButtonIconSlots = {\n left?: React.ReactNode;\n right?: React.ReactNode;\n};\n\ninterface ButtonIconStyles {\n left?: StyleValue;\n right?: StyleValue;\n}\n\nexport interface ButtonStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | ButtonIconStyles;\n}\n\nexport type ButtonStylesProp = StylesProp<ButtonStyleSlots>;\n\nconst resolveButtonBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction resolveButtonStyles(styles: ButtonStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveButtonBaseStyles(styles)\n const { root, icon } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n return resolveButtonBaseStyles({ root, iconLeft, iconRight });\n}\n\nexport interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"href\" | \"target\"> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Size class appended to the root element. Accepts any string. */\n size?: ButtonSize;\n /** Disables interaction and applies disabled styling */\n isDisabled?: boolean;\n /** React Aria press handler — preferred over onClick for accessibility */\n onPress?: (e: { target: EventTarget | null }) => void;\n /** Icon slots rendered before (left) or after (right) the button label */\n icon?: React.ReactNode | ButtonIconSlots;\n /** Renders the button as an anchor element when provided */\n href?: string;\n /** Browsing context for the anchor variant (e.g. \"_blank\") */\n target?: React.HTMLAttributeAnchorTarget;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ButtonStylesProp;\n}\n\nfunction isButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is ButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon);\n}\n\nfunction resolveButtonIcon(icon: ButtonProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isButtonIconSlots(icon)) {\n return icon;\n }\n\n return { left: icon };\n}\n\nfunction resolveButtonIconSizeClass(size: ButtonSize | undefined) {\n if (!size) {\n return undefined;\n }\n\n return (css as unknown as Record<string, string | undefined>)[`icon-${size}`];\n}\n\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n ({ className, styles, variant = \"default\", size = \"md\", children, onClick, onPress, isDisabled, disabled, icon, href, target, rel, ...props }, ref) => {\n const buttonRef = React.useRef<HTMLButtonElement | HTMLAnchorElement>(null);\n const mergedRef =
|
|
4297
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--background-pressed-border, var(--background-hover-border, var(--background-border)));\n }\n\n &:focus-visible {\n
|
|
4421
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport css from \"./Button.module.css\";\n\ntype ButtonSize = (string & {});\ntype ButtonIconSlots = {\n left?: React.ReactNode;\n right?: React.ReactNode;\n};\n\ninterface ButtonIconStyles {\n left?: StyleValue;\n right?: StyleValue;\n}\n\nexport interface ButtonStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | ButtonIconStyles;\n}\n\nexport type ButtonStylesProp = StylesProp<ButtonStyleSlots>;\n\nconst resolveButtonBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction resolveButtonStyles(styles: ButtonStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveButtonBaseStyles(styles)\n const { root, icon } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n return resolveButtonBaseStyles({ root, iconLeft, iconRight });\n}\n\nexport interface ButtonProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"href\" | \"target\"> {\n /** Variant class appended to the root element. Accepts any string. */\n variant?: string;\n /** Size class appended to the root element. Accepts any string. */\n size?: ButtonSize;\n /** Disables interaction and applies disabled styling */\n isDisabled?: boolean;\n /** React Aria press handler — preferred over onClick for accessibility */\n onPress?: (e: { target: EventTarget | null }) => void;\n /** Icon slots rendered before (left) or after (right) the button label */\n icon?: React.ReactNode | ButtonIconSlots;\n /** Renders the button as an anchor element when provided */\n href?: string;\n /** Browsing context for the anchor variant (e.g. \"_blank\") */\n target?: React.HTMLAttributeAnchorTarget;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ButtonStylesProp;\n}\n\nfunction isButtonIconSlots(icon: ButtonProps[\"icon\"]): icon is ButtonIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon);\n}\n\nfunction resolveButtonIcon(icon: ButtonProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isButtonIconSlots(icon)) {\n return icon;\n }\n\n return { left: icon };\n}\n\nfunction resolveButtonIconSizeClass(size: ButtonSize | undefined) {\n if (!size) {\n return undefined;\n }\n\n return (css as unknown as Record<string, string | undefined>)[`icon-${size}`];\n}\n\nconst Button = React.forwardRef<HTMLButtonElement | HTMLAnchorElement, ButtonProps>(\n ({ className, styles, variant = \"default\", size = \"md\", children, onClick, onPress, isDisabled, disabled, icon, href, target, rel, ...props }, ref) => {\n const scopeRef = React.useRef<HTMLDivElement>(null);\n const buttonRef = React.useRef<HTMLButtonElement | HTMLAnchorElement>(null);\n const mergedRef = useMergeRefs(ref, buttonRef);\n const isButtonDisabled = isDisabled ?? disabled ?? false;\n const [isPressed, setIsPressed] = React.useState(false);\n const isAnchor = !!href;\n\n const handlePress = React.useCallback((e: any) => {\n if (onPress) onPress({ target: e.target });\n if (onClick) onClick(e as unknown as React.MouseEvent<HTMLButtonElement>);\n }, [onPress, onClick]);\n\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n if (!isButtonDisabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e as any);\n }, [isButtonDisabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e as any);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e as any);\n }, [props]);\n\n const { buttonProps } = useButton({\n isDisabled: isButtonDisabled,\n onPress: handlePress,\n }, buttonRef as React.RefObject<HTMLButtonElement>);\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing({ autoFocus: props.autoFocus });\n const { hoverProps, isHovered } = useHover({ isDisabled: isButtonDisabled });\n\n const resolved = resolveButtonStyles(styles);\n const resolvedIcon = resolveButtonIcon(icon);\n const iconSizeClassName = resolveButtonIconSizeClass(size);\n const buttonClassName = cn(\"button\", variant, size, css.button, className, resolved.root);\n\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: buttonRef as React.RefObject<HTMLElement>,\n surfaceSelector: \"button, a\",\n radiusSource: \"surface\",\n });\n\n if (isAnchor) {\n return (\n <div ref={scopeRef} className={cn(\"button-scope\", scopeProps.className)}>\n <div {...indicatorProps} />\n <a\n {...mergeProps(focusProps, hoverProps, props as any)}\n ref={mergedRef as unknown as React.RefObject<HTMLAnchorElement>}\n href={href}\n target={target}\n rel={rel ?? (target === \"_blank\" ? \"noopener noreferrer\" : undefined)}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </a>\n </div>\n );\n }\n\n return (\n <div ref={scopeRef} className={cn(\"button-scope\", scopeProps.className)}>\n <div {...indicatorProps} />\n <button\n {...mergeProps(buttonProps, focusProps, hoverProps, props)}\n disabled={isButtonDisabled}\n ref={mergedRef as unknown as React.RefObject<HTMLButtonElement>}\n onMouseDown={handleMouseDown}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n className={buttonClassName}\n data-disabled={isButtonDisabled ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : \"false\"}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {resolvedIcon?.left && <span className={cn(iconSizeClassName, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(iconSizeClassName, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </button>\n </div>\n );\n }\n);\n\nButton.displayName = \"Button\";\n\nexport { Button };\n",
|
|
4422
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .button {\n @apply inline-flex items-center justify-center gap-2 select-none cursor-pointer whitespace-nowrap;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n\n font-weight: var(--font-weight-medium, 500);\n font-size: var(--text-sm, 0.875rem);\n line-height: var(--leading-tight, 1.25);\n\n &:hover:not(:disabled) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n border-color: var(--background-pressed-border, var(--background-hover-border, var(--background-border)));\n }\n\n &:focus-visible {\n outline: none;\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n filter: grayscale(0.5);\n }\n }\n}\n",
|
|
4298
4423
|
"cssTypes": "export interface Styles {\n button: string;\n \"default\": string;\n \"primary\": string;\n \"secondary\": string;\n \"outline\": string;\n \"ghost\": string;\n \"danger\": string;\n \"sm\": string;\n \"md\": string;\n \"lg\": string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4299
4424
|
},
|
|
4300
4425
|
"card": {
|
|
@@ -4303,8 +4428,8 @@ export const generatedSourceCode = {
|
|
|
4303
4428
|
"cssTypes": "declare const styles: {\n card: string;\n header: string;\n body: string;\n footer: string;\n};\n\nexport default styles;\n"
|
|
4304
4429
|
},
|
|
4305
4430
|
"checkbox": {
|
|
4306
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState } from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Checkbox.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface CheckboxIconStyles {\n checkmark?: StyleValue;\n indeterminate?: StyleValue;\n}\n\nexport interface CheckboxStyleSlots {\n root?: StyleValue;\n checkbox?: StyleValue;\n \"icon-checkmark\"?: StyleValue;\n \"icon-indeterminate\"?: StyleValue;\n icon?: StyleValue | CheckboxIconStyles;\n label?: StyleValue;\n \"helper-text\"?: StyleValue;\n}\n\nexport type CheckboxStylesProp = StylesProp<CheckboxStyleSlots>;\n\nconst resolveCheckboxBaseStyles = createStylesResolver([\n \"root\",\n \"checkbox\",\n \"icon-checkmark\",\n \"icon-indeterminate\",\n \"label\",\n \"helper-text\",\n] as const);\n\nfunction resolveCheckboxStyles(styles: CheckboxStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveCheckboxBaseStyles(styles);\n const { root, checkbox, icon, label } = styles;\n\n let iconCheckmark: StyleValue | undefined = styles[\"icon-checkmark\"];\n let iconIndeterminate: StyleValue | undefined = styles[\"icon-indeterminate\"];\n\n if (icon) {\n if (typeof icon === \"string\" || Array.isArray(icon)) {\n iconCheckmark = cn(icon, iconCheckmark);\n iconIndeterminate = cn(icon, iconIndeterminate);\n } else {\n iconCheckmark = cn(icon.checkmark, iconCheckmark);\n iconIndeterminate = cn(icon.indeterminate, iconIndeterminate);\n }\n }\n\n return resolveCheckboxBaseStyles({\n root,\n checkbox,\n \"icon-checkmark\": iconCheckmark,\n \"icon-indeterminate\": iconIndeterminate,\n label,\n \"helper-text\": styles[\"helper-text\"],\n });\n}\n\nexport interface CheckboxProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n /** Size of the checkbox */\n size?: Size;\n /** Label text or element displayed next to the checkbox */\n label?: React.ReactNode;\n /** Helper text shown below the checkbox */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to show an indeterminate (partial selection) state */\n isIndeterminate?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: CheckboxStylesProp;\n}\n\nconst sizeMap: Record<Size, string> = {\n sm: css[\"size-sm\"],\n md: css[\"size-md\"],\n lg: css[\"size-lg\"],\n};\n\nconst labelSizeMap: Record<Size, string> = {\n sm: css[\"label-sm\"],\n md: css[\"label-md\"],\n lg: css[\"label-lg\"],\n};\n\nexport const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(\n (\n {\n className,\n size = \"md\",\n label,\n helperText,\n helperTextError = false,\n id,\n disabled = false,\n checked,\n defaultChecked,\n onChange,\n isIndeterminate = false,\n styles,\n ...props\n },\n ref\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const
|
|
4307
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .container {\n @apply relative inline-flex items-center justify-center;\n }\n\n .checkbox {\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-xs, 0.25rem);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-
|
|
4431
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState } from \"react\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { mergeProps } from \"@react-aria/utils\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport css from \"./Checkbox.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\ninterface CheckboxIconStyles {\n checkmark?: StyleValue;\n indeterminate?: StyleValue;\n}\n\nexport interface CheckboxStyleSlots {\n root?: StyleValue;\n checkbox?: StyleValue;\n \"icon-checkmark\"?: StyleValue;\n \"icon-indeterminate\"?: StyleValue;\n icon?: StyleValue | CheckboxIconStyles;\n label?: StyleValue;\n \"helper-text\"?: StyleValue;\n}\n\nexport type CheckboxStylesProp = StylesProp<CheckboxStyleSlots>;\n\nconst resolveCheckboxBaseStyles = createStylesResolver([\n \"root\",\n \"checkbox\",\n \"icon-checkmark\",\n \"icon-indeterminate\",\n \"label\",\n \"helper-text\",\n] as const);\n\nfunction resolveCheckboxStyles(styles: CheckboxStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveCheckboxBaseStyles(styles);\n const { root, checkbox, icon, label } = styles;\n\n let iconCheckmark: StyleValue | undefined = styles[\"icon-checkmark\"];\n let iconIndeterminate: StyleValue | undefined = styles[\"icon-indeterminate\"];\n\n if (icon) {\n if (typeof icon === \"string\" || Array.isArray(icon)) {\n iconCheckmark = cn(icon, iconCheckmark);\n iconIndeterminate = cn(icon, iconIndeterminate);\n } else {\n iconCheckmark = cn(icon.checkmark, iconCheckmark);\n iconIndeterminate = cn(icon.indeterminate, iconIndeterminate);\n }\n }\n\n return resolveCheckboxBaseStyles({\n root,\n checkbox,\n \"icon-checkmark\": iconCheckmark,\n \"icon-indeterminate\": iconIndeterminate,\n label,\n \"helper-text\": styles[\"helper-text\"],\n });\n}\n\nexport interface CheckboxProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"size\"> {\n /** Size of the checkbox */\n size?: Size;\n /** Label text or element displayed next to the checkbox */\n label?: React.ReactNode;\n /** Helper text shown below the checkbox */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to show an indeterminate (partial selection) state */\n isIndeterminate?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: CheckboxStylesProp;\n}\n\nconst sizeMap: Record<Size, string> = {\n sm: css[\"size-sm\"],\n md: css[\"size-md\"],\n lg: css[\"size-lg\"],\n};\n\nconst labelSizeMap: Record<Size, string> = {\n sm: css[\"label-sm\"],\n md: css[\"label-md\"],\n lg: css[\"label-lg\"],\n};\n\nexport const Checkbox = forwardRef<HTMLDivElement, CheckboxProps>(\n (\n {\n className,\n size = \"md\",\n label,\n helperText,\n helperTextError = false,\n id,\n disabled = false,\n checked,\n defaultChecked,\n onChange,\n isIndeterminate = false,\n styles,\n ...props\n },\n ref\n ) => {\n const inputRef = React.useRef<HTMLInputElement>(null);\n const rootRef = React.useRef<HTMLDivElement>(null);\n // Track pressed state for tactile feedback animation (data-pressed attribute)\n const [isPressed, setIsPressed] = useState(false);\n const [internalChecked, setInternalChecked] = useState(() =>\n checked !== undefined ? checked : (defaultChecked ?? false)\n );\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef: rootRef,\n containerRef: rootRef,\n surfaceSelector: '[data-checkbox-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n });\n\n // React Aria press state handlers for tactile scale animation (mouse)\n const handleMouseDown = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n if (!disabled) {\n setIsPressed(true);\n }\n props.onMouseDown?.(e);\n }, [disabled, props]);\n\n const handleMouseUp = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseUp?.(e);\n }, [props]);\n\n const handleMouseLeave = React.useCallback((e: React.MouseEvent<HTMLInputElement>) => {\n setIsPressed(false);\n props.onMouseLeave?.(e);\n }, [props]);\n\n // React Aria press state handlers for keyboard interactions (Space/Enter)\n const handleKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (!disabled && (e.key === \" \" || e.key === \"Enter\")) {\n setIsPressed(true);\n }\n props.onKeyDown?.(e);\n }, [disabled, props]);\n\n const handleKeyUp = React.useCallback((e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \" \" || e.key === \"Enter\") {\n setIsPressed(false);\n }\n props.onKeyUp?.(e);\n }, [props]);\n\n React.useEffect(() => {\n if (checked !== undefined) {\n setInternalChecked(checked);\n }\n }, [checked]);\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n // Update internal state (needed for uncontrolled mode)\n setInternalChecked(e.target.checked);\n // Call parent handler if provided\n onChange?.(e);\n };\n\n // Filter out boolean props to avoid DOM attribute warnings\n const domProps = Object.fromEntries(\n Object.entries(props).filter(([, value]) => typeof value !== 'boolean')\n );\n\n const inputProps = mergeProps(domProps, focusProps, {\n onChange: handleChange,\n onMouseDown: handleMouseDown,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n }) as React.InputHTMLAttributes<HTMLInputElement>;\n\n // Determine if this is a controlled component\n const isControlled = checked !== undefined;\n const displayChecked = isControlled ? checked : internalChecked;\n\n const resolved = resolveCheckboxStyles(styles);\n const mergedRootRef = useMergeRefs(rootRef, ref);\n\n return (\n <div ref={mergedRootRef} className={cn(\"checkbox-root\", scopeProps.className, css['checkbox-root'], resolved.root)}>\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div className={cn(css.container, sizeMap[size])}>\n <input\n ref={inputRef}\n type=\"checkbox\"\n id={id}\n disabled={disabled}\n {...(isControlled ? { checked } : { defaultChecked: internalChecked })}\n className={cn(\n 'checkbox',\n css.checkbox,\n className,\n resolved.checkbox\n )}\n data-size={size}\n data-selected={displayChecked ? \"true\" : undefined}\n data-disabled={disabled ? \"true\" : undefined}\n data-indeterminate={isIndeterminate ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n data-checkbox-focus-surface=\"true\"\n {...inputProps}\n />\n {displayChecked && !isIndeterminate && (\n <svg\n className={cn('checkbox checkmark', css.checkmark, resolved[\"icon-checkmark\"])}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"3\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n )}\n {isIndeterminate && (\n <svg\n className={cn('checkbox indeterminate', css.indeterminate, resolved[\"icon-indeterminate\"])}\n viewBox=\"0 0 24 24\"\n fill=\"currentColor\"\n >\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" stroke=\"currentColor\" strokeWidth=\"3\" strokeLinecap=\"round\" />\n </svg>\n )}\n </div>\n {label && (\n <label\n htmlFor={id}\n className={cn(\n css.label,\n labelSizeMap[size],\n resolved.label\n )}\n data-disabled={disabled ? \"true\" : undefined}\n >\n {label}\n </label>\n )}\n {helperText && (\n <p\n className={cn(\n css[\"helper-text\"],\n resolved[\"helper-text\"]\n )}\n data-error={helperTextError ? \"true\" : undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nCheckbox.displayName = \"Checkbox\";\n",
|
|
4432
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .checkbox-root {\n @apply inline-flex items-center justify-center gap-3;\n }\n\n .container {\n @apply relative inline-flex items-center justify-center;\n }\n\n .checkbox {\n @apply relative h-5 w-5 cursor-pointer appearance-none;\n\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-xs, 0.25rem);\n outline: none;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n\n &:hover:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-indeterminate=\"true\"] {\n background-color: var(--background-indeterminate);\n border-color: var(--background-indeterminate-border);\n }\n\n &[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--disabled-opacity, 0.6);\n pointer-events: none;\n }\n\n /* Sizes */\n &.size-sm {\n @apply h-4 w-4;\n }\n\n &.size-md {\n @apply h-5 w-5;\n }\n\n &.size-lg {\n @apply h-6 w-6;\n }\n }\n\n .checkmark,\n .indeterminate {\n @apply absolute;\n inset: 50%;\n width: 65%;\n height: 65%;\n transform: translate(-50%, -50%);\n color: var(--icon-foreground);\n pointer-events: none;\n }\n\n .label {\n @apply cursor-pointer select-none;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-disabled=\"true\"] {\n @apply opacity-60 cursor-not-allowed;\n }\n }\n\n .label-sm {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .label-md {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .label-lg {\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .helper-text {\n @apply text-sm ml-8;\n transition: color 200ms var(--ease-snappy-pop);\n color: var(--helper-text-foreground);\n\n &[data-error=\"true\"] {\n color: var(--helper-text-error-foreground);\n }\n }\n}\n",
|
|
4308
4433
|
"cssTypes": "declare const styles: {\n \"checkbox-root\": string;\n container: string;\n checkbox: string;\n checkmark: string;\n indeterminate: string;\n \"size-sm\": string;\n \"size-md\": string;\n \"size-lg\": string;\n label: string;\n \"label-sm\": string;\n \"label-md\": string;\n \"label-lg\": string;\n \"helper-text\": string;\n};\n\nexport default styles;\n"
|
|
4309
4434
|
},
|
|
4310
4435
|
"code": {
|
|
@@ -4318,7 +4443,7 @@ export const generatedSourceCode = {
|
|
|
4318
4443
|
"cssTypes": "export interface Styles {\n color: string;\n controls: string;\n \"input-group\": string;\n input: string;\n format: string;\n canvas: string;\n \"canvas-inner\": string;\n \"canvas-gradient-hue\": string;\n \"canvas-gradient-saturation\": string;\n \"canvas-gradient-brightness\": string;\n \"canvas-pointer\": string;\n \"hue-slider\": string;\n \"hue-track\": string;\n \"hue-thumb\": string;\n \"opacity-slider\": string;\n \"opacity-track\": string;\n \"opacity-thumb\": string;\n \"recent-colors\": string;\n \"recent-color-swatch\": string;\n \"preview-swatch\": string;\n preview: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4319
4444
|
},
|
|
4320
4445
|
"command": {
|
|
4321
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\"\n\nimport { createPortal } from \"react-dom\";\n\nimport { useDialog } from \"@react-aria/dialog\";\nimport { FocusScope } from \"@react-aria/focus\";\n\nimport { useOverlayTriggerState } from \"@react-stately/overlays\";\n\nimport { filterDOMProps } from \"@react-aria/utils\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Search } from \"lucide-react\";\nimport { useScrollLock } from \"../../hooks/useScrollLock\";\n\nimport { Card } from \"../Card\";\nimport { Scroll } from \"../Scroll\";\nimport { Badge } from \"../Badge\";\nimport { Input, type InputProps } from \"../Input\";\n\nimport type { Key } from \"react-aria\";\nimport styles from \"./Command.module.css\";\n\ninterface CommandStyleSlots {\n root?: StyleValue;\n overlay?: StyleValue;\n input?: StyleValue;\n list?: StyleValue;\n item?: StyleValue;\n itemContent?: StyleValue;\n footer?: StyleValue;\n}\n\ntype CommandStylesProp = StylesProp<CommandStyleSlots>;\n\nconst resolveCommandBaseStyles = createStylesResolver([\n \"root\",\n \"overlay\",\n \"input\",\n \"list\",\n \"item\",\n \"itemContent\",\n \"footer\",\n] as const);\n\nexport interface CommandItem {\n id: string;\n label: string;\n description?: string;\n category?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n keywords?: string[];\n action: () => void | Promise<void>;\n hint?: string;\n}\n\nexport interface CommandGroupedItems {\n category: string | undefined;\n items: CommandItem[];\n}\n\ninterface CommandContextValue {\n isOpen: boolean;\n close: () => void;\n focusedKey: Key | null;\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>;\n registerItem: (key: Key, textValue: string) => void;\n unregisterItem: (key: Key) => void;\n actionRef: React.MutableRefObject<Map<Key, () => void | Promise<void>>>;\n searchInputRef: React.MutableRefObject<HTMLInputElement | null>;\n scrollableRef: React.MutableRefObject<HTMLDivElement | null>;\n searchValue: string;\n setSearchValue: React.Dispatch<React.SetStateAction<string>>;\n filteredItems: CommandItem[];\n groupedItems: CommandGroupedItems[];\n}\n\nconst CommandContext = React.createContext<CommandContextValue | undefined>(\n undefined,\n);\n\nfunction useCommandContext() {\n const ctx = React.useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"Command sub-components must be used within Command\");\n }\n return ctx;\n}\n\nfunction scoreCommandRelevance(\n text: string,\n query: string,\n): number {\n const t = text.toLowerCase();\n const q = query.toLowerCase();\n\n if (t === q) return 1000;\n if (t.startsWith(q)) return 900;\n if (t.split(/\\s+/).some((word) => word === q)) return 800;\n if (t.includes(q)) {\n const index = t.indexOf(q);\n return 710 - Math.min(index, 10);\n }\n return 0;\n}\n\nexport interface CommandProps {\n /** Whether the command palette is open */\n open?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Additional CSS class for the palette dialog */\n className?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: CommandStylesProp;\n /** List of command items to display */\n items?: CommandItem[];\n /** Custom filter function for commands against the query */\n filter?: (command: CommandItem, query: string) => boolean;\n /** Child elements rendered inside the palette */\n children?: React.ReactNode;\n}\n\nconst Command = React.forwardRef<HTMLDivElement, CommandProps>(\n (\n { open = false, onOpenChange, className, styles: commandStyles, items = [], filter, children },\n ref,\n ) => {\n const [mounted, setMounted] = React.useState(false);\n const overlayState = useOverlayTriggerState({\n isOpen: open,\n onOpenChange,\n });\n\n const modalRef = React.useRef<HTMLDivElement>(null);\n const paletteRef = React.useRef<HTMLDivElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const scrollableRef = React.useRef<HTMLDivElement>(null);\n const resolved = resolveCommandBaseStyles(commandStyles);\n\n useScrollLock(overlayState.isOpen, scrollableRef.current);\n const itemsRef = React.useRef<Map<Key, string>>(new Map());\n const actionRef = React.useRef<Map<Key, () => void | Promise<void>>>(\n new Map(),\n );\n const focusedKeyRef = React.useRef<Key | null>(null);\n\n const [focusedKey, setFocusedKey] = React.useState<Key | null>(null);\n const [itemCount, setItemCount] = React.useState(0);\n const [searchValue, setSearchValue] = React.useState(\"\");\n\n const filteredItems = items.filter((cmd) => !filter || filter(cmd, searchValue));\n\n const groupedItems = React.useMemo(() => {\n const groups = new Map<string | undefined, CommandItem[]>();\n filteredItems.forEach((cmd) => {\n const cat = cmd.category;\n if (!groups.has(cat)) {\n groups.set(cat, []);\n }\n groups.get(cat)!.push(cmd);\n });\n\n // Maintain category order from original items\n const categoryOrder = new Map<string | undefined, number>();\n let idx = 0;\n items.forEach((cmd) => {\n if (!categoryOrder.has(cmd.category)) {\n categoryOrder.set(cmd.category, idx++);\n }\n });\n\n return Array.from(groups.entries())\n .sort(\n ([a], [b]) =>\n (categoryOrder.get(a) ?? Infinity) - (categoryOrder.get(b) ?? Infinity),\n )\n .map(([category, items]) => ({ category, items }));\n }, [filteredItems, items]);\n\n React.useImperativeHandle(ref, () => paletteRef.current as HTMLDivElement);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n // Sync focusedKeyRef with focusedKey\n React.useEffect(() => {\n focusedKeyRef.current = focusedKey;\n }, [focusedKey]);\n\n // Auto-focus search input when opening\n React.useEffect(() => {\n if (overlayState.isOpen && searchInputRef.current) {\n setTimeout(() => searchInputRef.current?.focus(), 0);\n }\n }, [overlayState.isOpen]);\n\n // Cleanup state when overlay closes\n React.useEffect(() => {\n if (!overlayState.isOpen) {\n scrollableRef.current = null;\n setSearchValue(\"\");\n }\n }, [overlayState.isOpen]);\n\n // Cmd+K global listener\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac =\n navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0 ||\n navigator.userAgent.indexOf(\"Mac\") !== -1;\n const isCommandKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (isCommandKey && event.key === \"k\") {\n event.preventDefault();\n overlayState.open();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [overlayState]);\n\n // Auto-focus first item when items change (filtering, opening)\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n if (!searchValue) {\n setFocusedKey(null);\n return;\n }\n\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length > 0) {\n setFocusedKey(keys[0]);\n } else {\n setFocusedKey(null);\n }\n }, [itemCount, overlayState.isOpen, searchValue]);\n\n // Keyboard navigation\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[0]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[(idx + 1) % keys.length]);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[keys.length - 1]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[idx === 0 ? keys.length - 1 : idx - 1]);\n }\n break;\n }\n case \"Enter\": {\n event.preventDefault();\n if (focusedKey !== null) {\n const action = actionRef.current.get(focusedKey);\n if (action) {\n action();\n overlayState.close();\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n overlayState.close();\n break;\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [overlayState.isOpen, focusedKey]);\n\n const registerItem = React.useCallback((key: Key, textValue: string) => {\n itemsRef.current.set(key, textValue);\n setItemCount((c) => c + 1);\n }, []);\n\n const unregisterItem = React.useCallback((key: Key) => {\n itemsRef.current.delete(key);\n setItemCount((c) => c + 1);\n }, []);\n\n // Click outside to close\n const handleOverlayClick = React.useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n overlayState.close();\n }\n },\n [overlayState],\n );\n\n const { dialogProps } = useDialog(\n { \"aria-label\": \"Command palette\" },\n modalRef,\n );\n\n if (!mounted || !overlayState.isOpen) {\n return null;\n }\n\n return createPortal(\n <FocusScope contain restoreFocus>\n <div\n className={cn(\n \"command\",\n styles[\"overlay\"],\n resolved.overlay,\n )}\n onClick={handleOverlayClick}\n >\n <Card\n {...filterDOMProps(dialogProps)}\n ref={modalRef}\n className={cn(\"content\", styles[\"content\"], className, resolved.root)}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <CommandContext.Provider\n value={{\n isOpen: overlayState.isOpen,\n close: overlayState.close,\n focusedKey,\n setFocusedKey,\n registerItem,\n unregisterItem,\n actionRef,\n searchInputRef,\n scrollableRef,\n searchValue,\n setSearchValue,\n filteredItems,\n groupedItems,\n }}\n >\n {children}\n </CommandContext.Provider>\n </Card>\n </div>\n </FocusScope>,\n document.body,\n );\n },\n);\n\nCommand.displayName = \"Command\";\n\ninterface CommandInputProps extends InputProps {}\n\nconst CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(\n ({ value: externalValue, onChange: externalOnChange, icon, actions, placeholder = \"Search...\", ...props }, ref) => {\n const { searchInputRef, searchValue, setSearchValue } = useCommandContext();\n\n const value = externalValue !== undefined ? externalValue : searchValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n externalOnChange?.(e);\n };\n\n const inputRef = (ref ?? searchInputRef) as React.RefObject<HTMLInputElement>;\n\n const resolvedActions = actions ?? (value ? [{ icon: <>✕</>, title: \"Clear search\", onClick: () => { setSearchValue(\"\"); } }] : []);\n\n return (\n <Card.Header className={styles[\"search\"]}>\n <Input\n ref={inputRef}\n value={value as string}\n onChange={handleChange}\n icon={icon ?? <Search className=\"w-4 h-4\" />}\n actions={resolvedActions}\n placeholder={placeholder}\n aria-label=\"Search commands\"\n styles={{ root: styles[\"input\"] }}\n {...props}\n />\n </Card.Header>\n );\n }\n);\n\nCommandInput.displayName = \"Command.Input\";\n\ninterface CommandListProps {\n /** Child elements rendered inside the list */\n children?: React.ReactNode;\n /** Message shown when no items match the search */\n emptyMessage?: string;\n /** Additional CSS class for the list container */\n className?: string;\n}\n\n/** Scrollable container that renders the filtered command items */\nconst CommandListComponent = React.forwardRef<\n HTMLDivElement,\n CommandListProps\n>(({ children, emptyMessage = \"No items found.\", className }, ref) => {\n const { scrollableRef } = useCommandContext();\n\n return (\n <div className={cn(styles[\"inner\"], className)}>\n <Scroll\n ref={(el) => {\n if (ref) {\n if (typeof ref === \"function\") {\n ref(el);\n } else {\n ref.current = el;\n }\n }\n scrollableRef.current = el;\n }}\n className={styles[\"list\"]}\n maxHeight=\"44dvh\"\n fade-y\n >\n <div role=\"listbox\" aria-label=\"Commands\">\n {!children ? (\n <div className={styles[\"empty\"]}>{emptyMessage}</div>\n ) : (\n children\n )}\n </div>\n </Scroll>\n </div>\n );\n});\n\nCommandListComponent.displayName = \"Command.List\";\n\ninterface CommandItemProps {\n /** Unique key identifying this command item */\n value: Key;\n /** Plain-text label used for keyboard navigation lookup */\n textValue: string;\n /** Called when the item is selected */\n action: () => void | Promise<void>;\n /** Child elements rendered inside the item */\n children?: React.ReactNode;\n /** Additional CSS class for the item */\n className?: string;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the command item */\n hint?: string;\n}\n\nconst CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(\n ({ value, textValue, action, children, className, hint }, ref) => {\n const { focusedKey, registerItem, unregisterItem, actionRef, close } =\n useCommandContext();\n\n React.useEffect(() => {\n registerItem(value, textValue);\n actionRef.current.set(value, action);\n return () => {\n unregisterItem(value);\n actionRef.current.delete(value);\n };\n }, [value, textValue, action, registerItem, unregisterItem, actionRef]);\n\n const isHighlighted = focusedKey === value;\n\n return (\n <div\n ref={ref}\n data-highlighted={isHighlighted}\n role=\"option\"\n aria-selected={isHighlighted}\n onClick={() => { action(); close(); }}\n className={cn(\"item\", styles[\"item\"], className)}\n >\n <div className={styles[\"item-content\"]}>{children}</div>\n {hint && (\n <Badge variant=\"secondary\" size=\"sm\" className={styles[\"hint-wrapper\"]}>\n {hint}\n </Badge>\n )}\n </div>\n );\n },\n);\n\nCommandItem.displayName = \"Command.Item\";\n\ninterface CommandCategoryProps {\n /** Child elements rendered inside the category header */\n children?: React.ReactNode;\n /** Additional CSS class for the category */\n className?: string;\n}\n\n/** Labeled section grouping related commands */\nconst CommandCategory = React.forwardRef<\n HTMLDivElement,\n CommandCategoryProps\n>(({ children, className }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(styles[\"category-header\"], className)}\n >\n {children}\n </div>\n );\n});\n\nCommandCategory.displayName = \"Command.Category\";\n\ninterface CommandFooterProps {\n /** Child elements rendered inside the footer */\n children?: React.ReactNode;\n /** Additional CSS class applied to the footer */\n className?: string;\n}\n\n/** Fixed bottom bar in the command palette for hints or actions */\nconst CommandFooter = React.forwardRef<HTMLDivElement, CommandFooterProps>(\n ({ children, className }, ref) => {\n return (\n <Card.Footer ref={ref} className={cn(styles[\"footer\"], className)}>\n {children}\n </Card.Footer>\n );\n },\n);\n\nCommandFooter.displayName = \"Command.Footer\";\n\nexport interface CommandGroupsProps {\n /** Renders a category header for the given category name */\n renderCategory?: (category: string | undefined) => React.ReactNode;\n /** Renders a single command item row */\n renderItem: (command: CommandItem, hint?: string) => React.ReactNode;\n /** Additional CSS class for the groups container */\n className?: string;\n}\n\n/** Wrapper that renders multiple Command.Category sections */\nconst CommandGroups = React.forwardRef<HTMLDivElement, CommandGroupsProps>(\n ({ renderCategory, renderItem, className }, ref) => {\n const { groupedItems } = useCommandContext();\n\n return (\n <div ref={ref} className={className}>\n {groupedItems.map(({ category, items }) => (\n <div key={category || \"uncategorized\"}>\n {renderCategory && renderCategory(category)}\n {items.map((cmd) => (\n <React.Fragment key={cmd.id}>{renderItem(cmd, cmd.hint)}</React.Fragment>\n ))}\n </div>\n ))}\n </div>\n );\n },\n);\n\nCommandGroups.displayName = \"Command.Groups\";\n\ninterface CommandComponent\n extends React.ForwardRefExoticComponent<\n CommandProps & React.RefAttributes<HTMLDivElement>\n > {\n Input: typeof CommandInput;\n List: typeof CommandListComponent;\n Item: typeof CommandItem;\n Category: typeof CommandCategory;\n Footer: typeof CommandFooter;\n Groups: typeof CommandGroups;\n}\n\nconst CommandWithSubcomponents = Object.assign(Command, {\n Input: CommandInput,\n List: CommandListComponent,\n Item: CommandItem,\n Category: CommandCategory,\n Footer: CommandFooter,\n Groups: CommandGroups,\n}) as CommandComponent;\n\nexport { CommandWithSubcomponents as Command };\nexport { scoreCommandRelevance };\nexport { useCommandContext };\n",
|
|
4446
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\"\n\nimport { createPortal } from \"react-dom\";\n\nimport { useDialog } from \"@react-aria/dialog\";\nimport { FocusScope } from \"@react-aria/focus\";\n\nimport { useOverlayTriggerState } from \"@react-stately/overlays\";\n\nimport { filterDOMProps } from \"@react-aria/utils\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Search } from \"lucide-react\";\nimport { useScrollLock } from \"../../hooks/useScrollLock\";\n\nimport { Card } from \"../Card\";\nimport { Scroll } from \"../Scroll\";\nimport { Badge } from \"../Badge\";\nimport { Input, type InputProps } from \"../Input\";\n\nimport type { Key } from \"react-aria\";\nimport styles from \"./Command.module.css\";\n\ninterface CommandStyleSlots {\n root?: StyleValue;\n overlay?: StyleValue;\n input?: StyleValue;\n list?: StyleValue;\n item?: StyleValue;\n itemContent?: StyleValue;\n footer?: StyleValue;\n}\n\ntype CommandStylesProp = StylesProp<CommandStyleSlots>;\n\nconst resolveCommandBaseStyles = createStylesResolver([\n \"root\",\n \"overlay\",\n \"input\",\n \"list\",\n \"item\",\n \"itemContent\",\n \"footer\",\n] as const);\n\nexport interface CommandItem {\n id: string;\n label: string;\n description?: string;\n category?: string;\n shortcut?: string;\n icon?: React.ReactNode;\n keywords?: string[];\n action: () => void | Promise<void>;\n hint?: string;\n}\n\nexport interface CommandGroupedItems {\n category: string | undefined;\n items: CommandItem[];\n}\n\ninterface CommandContextValue {\n isOpen: boolean;\n close: () => void;\n focusedKey: Key | null;\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>;\n registerItem: (key: Key, textValue: string) => void;\n unregisterItem: (key: Key) => void;\n actionRef: React.MutableRefObject<Map<Key, () => void | Promise<void>>>;\n searchInputRef: React.MutableRefObject<HTMLInputElement | null>;\n scrollableRef: React.MutableRefObject<HTMLDivElement | null>;\n searchValue: string;\n setSearchValue: React.Dispatch<React.SetStateAction<string>>;\n filteredItems: CommandItem[];\n groupedItems: CommandGroupedItems[];\n}\n\nconst CommandContext = React.createContext<CommandContextValue | undefined>(\n undefined,\n);\n\nfunction useCommandContext() {\n const ctx = React.useContext(CommandContext);\n if (!ctx) {\n throw new Error(\"Command sub-components must be used within Command\");\n }\n return ctx;\n}\n\nfunction scoreCommandRelevance(\n text: string,\n query: string,\n): number {\n const t = text.toLowerCase();\n const q = query.toLowerCase();\n\n if (t === q) return 1000;\n if (t.startsWith(q)) return 900;\n if (t.split(/\\s+/).some((word) => word === q)) return 800;\n if (t.includes(q)) {\n const index = t.indexOf(q);\n return 710 - Math.min(index, 10);\n }\n return 0;\n}\n\nexport interface CommandProps {\n /** Whether the command palette is open */\n open?: boolean;\n /** Called when the open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Additional CSS class for the palette dialog */\n className?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: CommandStylesProp;\n /** List of command items to display */\n items?: CommandItem[];\n /** Custom filter function for commands against the query */\n filter?: (command: CommandItem, query: string) => boolean;\n /** Child elements rendered inside the palette */\n children?: React.ReactNode;\n}\n\nconst Command = React.forwardRef<HTMLDivElement, CommandProps>(\n (\n { open = false, onOpenChange, className, styles: commandStyles, items = [], filter, children },\n ref,\n ) => {\n const [mounted, setMounted] = React.useState(false);\n const overlayState = useOverlayTriggerState({\n isOpen: open,\n onOpenChange,\n });\n\n const modalRef = React.useRef<HTMLDivElement>(null);\n const paletteRef = React.useRef<HTMLDivElement>(null);\n const searchInputRef = React.useRef<HTMLInputElement>(null);\n const scrollableRef = React.useRef<HTMLDivElement>(null);\n const resolved = resolveCommandBaseStyles(commandStyles);\n\n useScrollLock(overlayState.isOpen, scrollableRef.current);\n const itemsRef = React.useRef<Map<Key, string>>(new Map());\n const actionRef = React.useRef<Map<Key, () => void | Promise<void>>>(\n new Map(),\n );\n const focusedKeyRef = React.useRef<Key | null>(null);\n\n const [focusedKey, setFocusedKey] = React.useState<Key | null>(null);\n const [itemCount, setItemCount] = React.useState(0);\n const [searchValue, setSearchValue] = React.useState(\"\");\n\n const filteredItems = items.filter((cmd) => !filter || filter(cmd, searchValue));\n\n const groupedItems = React.useMemo(() => {\n const groups = new Map<string | undefined, CommandItem[]>();\n filteredItems.forEach((cmd) => {\n const cat = cmd.category;\n if (!groups.has(cat)) {\n groups.set(cat, []);\n }\n groups.get(cat)!.push(cmd);\n });\n\n // Maintain category order from original items\n const categoryOrder = new Map<string | undefined, number>();\n let idx = 0;\n items.forEach((cmd) => {\n if (!categoryOrder.has(cmd.category)) {\n categoryOrder.set(cmd.category, idx++);\n }\n });\n\n return Array.from(groups.entries())\n .sort(\n ([a], [b]) =>\n (categoryOrder.get(a) ?? Infinity) - (categoryOrder.get(b) ?? Infinity),\n )\n .map(([category, items]) => ({ category, items }));\n }, [filteredItems, items]);\n\n React.useImperativeHandle(ref, () => paletteRef.current as HTMLDivElement);\n\n React.useEffect(() => {\n setMounted(true);\n }, []);\n\n // Sync focusedKeyRef with focusedKey\n React.useEffect(() => {\n focusedKeyRef.current = focusedKey;\n }, [focusedKey]);\n\n // Auto-focus search input when opening\n React.useEffect(() => {\n if (overlayState.isOpen && searchInputRef.current) {\n setTimeout(() => searchInputRef.current?.focus(), 0);\n }\n }, [overlayState.isOpen]);\n\n // Cleanup state when overlay closes\n React.useEffect(() => {\n if (!overlayState.isOpen) {\n scrollableRef.current = null;\n setSearchValue(\"\");\n }\n }, [overlayState.isOpen]);\n\n // Cmd+K global listener\n React.useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n const isMac =\n navigator.platform.toUpperCase().indexOf(\"MAC\") >= 0 ||\n navigator.userAgent.indexOf(\"Mac\") !== -1;\n const isCommandKey = isMac ? event.metaKey : event.ctrlKey;\n\n if (isCommandKey && event.key === \"k\") {\n event.preventDefault();\n overlayState.open();\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, [overlayState]);\n\n // Auto-focus first item when items change (filtering, opening)\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n if (!searchValue) {\n setFocusedKey(null);\n return;\n }\n\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length > 0) {\n setFocusedKey(keys[0]);\n } else {\n setFocusedKey(null);\n }\n }, [itemCount, overlayState.isOpen, searchValue]);\n\n // Keyboard navigation\n React.useEffect(() => {\n if (!overlayState.isOpen) return;\n\n const handleKeyDown = (event: KeyboardEvent) => {\n switch (event.key) {\n case \"ArrowDown\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[0]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[(idx + 1) % keys.length]);\n }\n break;\n }\n case \"ArrowUp\": {\n event.preventDefault();\n const keys = Array.from(itemsRef.current.keys());\n if (keys.length === 0) return;\n if (focusedKey === null) {\n setFocusedKey(keys[keys.length - 1]);\n } else {\n const idx = keys.indexOf(focusedKey);\n setFocusedKey(keys[idx === 0 ? keys.length - 1 : idx - 1]);\n }\n break;\n }\n case \"Enter\": {\n event.preventDefault();\n if (focusedKey !== null) {\n const action = actionRef.current.get(focusedKey);\n if (action) {\n action();\n overlayState.close();\n }\n }\n break;\n }\n case \"Escape\": {\n event.preventDefault();\n overlayState.close();\n break;\n }\n }\n };\n\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [overlayState.isOpen, focusedKey]);\n\n const registerItem = React.useCallback((key: Key, textValue: string) => {\n itemsRef.current.set(key, textValue);\n setItemCount((c) => c + 1);\n }, []);\n\n const unregisterItem = React.useCallback((key: Key) => {\n itemsRef.current.delete(key);\n setItemCount((c) => c + 1);\n }, []);\n\n // Click outside to close\n const handleOverlayClick = React.useCallback(\n (e: React.MouseEvent) => {\n if (e.target === e.currentTarget) {\n overlayState.close();\n }\n },\n [overlayState],\n );\n\n const { dialogProps } = useDialog(\n { \"aria-label\": \"Command palette\" },\n modalRef,\n );\n\n if (!mounted || !overlayState.isOpen) {\n return null;\n }\n\n return createPortal(\n <FocusScope contain restoreFocus>\n <div\n className={cn(\n \"command\",\n styles[\"overlay\"],\n resolved.overlay,\n )}\n onClick={handleOverlayClick}\n >\n <Card\n {...filterDOMProps(dialogProps)}\n ref={modalRef}\n className={cn(\"content\", styles[\"content\"], className, resolved.root)}\n role=\"dialog\"\n aria-modal=\"true\"\n >\n <CommandContext.Provider\n value={{\n isOpen: overlayState.isOpen,\n close: overlayState.close,\n focusedKey,\n setFocusedKey,\n registerItem,\n unregisterItem,\n actionRef,\n searchInputRef,\n scrollableRef,\n searchValue,\n setSearchValue,\n filteredItems,\n groupedItems,\n }}\n >\n {children}\n </CommandContext.Provider>\n </Card>\n </div>\n </FocusScope>,\n document.body,\n );\n },\n);\n\nCommand.displayName = \"Command\";\n\ninterface CommandInputProps extends InputProps {}\n\nconst CommandInput = React.forwardRef<HTMLInputElement, CommandInputProps>(\n ({ value: externalValue, onChange: externalOnChange, icon, actions, placeholder = \"Search...\", ...props }, ref) => {\n const { searchInputRef, searchValue, setSearchValue } = useCommandContext();\n\n const value = externalValue !== undefined ? externalValue : searchValue;\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n externalOnChange?.(e);\n };\n\n const inputRef = (ref ?? searchInputRef) as React.RefObject<HTMLInputElement>;\n\n const resolvedActions = actions ?? (value ? [{ icon: <>✕</>, title: \"Clear search\", onClick: () => { setSearchValue(\"\"); } }] : []);\n\n return (\n <Card.Header className={styles[\"search\"]}>\n <Input\n ref={inputRef}\n value={value as string}\n onChange={handleChange}\n icon={icon ?? <Search className=\"w-4 h-4\" />}\n actions={resolvedActions}\n placeholder={placeholder}\n aria-label=\"Search commands\"\n styles={{ root: styles[\"input\"] }}\n {...props}\n />\n </Card.Header>\n );\n }\n);\n\nCommandInput.displayName = \"Command.Input\";\n\ninterface CommandListProps {\n /** Child elements rendered inside the list */\n children?: React.ReactNode;\n /** Message shown when no items match the search */\n emptyMessage?: string;\n /** Additional CSS class for the list container */\n className?: string;\n}\n\n/** Scrollable container that renders the filtered command items */\nconst CommandListComponent = React.forwardRef<\n HTMLDivElement,\n CommandListProps\n>(({ children, emptyMessage = \"No items found.\", className }, ref) => {\n const { scrollableRef } = useCommandContext();\n\n return (\n <div className={cn(styles[\"inner\"], className)}>\n <Scroll\n ref={useMergeRefs(ref, scrollableRef)}\n className={styles[\"list\"]}\n maxHeight=\"44dvh\"\n fade-y\n >\n <div role=\"listbox\" aria-label=\"Commands\">\n {!children ? (\n <div className={styles[\"empty\"]}>{emptyMessage}</div>\n ) : (\n children\n )}\n </div>\n </Scroll>\n </div>\n );\n});\n\nCommandListComponent.displayName = \"Command.List\";\n\ninterface CommandItemProps {\n /** Unique key identifying this command item */\n value: Key;\n /** Plain-text label used for keyboard navigation lookup */\n textValue: string;\n /** Called when the item is selected */\n action: () => void | Promise<void>;\n /** Child elements rendered inside the item */\n children?: React.ReactNode;\n /** Additional CSS class for the item */\n className?: string;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the command item */\n hint?: string;\n}\n\nconst CommandItem = React.forwardRef<HTMLDivElement, CommandItemProps>(\n ({ value, textValue, action, children, className, hint }, ref) => {\n const { focusedKey, registerItem, unregisterItem, actionRef, close } =\n useCommandContext();\n\n React.useEffect(() => {\n registerItem(value, textValue);\n actionRef.current.set(value, action);\n return () => {\n unregisterItem(value);\n actionRef.current.delete(value);\n };\n }, [value, textValue, action, registerItem, unregisterItem, actionRef]);\n\n const isHighlighted = focusedKey === value;\n\n return (\n <div\n ref={ref}\n data-highlighted={isHighlighted}\n role=\"option\"\n aria-selected={isHighlighted}\n onClick={() => { action(); close(); }}\n className={cn(\"item\", styles[\"item\"], className)}\n >\n <div className={styles[\"item-content\"]}>{children}</div>\n {hint && (\n <Badge variant=\"secondary\" size=\"sm\" className={styles[\"hint-wrapper\"]}>\n {hint}\n </Badge>\n )}\n </div>\n );\n },\n);\n\nCommandItem.displayName = \"Command.Item\";\n\ninterface CommandCategoryProps {\n /** Child elements rendered inside the category header */\n children?: React.ReactNode;\n /** Additional CSS class for the category */\n className?: string;\n}\n\n/** Labeled section grouping related commands */\nconst CommandCategory = React.forwardRef<\n HTMLDivElement,\n CommandCategoryProps\n>(({ children, className }, ref) => {\n return (\n <div\n ref={ref}\n className={cn(styles[\"category-header\"], className)}\n >\n {children}\n </div>\n );\n});\n\nCommandCategory.displayName = \"Command.Category\";\n\ninterface CommandFooterProps {\n /** Child elements rendered inside the footer */\n children?: React.ReactNode;\n /** Additional CSS class applied to the footer */\n className?: string;\n}\n\n/** Fixed bottom bar in the command palette for hints or actions */\nconst CommandFooter = React.forwardRef<HTMLDivElement, CommandFooterProps>(\n ({ children, className }, ref) => {\n return (\n <Card.Footer ref={ref} className={cn(styles[\"footer\"], className)}>\n {children}\n </Card.Footer>\n );\n },\n);\n\nCommandFooter.displayName = \"Command.Footer\";\n\nexport interface CommandGroupsProps {\n /** Renders a category header for the given category name */\n renderCategory?: (category: string | undefined) => React.ReactNode;\n /** Renders a single command item row */\n renderItem: (command: CommandItem, hint?: string) => React.ReactNode;\n /** Additional CSS class for the groups container */\n className?: string;\n}\n\n/** Wrapper that renders multiple Command.Category sections */\nconst CommandGroups = React.forwardRef<HTMLDivElement, CommandGroupsProps>(\n ({ renderCategory, renderItem, className }, ref) => {\n const { groupedItems } = useCommandContext();\n\n return (\n <div ref={ref} className={className}>\n {groupedItems.map(({ category, items }) => (\n <div key={category || \"uncategorized\"}>\n {renderCategory && renderCategory(category)}\n {items.map((cmd) => (\n <React.Fragment key={cmd.id}>{renderItem(cmd, cmd.hint)}</React.Fragment>\n ))}\n </div>\n ))}\n </div>\n );\n },\n);\n\nCommandGroups.displayName = \"Command.Groups\";\n\ninterface CommandComponent\n extends React.ForwardRefExoticComponent<\n CommandProps & React.RefAttributes<HTMLDivElement>\n > {\n Input: typeof CommandInput;\n List: typeof CommandListComponent;\n Item: typeof CommandItem;\n Category: typeof CommandCategory;\n Footer: typeof CommandFooter;\n Groups: typeof CommandGroups;\n}\n\nconst CommandWithSubcomponents = Object.assign(Command, {\n Input: CommandInput,\n List: CommandListComponent,\n Item: CommandItem,\n Category: CommandCategory,\n Footer: CommandFooter,\n Groups: CommandGroups,\n}) as CommandComponent;\n\nexport { CommandWithSubcomponents as Command };\nexport { scoreCommandRelevance };\nexport { useCommandContext };\n",
|
|
4322
4447
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n /* Overlay Container */\n .overlay {\n @apply fixed inset-0 flex items-start justify-center overflow-hidden;\n z-index: 999;\n padding-top: 20vh;\n /* Apply backdrop styles directly to avoid creating a containing block that disrupts sticky elements */\n background-color: var(--overlay);\n backdrop-filter: var(--overlay-backdrop);\n }\n\n /* Content */\n .content {\n @apply relative m-2 w-full max-w-[28rem];\n border-radius: var(--radius-sm);\n background: var(--background);\n margin-inline: 1rem;\n box-shadow: var(--shadow);\n animation: fade-in-zoom-in 0.2s ease-out;\n }\n\n .inner {\n border-radius: var(--radius-sm) var(--radius-sm) 0 0;\n border-top: var(--border-width-base) solid var(--border-color);\n @apply overflow-hidden;\n }\n\n /* Search Section */\n .search {\n @apply border-none flex p-1.5;\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n }\n\n .input {\n border-color: transparent;\n background: transparent;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n\n /* List Section */\n .list {\n @apply py-0.5 px-2 space-y-2;\n background-color: var(--background-list);\n }\n\n .list :global([role=\"listbox\"]) {\n @apply flex w-full flex-col;\n }\n\n .item {\n @apply flex items-center justify-between rounded-sm px-2 py-0.5 cursor-pointer;\n border-radius: 0.375rem;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n color: var(--foreground);\n }\n\n .item:hover {\n background-color: var(--background-hover);\n }\n\n .item[data-highlighted=\"true\"] {\n background-color: var(--background-pressed);\n }\n\n .item-content {\n @apply flex min-w-0 flex-1 items-center gap-2.5;\n flex: 1;\n }\n\n .item-icon {\n @apply flex h-6 w-6 shrink-0 items-center justify-center;\n color: var(--foreground);\n }\n\n .item-labels {\n flex: 1;\n @apply min-w-0;\n }\n\n .item-label {\n font-size: var(--text-sm);\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n color: var(--foreground-muted);\n font-size: 0.875rem;\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .hint-wrapper {\n @apply flex items-center;\n }\n\n .category-header {\n @apply px-2 py-1.5 mt-2 first:mt-0;\n font-size: var(--text-sm);\n font-weight: var(--font-weight-semibold);\n color: var(--foreground-muted);\n }\n\n /* Empty State */\n .empty {\n padding: 1.5rem 1rem;\n text-align: center;\n font-size: 0.875rem;\n color: var(--foreground-muted);\n }\n\n /* Footer */\n .footer {\n @apply flex w-full items-center gap-2 px-1.5 py-2;\n background-color: var(--background-footer);\n border-top: 1px solid var(--border-color);\n justify-content: flex-between;\n }\n\n /* Animations */\n @keyframes fade-in-zoom-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }\n}\n",
|
|
4323
4448
|
"cssTypes": "export interface Styles {\n overlay: string;\n content: string;\n inner: string;\n search: string;\n input: string;\n list: string;\n item: string;\n \"item-content\": string;\n \"item-icon\": string;\n \"item-labels\": string;\n \"item-label\": string;\n \"item-description\": string;\n \"category-header\": string;\n empty: string;\n footer: string;\n \"hint-wrapper\": string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4324
4449
|
},
|
|
@@ -4338,13 +4463,13 @@ export const generatedSourceCode = {
|
|
|
4338
4463
|
"cssTypes": "declare const styles: {\n readonly divider: string;\n};\n\nexport default styles;\n"
|
|
4339
4464
|
},
|
|
4340
4465
|
"expand": {
|
|
4341
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useToggleState, ToggleState } from \"react-stately\";\nimport { useButton, useFocusRing, mergeProps } from \"react-aria\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Divider, DividerProps } from \"@/components/Divider\";\nimport styles from \"./Expand.module.css\";\nimport { ChevronDown } from \"lucide-react\";\n\ninterface ExpandStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n}\n\ntype ExpandStylesProp = StylesProp<ExpandStyleSlots>;\n\nconst resolveExpandBaseStyles = createStylesResolver(['root', 'trigger', 'content'] as const);\n\ninterface ExpandContextValue {\n state: ToggleState;\n isDisabled: boolean;\n}\n\nconst ExpandContext = React.createContext<ExpandContextValue | null>(null);\n\nconst useExpandContext = () => {\n const context = React.useContext(ExpandContext);\n if (!context) {\n throw new Error(\n \"Expand compound components must be used within an Expand component\",\n );\n }\n return context;\n};\n\n// --- Sub-components ---\n\nexport interface ExpandIconProps\n extends React.HTMLAttributes<HTMLSpanElement> {\n /** Custom icon element; defaults to a chevron */\n children?: React.ReactNode;\n}\n\n/** Animated chevron icon that rotates when the section is open */\nconst ExpandIcon = React.forwardRef<HTMLSpanElement, ExpandIconProps>(\n ({ children, className, ...props }, ref) => {\n const context = React.useContext(ExpandContext);\n return (\n <span\n ref={ref}\n className={cn(styles.icon, className)}\n data-expanded={context?.state.isSelected || undefined}\n {...props}\n >\n {children ?? <ChevronDown size={16} className=\"text-foreground-400\" />}\n </span>\n );\n },\n);\nExpandIcon.displayName = \"Expand.Icon\";\n\ninterface ExpandTriggerProps\n extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"title\"> {\n /** Label or content of the trigger button */\n children?: React.ReactNode;\n /** ReactNode label rendered in the title span (overrides HTML title tooltip) */\n title?: React.ReactNode;\n}\n\n/** Clickable button that toggles the expand/collapse state */\nconst ExpandTrigger = React.forwardRef<HTMLButtonElement, ExpandTriggerProps>(\n ({ children, className, title, ...props }, ref) => {\n const { state, isDisabled } = useExpandContext();\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n React.useImperativeHandle(\n ref,\n () => triggerRef.current as HTMLButtonElement,\n );\n\n const { buttonProps, isPressed } = useButton(\n {\n isDisabled,\n onPress: () => state.toggle(),\n // Filter out form-related props that useButton doesn't support\n ...Object.fromEntries(\n Object.entries(props).filter(\n ([key]) =>\n ![\n \"formAction\",\n \"formEncType\",\n \"formMethod\",\n \"formNoValidate\",\n \"formTarget\",\n ].includes(key),\n ),\n ),\n },\n triggerRef,\n );\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n\n const hasElementChildren = React.Children.toArray(children).some(\n (child) => React.isValidElement(child),\n );\n\n // Default: styled button with title span + auto-injected chevron\n return (\n <button\n ref={triggerRef}\n {...mergeProps(buttonProps, focusProps)}\n className={cn(styles.trigger, className)}\n aria-expanded={state.isSelected}\n data-expanded={state.isSelected || undefined}\n data-disabled={isDisabled || undefined}\n data-focused={isFocused || undefined}\n data-focus-visible={isFocusVisible || undefined}\n data-pressed={isPressed || undefined}\n >\n {hasElementChildren && title === undefined ? (\n children\n ) : (\n <>\n <span className={styles.title}>{title ?? children}</span>\n <ExpandIcon />\n </>\n )}\n </button>\n );\n },\n);\nExpandTrigger.displayName = \"Expand.Trigger\";\n\ninterface ExpandContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Content shown when the expand is open */\n children: React.ReactNode;\n /** Direction the content reveals from the trigger */\n from?: \"below\" | \"above\" | \"left\" | \"right\";\n}\n\n/** Collapsible content area revealed when expanded */\nconst ExpandContent = React.forwardRef<HTMLDivElement, ExpandContentProps>(\n ({ children, className, from, ...props }, ref) => {\n const { state } = useExpandContext();\n\n return (\n <div\n ref={ref}\n className={cn(styles.content, className)}\n data-expanded={state.isSelected || undefined}\n data-from={from && from !== \"below\" ? from : undefined}\n aria-hidden={!state.isSelected}\n {...props}\n >\n <div className={styles[\"content-inner\"]}>{children}</div>\n </div>\n );\n },\n);\nExpandContent.displayName = \"Expand.Content\";\n\n/** Separator line between expand sections */\nconst ExpandDivider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, spacing = \"none\", ...props }, ref) => {\n return (\n <Divider\n ref={ref}\n className={cn(\"mt-2\", className)}\n spacing={spacing}\n {...props}\n />\n );\n },\n);\nExpandDivider.displayName = \"Expand.Divider\";\n\n// --- Main Expand Component ---\n\nexport interface ExpandProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\" | \"onChange\"> {\n /** Header text or element for the trigger button in preset (non-compound) mode */\n title?: React.ReactNode;\n /** Controlled expanded state */\n isExpanded?: boolean;\n /** Initial expanded state for uncontrolled usage */\n defaultExpanded?: boolean;\n /** Called when the expanded state changes */\n onExpandedChange?: (isExpanded: boolean) => void;\n /** Alias for onExpandedChange */\n onChange?: (isExpanded: boolean) => void;\n /** Whether the expand is disabled */\n isDisabled?: boolean;\n /** Compound sub-components or content nodes */\n children?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: ExpandStylesProp;\n}\n\nconst ExpandRoot = React.forwardRef<HTMLDivElement, ExpandProps>(\n (\n {\n isExpanded,\n defaultExpanded = false,\n onExpandedChange,\n onChange,\n isDisabled = false,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const state = useToggleState({\n isSelected: isExpanded,\n defaultSelected: defaultExpanded,\n onChange: onExpandedChange || onChange,\n });\n\n const { title, styles: expandStyles, ...divProps } = props;\n const resolved = resolveExpandBaseStyles(expandStyles);\n\n return (\n <ExpandContext.Provider value={{ state, isDisabled }}>\n <div\n ref={ref}\n className={cn(\"expand\", styles.expand, className, resolved.root)}\n data-disabled={isDisabled || undefined}\n {...divProps}\n >\n {children}\n </div>\n </ExpandContext.Provider>\n );\n },\n);\nExpandRoot.displayName = \"Expand\";\n\n// Compatibility wrapper to support both old API and new Compound API\nconst Expand = React.forwardRef<\n HTMLDivElement,\n ExpandProps & {\n Trigger?: typeof ExpandTrigger;\n Content?: typeof ExpandContent;\n Divider?: typeof ExpandDivider;\n Icon?: typeof ExpandIcon;\n }\n>((props, ref) => {\n const { title, children, ...rootProps } = props;\n const resolved = resolveExpandBaseStyles(props.styles);\n\n // If title is provided, use the \"Preset\" structure (Backward Compatibility)\n if (title !== undefined) {\n const childrenArray = React.Children.toArray(children);\n const customDivider = childrenArray.find(\n (child) => React.isValidElement(child) && child.type === ExpandDivider,\n );\n const filteredChildren = childrenArray.filter(\n (child) => !(React.isValidElement(child) && child.type === ExpandDivider),\n );\n\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n <ExpandTrigger className={resolved.trigger}>{title}</ExpandTrigger>\n {customDivider || <ExpandDivider />}\n <ExpandContent className={resolved.content}>\n {filteredChildren}\n </ExpandContent>\n </ExpandRoot>\n );\n }\n\n // Otherwise, use Compound structure (children are expected to include Trigger/Content/Divider)\n return (\n <ExpandRoot ref={ref} {...rootProps}>\n {children}\n </ExpandRoot>\n );\n}) as React.ForwardRefExoticComponent<\n ExpandProps & React.RefAttributes<HTMLDivElement>\n> & {\n Trigger: typeof ExpandTrigger;\n Content: typeof ExpandContent;\n Divider: typeof ExpandDivider;\n Icon: typeof ExpandIcon;\n};\n\nExpand.displayName = \"Expand\";\n\n// Attach sub-components\nExpand.Trigger = ExpandTrigger;\nExpand.Content = ExpandContent;\nExpand.Divider = ExpandDivider;\nExpand.Icon = ExpandIcon;\n\nexport { Expand };\n",
|
|
4342
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .expand {\n --disabled-opacity: 0.6;\n\n @apply flex flex-col;\n }\n\n .expand[data-disabled] {\n opacity: var(--disabled-opacity);\n
|
|
4343
|
-
"cssTypes": "declare const styles: {\n expand: string;\n trigger: string;\n icon: string;\n title: string;\n content: string;\n \"content-inner\": string;\n};\n\nexport default styles;\n"
|
|
4466
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useButton } from \"@react-aria/button\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useToggleState, type ToggleState } from \"react-stately\";\nimport { ChevronDown } from \"lucide-react\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Divider, type DividerProps } from \"@/components/Divider\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport styles from \"./Expand.module.css\";\n\ntype ExpandDirection = \"below\" | \"above\" | \"left\" | \"right\";\n\ninterface ExpandIconStyles {\n collapsed?: StyleValue;\n expanded?: StyleValue;\n}\n\nexport interface ExpandStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n icon?: StyleValue | ExpandIconStyles;\n title?: StyleValue;\n content?: StyleValue;\n contentInner?: StyleValue;\n divider?: StyleValue;\n}\n\nexport type ExpandStylesProp = StylesProp<ExpandStyleSlots>;\n\nconst resolveExpandBaseStyles = createStylesResolver([\n \"root\",\n \"trigger\",\n \"icon\",\n \"iconCollapsed\",\n \"iconExpanded\",\n \"title\",\n \"content\",\n \"contentInner\",\n \"divider\",\n] as const);\n\nfunction resolveExpandStyles(stylesProp: ExpandStylesProp | undefined) {\n if (!stylesProp || typeof stylesProp === \"string\" || Array.isArray(stylesProp)) {\n return resolveExpandBaseStyles(stylesProp);\n }\n\n const { root, trigger, icon, title, content, contentInner, divider } = stylesProp;\n\n let iconClassName: StyleValue | undefined;\n let iconCollapsed: StyleValue | undefined;\n let iconExpanded: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === \"string\" || Array.isArray(icon)) {\n iconClassName = icon;\n iconCollapsed = icon;\n iconExpanded = icon;\n } else {\n iconCollapsed = icon.collapsed;\n iconExpanded = icon.expanded;\n }\n }\n\n return resolveExpandBaseStyles({\n root,\n trigger,\n icon: iconClassName,\n iconCollapsed,\n iconExpanded,\n title,\n content,\n contentInner,\n divider,\n });\n}\n\ninterface ExpandContextValue {\n state: ToggleState;\n isDisabled: boolean;\n resolvedStyles: ReturnType<typeof resolveExpandStyles>;\n}\n\nconst ExpandContext = React.createContext<ExpandContextValue | null>(null);\n\nfunction useExpandContext() {\n const context = React.useContext(ExpandContext);\n if (!context) {\n throw new Error(\"Expand compound components must be used within Expand\");\n }\n\n return context;\n}\n\nexport interface ExpandIconProps extends React.HTMLAttributes<HTMLSpanElement> {\n /** Custom icon element rendered inside the icon wrapper */\n children?: React.ReactNode;\n}\n\nconst ExpandIcon = React.forwardRef<HTMLSpanElement, ExpandIconProps>(\n ({ children, className, ...props }, ref) => {\n const { state, resolvedStyles } = useExpandContext();\n\n return (\n <span\n ref={ref}\n className={cn(\n \"icon\",\n styles.icon,\n resolvedStyles.icon,\n state.isSelected ? resolvedStyles.iconExpanded : resolvedStyles.iconCollapsed,\n className,\n )}\n data-selected={state.isSelected ? \"true\" : undefined}\n data-expanded={state.isSelected ? \"true\" : undefined}\n aria-hidden=\"true\"\n {...props}\n >\n {children ?? <ChevronDown size={16} />}\n </span>\n );\n },\n);\nExpandIcon.displayName = \"Expand.Icon\";\n\ninterface ExpandTriggerProps extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, \"title\"> {\n /** Label or content rendered inside the trigger */\n children?: React.ReactNode;\n /** Optional title element rendered in the trigger's text slot */\n title?: React.ReactNode;\n}\n\nconst ExpandTrigger = React.forwardRef<HTMLButtonElement, ExpandTriggerProps>(\n ({ children, className, title, ...props }, ref) => {\n const { state, isDisabled, resolvedStyles } = useExpandContext();\n const triggerRef = React.useRef<HTMLButtonElement>(null);\n const mergedRef = useMergeRefs(triggerRef, ref);\n\n const { buttonProps, isPressed } = useButton(\n {\n isDisabled,\n onPress: () => state.toggle(),\n },\n triggerRef,\n );\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n const hasElementChildren = React.Children.toArray(children).some(\n (child) => React.isValidElement(child),\n );\n const shouldRenderCompositeLabel = title !== undefined || !hasElementChildren;\n\n return (\n <button\n ref={mergedRef}\n {...mergeProps(buttonProps, focusProps, hoverProps, props)}\n className={cn(\n \"trigger\",\n styles.trigger,\n resolvedStyles.trigger,\n className,\n )}\n type=\"button\"\n aria-expanded={state.isSelected}\n data-selected={state.isSelected ? \"true\" : undefined}\n data-expanded={state.isSelected ? \"true\" : undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n data-expand-focus-surface=\"true\"\n >\n {shouldRenderCompositeLabel ? (\n <>\n <span\n className={cn(\"title\", styles.title, resolvedStyles.title)}\n >\n {title ?? children}\n </span>\n <ExpandIcon />\n </>\n ) : (\n children\n )}\n </button>\n );\n },\n);\nExpandTrigger.displayName = \"Expand.Trigger\";\n\nexport interface ExpandContentProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Content shown when the expand is open */\n children: React.ReactNode;\n /** Direction the content reveals from the trigger */\n from?: ExpandDirection;\n}\n\nconst ExpandContent = React.forwardRef<HTMLDivElement, ExpandContentProps>(\n ({ children, className, from, ...props }, ref) => {\n const { state, resolvedStyles } = useExpandContext();\n\n return (\n <div\n ref={ref}\n className={cn(\n \"content\",\n styles.content,\n resolvedStyles.content,\n className,\n )}\n data-selected={state.isSelected ? \"true\" : undefined}\n data-expanded={state.isSelected ? \"true\" : undefined}\n data-from={from && from !== \"below\" ? from : undefined}\n aria-hidden={!state.isSelected}\n {...props}\n >\n <div\n className={cn(\n \"content-inner\",\n styles[\"content-inner\"],\n resolvedStyles.contentInner,\n )}\n >\n {children}\n </div>\n </div>\n );\n },\n);\nExpandContent.displayName = \"Expand.Content\";\n\nconst ExpandDivider = React.forwardRef<HTMLDivElement, DividerProps>(\n ({ className, spacing = \"none\", styles: dividerStyles, ...props }, ref) => {\n const { resolvedStyles } = useExpandContext();\n\n return (\n <Divider\n ref={ref}\n spacing={spacing}\n styles={dividerStyles}\n className={cn(\"divider\", styles.divider, resolvedStyles.divider, className)}\n {...props}\n />\n );\n },\n);\nExpandDivider.displayName = \"Expand.Divider\";\n\nexport interface ExpandProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\" | \"onChange\"> {\n /** Header content rendered in preset mode */\n title?: React.ReactNode;\n /** Controlled expanded state */\n isExpanded?: boolean;\n /** Initial expanded state for uncontrolled usage */\n defaultExpanded?: boolean;\n /** Called when the expanded state changes */\n onExpandedChange?: (isExpanded: boolean) => void;\n /** Alias for onExpandedChange */\n onChange?: (isExpanded: boolean) => void;\n /** Whether the expand is disabled */\n isDisabled?: boolean;\n /** Compound sub-components or content nodes */\n children?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: ExpandStylesProp;\n}\n\nconst ExpandRoot = React.forwardRef<HTMLDivElement, ExpandProps>(\n (\n {\n className,\n title,\n isExpanded,\n defaultExpanded = false,\n onExpandedChange,\n onChange,\n isDisabled = false,\n children,\n styles: stylesProp,\n ...props\n },\n ref,\n ) => {\n const state = useToggleState({\n isSelected: isExpanded,\n defaultSelected: defaultExpanded,\n onChange: onExpandedChange ?? onChange,\n });\n\n const resolvedStyles = resolveExpandStyles(stylesProp);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const scopeRef = React.useRef<HTMLDivElement>(null);\n const mergedRootRef = useMergeRefs(rootRef, ref);\n const childrenArray = React.Children.toArray(children);\n\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: rootRef,\n surfaceSelector: '[data-expand-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"ring\",\n });\n\n const contextValue = React.useMemo<ExpandContextValue>(\n () => ({\n state,\n isDisabled,\n resolvedStyles,\n }),\n [state, isDisabled, resolvedStyles],\n );\n\n return (\n <ExpandContext.Provider value={contextValue}>\n <div ref={scopeRef} className={cn(\"expand-scope\", scopeProps.className, styles.scope)}>\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n ref={mergedRootRef}\n className={cn(\"expand\", styles.expand, className, resolvedStyles.root)}\n data-selected={state.isSelected ? \"true\" : undefined}\n data-expanded={state.isSelected ? \"true\" : undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n {...props}\n >\n {title !== undefined ? (\n <>\n <ExpandTrigger>{title}</ExpandTrigger>\n {childrenArray.find(\n (child) =>\n React.isValidElement(child) && child.type === ExpandDivider,\n ) ?? <ExpandDivider />}\n <ExpandContent>\n {childrenArray.filter(\n (child) =>\n !(React.isValidElement(child) && child.type === ExpandDivider),\n )}\n </ExpandContent>\n </>\n ) : (\n children\n )}\n </div>\n </div>\n </ExpandContext.Provider>\n );\n },\n);\nExpandRoot.displayName = \"Expand\";\n\nconst Expand = Object.assign(ExpandRoot, {\n Trigger: ExpandTrigger,\n Content: ExpandContent,\n Divider: ExpandDivider,\n Icon: ExpandIcon,\n});\n\nExpand.displayName = \"Expand\";\n\nexport { Expand };\n",
|
|
4467
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n position: relative;\n display: block;\n width: 100%;\n }\n\n .expand {\n --expand-disabled-opacity: 0.6;\n --expand-trigger-padding-x: 0.75rem;\n --expand-trigger-padding-y: 0.5rem;\n --expand-divider-spacing: 0.5rem;\n\n @apply flex w-full flex-col;\n }\n\n .expand[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--expand-disabled-opacity);\n }\n\n .trigger {\n @apply flex w-full items-stretch justify-between border-0 p-0 text-left;\n\n appearance: none;\n color: var(--foreground);\n background-color: var(--background);\n cursor: pointer;\n border-radius: 0;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n outline: none;\n box-shadow: none;\n transition:\n background-color 200ms var(--ease-smooth-settle),\n opacity 200ms var(--ease-smooth-settle)\n }\n\n .trigger:focus,\n .trigger:focus-visible {\n outline: none;\n box-shadow: none;\n }\n\n .trigger[data-disabled=\"true\"] {\n cursor: not-allowed;\n opacity: var(--expand-disabled-opacity);\n }\n\n .title {\n @apply flex min-w-0 flex-1 items-center overflow-hidden;\n\n padding: var(--expand-trigger-padding-y) 0 var(--expand-trigger-padding-y)\n var(--expand-trigger-padding-x);\n font-weight: var(--font-weight-medium);\n border-radius: 0;\n color: inherit;\n background-color: transparent;\n }\n\n .trigger:not([data-disabled=\"true\"]):has(.icon:hover) .title {\n background-color: transparent;\n }\n\n .icon {\n @apply flex shrink-0 items-center justify-center;\n\n padding: var(--expand-trigger-padding-y) var(--expand-trigger-padding-x);\n color: inherit;\n border-radius: 0;\n }\n\n @media (hover: hover) {\n .trigger:not([data-disabled=\"true\"]):hover .title,\n .trigger:not([data-disabled=\"true\"]):hover .icon {\n background-color: var(--background-hover);\n }\n\n .trigger:not([data-disabled=\"true\"]):has(.icon:hover) .title {\n background-color: transparent;\n }\n\n .trigger:not([data-disabled=\"true\"]) .icon:hover {\n border-radius: 0;\n }\n }\n\n .icon > * {\n transition: transform 250ms var(--ease-smooth-settle);\n }\n\n .icon[data-selected=\"true\"] > * {\n transform: rotate(180deg);\n }\n\n .expand:has(.content[data-from=\"above\"]) {\n flex-direction: column-reverse;\n }\n\n .expand:has(.content[data-from=\"above\"]) .icon > * {\n transform: rotate(180deg);\n }\n\n .expand:has(.content[data-from=\"above\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(0deg);\n }\n\n .expand:has(.content[data-from=\"left\"]) {\n @apply flex-row-reverse items-start;\n }\n\n .expand:has(.content[data-from=\"left\"]) .trigger {\n @apply w-auto flex-col;\n }\n\n .expand:has(.content[data-from=\"left\"]) .icon > * {\n transform: rotate(-90deg);\n }\n\n .expand:has(.content[data-from=\"left\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(90deg);\n }\n\n .expand:has(.content[data-from=\"right\"]) {\n @apply flex-row items-start;\n }\n\n .expand:has(.content[data-from=\"right\"]) .trigger {\n @apply w-auto flex-col;\n }\n\n .expand:has(.content[data-from=\"right\"]) .icon > * {\n transform: rotate(90deg);\n }\n\n .expand:has(.content[data-from=\"right\"]) .icon[data-selected=\"true\"] > * {\n transform: rotate(-90deg);\n }\n\n .content {\n display: grid;\n overflow: hidden;\n grid-template-rows: 0fr;\n transition: grid-template-rows 300ms var(--ease-smooth-settle);\n }\n\n .content[data-selected=\"true\"] {\n grid-template-rows: 1fr;\n }\n\n .content[data-from=\"left\"],\n .content[data-from=\"right\"] {\n grid-template-rows: 1fr;\n grid-template-columns: 0fr;\n transition: grid-template-columns 300ms var(--ease-smooth-settle);\n }\n\n .content[data-from=\"left\"][data-selected=\"true\"],\n .content[data-from=\"right\"][data-selected=\"true\"] {\n grid-template-columns: 1fr;\n }\n\n .content-inner {\n @apply min-h-0 overflow-hidden;\n\n min-width: 0;\n color: var(--foreground-content);\n background-color: var(--background-content);\n }\n\n .divider {\n margin-top: var(--expand-divider-spacing);\n }\n}\n",
|
|
4468
|
+
"cssTypes": "declare const styles: {\n scope: string;\n expand: string;\n trigger: string;\n icon: string;\n title: string;\n content: string;\n \"content-inner\": string;\n divider: string;\n};\n\nexport default styles;\n"
|
|
4344
4469
|
},
|
|
4345
4470
|
"flex": {
|
|
4346
|
-
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Flex.module.css\";\n\ntype FlexDirection = \"row\" | \"column\";\ntype FlexWrap = \"wrap\" | \"nowrap\";\ntype FlexJustify =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\ntype FlexAlign =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"baseline\";\ntype FlexGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\ninterface FlexStyleSlots {\n root?: StyleValue;\n}\n\ntype FlexStylesProp = StylesProp<FlexStyleSlots>;\n\nconst resolveFlexBaseStyles = createStylesResolver(['root'] as const);\n\nexport interface FlexProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Direction of the flex container */\n direction?: FlexDirection;\n /** Whether items wrap to the next line when they overflow */\n wrap?: FlexWrap;\n /** Gap between flex items */\n gap?: FlexGap;\n /** Alignment of items along the main axis */\n justify?: FlexJustify;\n /** Alignment of items along the cross axis */\n align?: FlexAlign;\n /** Wraps the flex container in a container query parent for breakpoint-aware responsiveness */\n containerQueryResponsive?: boolean;\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: FlexStylesProp;\n}\n\nconst directionMap = {\n row: styles[\"row\"],\n column: styles[\"column\"],\n} as const;\n\nconst wrapMap = {\n wrap: styles[\"wrap\"],\n nowrap: styles[\"nowrap\"],\n} as const;\n\nconst justifyMap = {\n \"flex-start\": styles[\"justify-flex-start\"],\n \"flex-end\": styles[\"justify-flex-end\"],\n center: styles[\"justify-center\"],\n \"space-between\": styles[\"justify-space-between\"],\n \"space-around\": styles[\"justify-space-around\"],\n \"space-evenly\": styles[\"justify-space-evenly\"],\n} as const;\n\nconst alignMap = {\n \"flex-start\": styles[\"align-flex-start\"],\n \"flex-end\": styles[\"align-flex-end\"],\n center: styles[\"align-center\"],\n stretch: styles[\"align-stretch\"],\n baseline: styles[\"align-baseline\"],\n} as const;\n\nconst gapMap = {\n xs: styles[\"gap-xs\"],\n sm: styles[\"gap-sm\"],\n md: styles[\"gap-md\"],\n lg: styles[\"gap-lg\"],\n xl: styles[\"gap-xl\"],\n} as const;\n\nconst Flex = React.forwardRef<HTMLDivElement, FlexProps>(\n (\n {\n className,\n styles: stylesProp,\n direction
|
|
4347
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full
|
|
4471
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Flex.module.css\";\n\ntype FlexDirection = \"row\" | \"column\";\ntype FlexWrap = \"wrap\" | \"nowrap\";\ntype FlexJustify =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"space-between\"\n | \"space-around\"\n | \"space-evenly\";\ntype FlexAlign =\n | \"flex-start\"\n | \"flex-end\"\n | \"center\"\n | \"stretch\"\n | \"baseline\";\ntype FlexGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\n\ninterface FlexStyleSlots {\n root?: StyleValue;\n}\n\ntype FlexStylesProp = StylesProp<FlexStyleSlots>;\n\nconst resolveFlexBaseStyles = createStylesResolver(['root'] as const);\n\nexport interface FlexProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Direction of the flex container */\n direction?: FlexDirection;\n /** Whether items wrap to the next line when they overflow */\n wrap?: FlexWrap;\n /** Gap between flex items */\n gap?: FlexGap;\n /** Alignment of items along the main axis */\n justify?: FlexJustify;\n /** Alignment of items along the cross axis */\n align?: FlexAlign;\n /** Wraps the flex container in a container query parent for breakpoint-aware responsiveness */\n containerQueryResponsive?: boolean;\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: FlexStylesProp;\n}\n\nconst directionMap = {\n row: styles[\"row\"],\n column: styles[\"column\"],\n} as const;\n\nconst wrapMap = {\n wrap: styles[\"wrap\"],\n nowrap: styles[\"nowrap\"],\n} as const;\n\nconst justifyMap = {\n \"flex-start\": styles[\"justify-flex-start\"],\n \"flex-end\": styles[\"justify-flex-end\"],\n center: styles[\"justify-center\"],\n \"space-between\": styles[\"justify-space-between\"],\n \"space-around\": styles[\"justify-space-around\"],\n \"space-evenly\": styles[\"justify-space-evenly\"],\n} as const;\n\nconst alignMap = {\n \"flex-start\": styles[\"align-flex-start\"],\n \"flex-end\": styles[\"align-flex-end\"],\n center: styles[\"align-center\"],\n stretch: styles[\"align-stretch\"],\n baseline: styles[\"align-baseline\"],\n} as const;\n\nconst gapMap = {\n xs: styles[\"gap-xs\"],\n sm: styles[\"gap-sm\"],\n md: styles[\"gap-md\"],\n lg: styles[\"gap-lg\"],\n xl: styles[\"gap-xl\"],\n} as const;\n\nconst Flex = React.forwardRef<HTMLDivElement, FlexProps>(\n (\n {\n className,\n styles: stylesProp,\n direction,\n wrap,\n gap,\n justify,\n align,\n containerQueryResponsive = false,\n children,\n ...props\n },\n ref\n ) => {\n const resolved = resolveFlexBaseStyles(stylesProp);\n if (containerQueryResponsive) {\n return (\n <div\n ref={ref}\n className={cn(styles[\"container-query-parent\"], className, resolved.root)}\n data-container-responsive=\"true\"\n {...props}\n >\n <div\n className={cn(\n styles.flex,\n direction && directionMap[direction],\n wrap && wrapMap[wrap],\n gap && gapMap[gap],\n justify && justifyMap[justify],\n align && alignMap[align],\n styles[\"container-responsive\"]\n )}\n data-direction={direction}\n data-wrap={wrap}\n data-gap={gap}\n data-justify={justify}\n data-align={align}\n >\n {children}\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n styles.flex,\n direction && directionMap[direction],\n wrap && wrapMap[wrap],\n gap && gapMap[gap],\n justify && justifyMap[justify],\n align && alignMap[align],\n className,\n resolved.root\n )}\n data-direction={direction}\n data-wrap={wrap}\n data-gap={gap}\n data-justify={justify}\n data-align={align}\n data-container-responsive={containerQueryResponsive || undefined}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nFlex.displayName = \"Flex\";\n\nexport { Flex };\n",
|
|
4472
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .flex {\n @apply flex w-full;\n }\n\n /* Direction variants */\n .flex.row { flex-direction: row; }\n .flex.column { flex-direction: column; }\n\n /* Wrap variants */\n .flex.wrap { flex-wrap: wrap; }\n .flex.nowrap { flex-wrap: nowrap; }\n\n /* Gap variants */\n .flex.gap-xs { gap: var(--spacing-xs); }\n .flex.gap-sm { gap: var(--spacing-sm); }\n .flex.gap-md { gap: var(--spacing-md); }\n .flex.gap-lg { gap: var(--spacing-lg); }\n .flex.gap-xl { gap: var(--spacing-xl); }\n\n /* Justify-content variants */\n .flex.justify-flex-start { justify-content: flex-start; }\n .flex.justify-flex-end { justify-content: flex-end; }\n .flex.justify-center { justify-content: center; }\n .flex.justify-space-between { justify-content: space-between; }\n .flex.justify-space-around { justify-content: space-around; }\n .flex.justify-space-evenly { justify-content: space-evenly; }\n\n /* Align-items variants */\n .flex.align-flex-start { align-items: flex-start; }\n .flex.align-flex-end { align-items: flex-end; }\n .flex.align-center { align-items: center; }\n .flex.align-stretch { align-items: stretch; }\n .flex.align-baseline { align-items: baseline; }\n\n /* Container query parent - establishes containment context */\n .container-query-parent {\n container-type: inline-size;\n container-name: flex-parent;\n @apply w-full;\n }\n\n /* Container query responsive behavior - use .flex.container-responsive for specificity parity with base variants */\n @container flex-parent (width < 400px) {\n .flex.container-responsive {\n flex-direction: column;\n flex-wrap: wrap;\n justify-content: flex-start;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (400px <= width < 500px) {\n .flex.container-responsive {\n flex-wrap: wrap;\n gap: var(--spacing-sm);\n }\n }\n\n @container flex-parent (500px <= width < 900px) {\n .flex.container-responsive {\n gap: var(--spacing-md);\n }\n }\n\n @container flex-parent (width >= 900px) {\n .flex.container-responsive {\n gap: var(--spacing-lg);\n }\n }\n}\n",
|
|
4348
4473
|
"cssTypes": "declare const styles: {\n flex: string;\n row: string;\n column: string;\n wrap: string;\n nowrap: string;\n \"gap-xs\": string;\n \"gap-sm\": string;\n \"gap-md\": string;\n \"gap-lg\": string;\n \"gap-xl\": string;\n \"justify-flex-start\": string;\n \"justify-flex-end\": string;\n \"justify-center\": string;\n \"justify-space-between\": string;\n \"justify-space-around\": string;\n \"justify-space-evenly\": string;\n \"align-flex-start\": string;\n \"align-flex-end\": string;\n \"align-center\": string;\n \"align-stretch\": string;\n \"align-baseline\": string;\n \"container-query-parent\": string;\n \"container-responsive\": string;\n};\n\nexport default styles;\n"
|
|
4349
4474
|
},
|
|
4350
4475
|
"frame": {
|
|
@@ -4353,9 +4478,9 @@ export const generatedSourceCode = {
|
|
|
4353
4478
|
"cssTypes": "declare const styles: {\n root: string;\n shape: string;\n stroke: string;\n};\n\nexport default styles;\n"
|
|
4354
4479
|
},
|
|
4355
4480
|
"gallery": {
|
|
4356
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing, useHover, usePress, mergeProps } from \"react-aria\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { Grid } from \"../Grid\"\nimport styles from \"./Gallery.module.css\"\n\n// Types\ntype GridColumns = number\ntype GridGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"\ntype ResponsiveColumns = {\n sm?: GridColumns\n md?: GridColumns\n lg?: GridColumns\n xl?: GridColumns\n}\n\ninterface GalleryProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Number of columns in the gallery grid */\n columns?: GridColumns | ResponsiveColumns\n /** Gap between gallery items */\n gap?: GridGap | number | string\n /** Number of rows in the gallery grid */\n rows?: \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"auto\"\n /** Whether to enable container-query-based responsive columns */\n responsive?: boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryStylesProp\n}\n\ninterface GalleryItemProps extends React.HTMLAttributes<HTMLElement> {\n /** URL the item links to */\n href?: string\n /** Called when the item is pressed */\n onPress?: (href?: string) => void\n /** Number of columns this item spans */\n columnSpan?: number\n /** Number of rows this item spans */\n rowSpan?: number\n /** Controls the item's layout orientation */\n orientation?: \"vertical\" | \"horizontal\"\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryItemStylesProp\n}\n\ninterface GalleryViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Aspect ratio of the view area (e.g. \"16/9\") */\n aspectRatio?: string\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryViewStylesProp\n}\n\ninterface GalleryBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, or slot object. */\n styles?: GalleryBodyStylesProp\n}\n\nexport interface GalleryStyleSlots {\n root?: StyleValue;\n item?: StyleValue;\n view?: StyleValue;\n body?: StyleValue;\n}\n\nexport type GalleryStylesProp = StylesProp<GalleryStyleSlots>;\n\nexport interface GalleryItemStyleSlots {\n root?: StyleValue;\n}\n\nexport type GalleryItemStylesProp = StylesProp<GalleryItemStyleSlots>;\n\nexport interface GalleryViewStyleSlots {\n root?: StyleValue;\n}\n\nexport type GalleryViewStylesProp = StylesProp<GalleryViewStyleSlots>;\n\nexport interface GalleryBodyStyleSlots {\n root?: StyleValue;\n}\n\nexport type GalleryBodyStylesProp = StylesProp<GalleryBodyStyleSlots>;\n\nconst resolveGalleryBaseStyles = createStylesResolver(['root', 'item', 'view', 'body'] as const);\nconst resolveGalleryItemBaseStyles = createStylesResolver(['root'] as const);\nconst resolveGalleryViewBaseStyles = createStylesResolver(['root'] as const);\nconst resolveGalleryBodyBaseStyles = createStylesResolver(['root'] as const);\n\nfunction resolveGalleryStyles(styles: GalleryStylesProp | undefined) {\n return resolveGalleryBaseStyles(styles);\n}\n\nfunction resolveGalleryItemStyles(styles: GalleryItemStylesProp | undefined) {\n return resolveGalleryItemBaseStyles(styles);\n}\n\nfunction resolveGalleryViewStyles(styles: GalleryViewStylesProp | undefined) {\n return resolveGalleryViewBaseStyles(styles);\n}\n\nfunction resolveGalleryBodyStyles(styles: GalleryBodyStylesProp | undefined) {\n return resolveGalleryBodyBaseStyles(styles);\n}\n\ntype GalleryResolvedStyles = ReturnType<typeof resolveGalleryBaseStyles>;\n\nconst GalleryStylesContext = React.createContext<GalleryResolvedStyles | undefined>(undefined);\n\n// Helper to map numeric columns to Grid's column values\nconst mapColumnsToGrid = (columns?: GridColumns | ResponsiveColumns): GridColumns | ResponsiveColumns => {\n if (!columns) return 3\n if (typeof columns === \"object\") return columns as ResponsiveColumns\n return columns\n}\n\n// Helper to map gap values to Grid's gap values\nconst mapGapToGrid = (gap?: GridGap | number | string): GridGap => {\n if (!gap) return \"md\"\n if (typeof gap === \"string\" && [\"xs\", \"sm\", \"md\", \"lg\", \"xl\"].includes(gap)) {\n return gap as GridGap\n }\n if (typeof gap === \"number\") {\n // Map numeric gap values (in pixels) to Grid gap presets\n if (gap <= 4) return \"xs\"\n if (gap <= 8) return \"sm\"\n if (gap <= 16) return \"md\"\n if (gap <= 24) return \"lg\"\n return \"xl\"\n }\n return \"md\" // default fallback\n}\n\n// Gallery Root Component\nconst GalleryRoot = React.forwardRef<HTMLDivElement, GalleryProps>(\n ({ columns = 3, gap = \"md\", rows, responsive, className, styles: stylesProp, children, ...props }, ref) => {\n const gridColumns = mapColumnsToGrid(columns)\n const gridGap = mapGapToGrid(gap)\n const resolved = resolveGalleryStyles(stylesProp);\n\n return (\n <GalleryStylesContext.Provider value={resolved}>\n <Grid\n ref={ref}\n columns={gridColumns as GridColumns | ResponsiveColumns}\n gap={gridGap}\n rows={rows}\n responsive={responsive}\n className={cn(\"gallery\", className, resolved.root)}\n {...props}\n >\n {children}\n </Grid>\n </GalleryStylesContext.Provider>\n )\n }\n)\nGalleryRoot.displayName = \"Gallery\"\n\n// Gallery Item Component\n/** A single media or content tile in the gallery grid */\nconst GalleryItem = React.forwardRef<HTMLElement, GalleryItemProps>(\n ({ href, onPress, columnSpan, rowSpan, orientation = \"vertical\", className, style, styles: stylesProp, children, ...props }, ref) => {\n const inherited = React.useContext(GalleryStylesContext);\n const resolved = resolveGalleryItemStyles(stylesProp);\n const elementRef = React.useRef<HTMLElement>(null)\n const combinedRef = (node: HTMLElement | null) => {\n (elementRef as React.MutableRefObject<HTMLElement | null>).current = node\n if (typeof ref === \"function\") {\n ref(node)\n } else if (ref) {\n ref.current = node\n }\n }\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({})\n\n // Use usePress for button interaction\n const { pressProps, isPressed } = usePress({\n onPress: () => onPress?.(href),\n })\n\n const spanStyles: React.CSSProperties = {\n ...(columnSpan && { gridColumn: `span ${columnSpan}` }),\n ...(rowSpan && { gridRow: `span ${rowSpan}` }),\n ...style,\n }\n\n // Ensure accessible name: aria-label, aria-labelledby, or text content\n const ariaLabel = props[\"aria-label\"] || props[\"aria-labelledby\"]\n const hasAccessibleName = ariaLabel || React.Children.count(children) > 0\n\n const commonProps = mergeProps(\n focusProps,\n hoverProps,\n pressProps,\n {\n className: cn('gallery', 'item', styles.item, className, inherited?.item, resolved.root),\n style: spanStyles,\n \"data-focused\": isFocused ? \"true\" : \"false\",\n \"data-focus-visible\": isFocusVisible ? \"true\" : \"false\",\n \"data-hovered\": isHovered ? \"true\" : \"false\",\n \"data-pressed\": isPressed ? \"true\" : \"false\",\n \"data-orientation\": orientation,\n ...(!hasAccessibleName && { \"aria-label\": \"Gallery item\" }),\n ...props,\n }\n )\n\n return (\n <div\n ref={combinedRef as React.Ref<HTMLDivElement>}\n role=\"button\"\n tabIndex={0}\n {...commonProps}\n >\n {children}\n </div>\n )\n }\n)\nGalleryItem.displayName = \"Gallery.Item\"\n\n// Gallery View Component\n/** Expanded full-screen view overlay for a selected gallery item */\nconst GalleryView = React.forwardRef<HTMLDivElement, GalleryViewProps>(\n ({ aspectRatio = \"16/9\", className, style, styles: stylesProp, children, ...props }, ref) => {\n const inherited = React.useContext(GalleryStylesContext);\n const resolved = resolveGalleryViewStyles(stylesProp);\n\n return (\n <div\n ref={ref}\n className={cn(\"gallery\", \"view\", styles.view, className, inherited?.view, resolved.root)}\n style={{\n \"--gallery-aspect-ratio\": aspectRatio,\n ...style\n } as React.CSSProperties}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryView.displayName = \"Gallery.View\"\n\n// Gallery Body Component\n/** Container for the gallery item's visible content */\nconst GalleryBody = React.forwardRef<HTMLDivElement, GalleryBodyProps>(\n ({ className, styles: stylesProp, children, ...props }, ref) => {\n const inherited = React.useContext(GalleryStylesContext);\n const resolved = resolveGalleryBodyStyles(stylesProp);\n\n return (\n <div\n ref={ref}\n className={cn('gallery', 'body', styles.body, className, inherited?.body, resolved.root)}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryBody.displayName = \"Gallery.Body\"\n\n// Compound Component\nconst Gallery = Object.assign(GalleryRoot, {\n Item: GalleryItem,\n View: GalleryView,\n Body: GalleryBody,\n})\n\nexport { Gallery, GalleryItem, GalleryView, GalleryBody }\nexport type { GalleryProps, GalleryItemProps, GalleryViewProps, GalleryBodyProps }\n",
|
|
4357
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .item {\n @apply
|
|
4358
|
-
"cssTypes": "declare const styles: {\n readonly item: string;\n readonly view: string;\n readonly body: string;\n};\n\nexport default styles;\n"
|
|
4481
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { mergeProps, useFocusRing, useHover, usePress } from \"react-aria\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\"\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\"\nimport { Grid } from \"../Grid\"\nimport css from \"./Gallery.module.css\"\n\ntype GridColumns = number\ntype GridGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\"\ntype Orientation = \"vertical\" | \"horizontal\"\ntype GalleryRows = \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"auto\"\ntype ResponsiveColumns = {\n sm?: GridColumns\n md?: GridColumns\n lg?: GridColumns\n xl?: GridColumns\n}\n\nexport interface GalleryStyleSlots {\n root?: StyleValue\n item?: StyleValue\n view?: StyleValue\n body?: StyleValue\n}\n\nexport type GalleryStylesProp = StylesProp<GalleryStyleSlots>\n\nexport interface GalleryItemStyleSlots {\n root?: StyleValue\n}\n\nexport type GalleryItemStylesProp = StylesProp<GalleryItemStyleSlots>\n\nexport interface GalleryViewStyleSlots {\n root?: StyleValue\n}\n\nexport type GalleryViewStylesProp = StylesProp<GalleryViewStyleSlots>\n\nexport interface GalleryBodyStyleSlots {\n root?: StyleValue\n}\n\nexport type GalleryBodyStylesProp = StylesProp<GalleryBodyStyleSlots>\n\nexport interface GalleryProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Number of columns in the gallery grid */\n columns?: GridColumns | ResponsiveColumns\n /** Gap between gallery items */\n gap?: GridGap | number | string\n /** Number of rows in the gallery grid */\n rows?: GalleryRows\n /** Whether to enable container-query-based responsive columns */\n responsive?: boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GalleryStylesProp\n}\n\nexport interface GalleryItemProps extends React.HTMLAttributes<HTMLDivElement> {\n /** URL the item links to */\n href?: string\n /** Called when the item is pressed */\n onPress?: (href?: string) => void\n /** Number of columns this item spans */\n columnSpan?: number\n /** Number of rows this item spans */\n rowSpan?: number\n /** Controls the item's layout orientation */\n orientation?: Orientation\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GalleryItemStylesProp\n}\n\nexport interface GalleryViewProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Aspect ratio of the view area (e.g. \"16/9\") */\n aspectRatio?: string\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GalleryViewStylesProp\n}\n\nexport interface GalleryBodyProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Classes applied to the root slot. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GalleryBodyStylesProp\n}\n\nconst resolveGalleryBaseStyles = createStylesResolver([\n \"root\",\n \"item\",\n \"view\",\n \"body\",\n] as const)\n\nconst resolveGalleryItemBaseStyles = createStylesResolver([\"root\"] as const)\nconst resolveGalleryViewBaseStyles = createStylesResolver([\"root\"] as const)\nconst resolveGalleryBodyBaseStyles = createStylesResolver([\"root\"] as const)\n\nfunction resolveGalleryStyles(\n styles: GalleryStylesProp | undefined\n): ReturnType<typeof resolveGalleryBaseStyles> {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveGalleryBaseStyles(styles)\n }\n\n const { root, item, view, body } = styles\n return resolveGalleryBaseStyles({ root, item, view, body })\n}\n\nfunction resolveGalleryItemStyles(\n styles: GalleryItemStylesProp | undefined\n): ReturnType<typeof resolveGalleryItemBaseStyles> {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveGalleryItemBaseStyles(styles)\n }\n\n const { root } = styles\n return resolveGalleryItemBaseStyles({ root })\n}\n\nfunction resolveGalleryViewStyles(\n styles: GalleryViewStylesProp | undefined\n): ReturnType<typeof resolveGalleryViewBaseStyles> {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveGalleryViewBaseStyles(styles)\n }\n\n const { root } = styles\n return resolveGalleryViewBaseStyles({ root })\n}\n\nfunction resolveGalleryBodyStyles(\n styles: GalleryBodyStylesProp | undefined\n): ReturnType<typeof resolveGalleryBodyBaseStyles> {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveGalleryBodyBaseStyles(styles)\n }\n\n const { root } = styles\n return resolveGalleryBodyBaseStyles({ root })\n}\n\ntype GalleryResolvedStyles = ReturnType<typeof resolveGalleryStyles>\n\nconst GalleryStylesContext = React.createContext<GalleryResolvedStyles | undefined>(undefined)\n\nconst mapColumnsToGrid = (columns?: GridColumns | ResponsiveColumns): GridColumns | ResponsiveColumns => {\n if (!columns) return 3\n if (typeof columns === \"object\") return columns\n return columns\n}\n\nconst mapGapToGrid = (gap?: GridGap | number | string): GridGap => {\n if (!gap) return \"md\"\n\n if (typeof gap === \"string\" && [\"xs\", \"sm\", \"md\", \"lg\", \"xl\"].includes(gap)) {\n return gap as GridGap\n }\n\n if (typeof gap === \"number\") {\n if (gap <= 4) return \"xs\"\n if (gap <= 8) return \"sm\"\n if (gap <= 16) return \"md\"\n if (gap <= 24) return \"lg\"\n return \"xl\"\n }\n\n return \"md\"\n}\n\nconst GalleryRoot = React.forwardRef<HTMLDivElement, GalleryProps>(\n (\n {\n columns = 3,\n gap = \"md\",\n rows,\n responsive,\n className,\n styles: stylesProp,\n children,\n ...props\n },\n ref\n ) => {\n const resolved = resolveGalleryStyles(stylesProp)\n\n return (\n <GalleryStylesContext.Provider value={resolved}>\n <Grid\n ref={ref}\n columns={mapColumnsToGrid(columns)}\n gap={mapGapToGrid(gap)}\n rows={rows}\n responsive={responsive}\n className={cn(\"gallery\", className, resolved.root)}\n {...props}\n >\n {children}\n </Grid>\n </GalleryStylesContext.Provider>\n )\n }\n)\nGalleryRoot.displayName = \"Gallery\"\n\nconst GalleryItem = React.forwardRef<HTMLDivElement, GalleryItemProps>(\n (\n {\n href,\n onPress,\n columnSpan,\n rowSpan,\n orientation = \"vertical\",\n className,\n style,\n styles: stylesProp,\n children,\n ...props\n },\n ref\n ) => {\n const inherited = React.useContext(GalleryStylesContext)\n const resolved = resolveGalleryItemStyles(stylesProp)\n const scopeRef = React.useRef<HTMLDivElement>(null)\n const itemRef = React.useRef<HTMLDivElement>(null)\n const mergedRef = useMergeRefs(itemRef, ref)\n const { focusProps, isFocused, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({})\n const { pressProps, isPressed } = usePress({\n onPress: () => onPress?.(href),\n })\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: scopeRef,\n surfaceSelector: '[data-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n })\n\n const spanStyles: React.CSSProperties = {\n ...(columnSpan ? { gridColumn: `span ${columnSpan}` } : {}),\n ...(rowSpan ? { gridRow: `span ${rowSpan}` } : {}),\n ...style,\n }\n\n const hasAccessibleName =\n props[\"aria-label\"] ||\n props[\"aria-labelledby\"] ||\n typeof props.title === \"string\" ||\n React.Children.count(children) > 0\n\n return (\n <div\n ref={scopeRef}\n className={cn(scopeProps.className, css[\"item-scope\"])}\n style={spanStyles}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n {...mergeProps(focusProps, hoverProps, pressProps, props)}\n ref={mergedRef}\n role=\"button\"\n tabIndex={props.tabIndex ?? 0}\n className={cn(\"gallery\", \"item\", css.item, inherited?.item, resolved.root, className)}\n data-selected={props[\"aria-pressed\"] === true ? \"true\" : undefined}\n data-disabled={props[\"aria-disabled\"] === true ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n data-orientation={orientation}\n data-focus-surface=\"true\"\n aria-label={hasAccessibleName ? props[\"aria-label\"] : \"Gallery item\"}\n >\n {children}\n </div>\n </div>\n )\n }\n)\nGalleryItem.displayName = \"Gallery.Item\"\n\nconst GalleryView = React.forwardRef<HTMLDivElement, GalleryViewProps>(\n ({ aspectRatio = \"16/9\", className, style, styles: stylesProp, children, ...props }, ref) => {\n const inherited = React.useContext(GalleryStylesContext)\n const resolved = resolveGalleryViewStyles(stylesProp)\n\n return (\n <div\n ref={ref}\n className={cn(\"gallery\", \"view\", css.view, inherited?.view, resolved.root, className)}\n style={\n {\n \"--aspect-ratio\": aspectRatio,\n ...style,\n } as React.CSSProperties\n }\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryView.displayName = \"Gallery.View\"\n\nconst GalleryBody = React.forwardRef<HTMLDivElement, GalleryBodyProps>(\n ({ className, styles: stylesProp, children, ...props }, ref) => {\n const inherited = React.useContext(GalleryStylesContext)\n const resolved = resolveGalleryBodyStyles(stylesProp)\n\n return (\n <div\n ref={ref}\n className={cn(\"gallery\", \"body\", css.body, inherited?.body, resolved.root, className)}\n {...props}\n >\n {children}\n </div>\n )\n }\n)\nGalleryBody.displayName = \"Gallery.Body\"\n\nconst Gallery = Object.assign(GalleryRoot, {\n Item: GalleryItem,\n View: GalleryView,\n Body: GalleryBody,\n})\n\nexport { Gallery, GalleryItem, GalleryView, GalleryBody }\n",
|
|
4482
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .item-scope {\n @apply block min-w-0;\n }\n\n .item {\n --border-width: var(--border-width-base, 1px);\n --radius: var(--radius-sm, 0.375rem);\n --transition: 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n --view-width: 200px;\n\n @apply flex flex-col overflow-hidden no-underline cursor-pointer w-full;\n\n color: inherit;\n background: var(--background);\n border: var(--border-width) solid var(--background-border);\n border-radius: var(--radius);\n transition:\n border-color var(--transition),\n transform var(--transition),\n background-color var(--transition),\n box-shadow var(--transition);\n }\n\n .item:focus {\n outline: none;\n }\n\n .item[data-disabled=\"true\"] {\n @apply cursor-not-allowed;\n }\n\n .item[data-hovered=\"true\"] {\n border-color: var(--background-hover-border);\n }\n\n .item[data-pressed=\"true\"] {\n border-color: var(--background-pressed-border, var(--background-hover-border));\n }\n\n .item[data-orientation=\"horizontal\"] {\n @apply flex-row;\n }\n\n .item[data-orientation=\"horizontal\"] .view {\n width: var(--view-width);\n }\n\n .view {\n --aspect-ratio: 16/9;\n --background: transparent;\n\n @apply relative overflow-hidden;\n\n aspect-ratio: var(--aspect-ratio);\n background: var(--background);\n }\n\n .view > img,\n .view > video {\n @apply w-full h-full object-cover;\n }\n\n .body {\n --gap: calc(var(--spacing, 0.25rem) * 1);\n --padding: calc(var(--spacing, 0.25rem) * 3);\n --title-color: var(--foreground);\n --description-color: var(--foreground-muted, var(--foreground));\n\n @apply flex flex-col self-start min-w-0;\n\n gap: var(--gap);\n padding: var(--padding);\n }\n\n .item[data-orientation=\"horizontal\"] .body {\n flex: 1;\n align-self: stretch;\n }\n\n .body > :first-child {\n color: var(--title-color);\n font-weight: var(--font-weight-medium, 500);\n }\n\n .body > :not(:first-child) {\n color: var(--description-color);\n font-size: var(--text-sm, 0.875rem);\n }\n}\n",
|
|
4483
|
+
"cssTypes": "declare const styles: {\n readonly \"item-scope\": string;\n readonly item: string;\n readonly view: string;\n readonly body: string;\n};\n\nexport default styles;\n"
|
|
4359
4484
|
},
|
|
4360
4485
|
"grid": {
|
|
4361
4486
|
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Grid.module.css\";\n\ninterface GridStyleSlots {\n root?: StyleValue;\n}\n\ntype GridStylesProp = StylesProp<GridStyleSlots>;\n\nconst resolveGridBaseStyles = createStylesResolver(['root'] as const);\n\ntype GridColumns = number | \"auto-fit\" | \"auto-fill\";\ntype GridRows = \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\" | \"auto\" | \"masonry\";\ntype GridGap = \"xs\" | \"sm\" | \"md\" | \"lg\" | \"xl\";\ntype GridJustifyItems = \"start\" | \"end\" | \"center\" | \"stretch\";\ntype GridAlignItems = \"start\" | \"end\" | \"center\" | \"stretch\" | \"baseline\";\ntype GridJustifyContent = \"start\" | \"end\" | \"center\" | \"stretch\" | \"space-between\" | \"space-around\" | \"space-evenly\";\ntype GridAlignContent = \"start\" | \"end\" | \"center\" | \"stretch\" | \"space-between\" | \"space-around\" | \"space-evenly\";\ntype GridAutoFlow = \"row\" | \"column\" | \"row-dense\" | \"column-dense\";\ntype GridTemplateColumns = GridColumns | (string & {});\n\ntype ResponsiveValue<T> = { sm?: T; md?: T; lg?: T; xl?: T };\n\nexport interface GridProps extends React.HTMLAttributes<HTMLDivElement> {\n /** Grid template columns value, or responsive object per breakpoint */\n columns?: GridTemplateColumns | ResponsiveValue<GridTemplateColumns>;\n /** Number of grid rows, or responsive object per breakpoint */\n rows?: GridRows | ResponsiveValue<GridRows>;\n /** Gap between all grid cells, or responsive object per breakpoint */\n gap?: GridGap | ResponsiveValue<GridGap>;\n /** Override gap between rows only */\n rowGap?: GridGap;\n /** Override gap between columns only */\n columnGap?: GridGap;\n /** Horizontal alignment of items within their cells */\n justifyItems?: GridJustifyItems;\n /** Vertical alignment of items within their cells */\n alignItems?: GridAlignItems;\n /** Horizontal distribution of the grid within its container */\n justifyContent?: GridJustifyContent;\n /** Vertical distribution of the grid rows within its container */\n alignContent?: GridAlignContent;\n /** Direction items are auto-placed when no explicit placement is set */\n autoFlow?: GridAutoFlow;\n /** Wraps the grid in a container query parent for breakpoint-aware responsiveness */\n responsive?: boolean;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GridStylesProp;\n}\n\nconst isResponsive = <T,>(v: unknown): v is ResponsiveValue<T> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\n\nconst colsToTpl = (c: GridTemplateColumns): string => {\n if (c === \"auto-fit\") return \"repeat(auto-fit, minmax(200px, 1fr))\";\n if (c === \"auto-fill\") return \"repeat(auto-fill, minmax(200px, 1fr))\";\n if (typeof c === \"number\") return `repeat(${c}, 1fr)`;\n return c;\n};\n\nconst rowsToTpl = (r: GridRows): string => {\n if (r === \"masonry\" || r === \"auto\") return r;\n return `repeat(${r}, auto)`;\n};\n\nconst gapVal: Record<GridGap, string> = {\n xs: \"calc(var(--spacing, 0.25rem) * 1)\",\n sm: \"calc(var(--spacing, 0.25rem) * 2)\",\n md: \"calc(var(--spacing, 0.25rem) * 4)\",\n lg: \"calc(var(--spacing, 0.25rem) * 6)\",\n xl: \"calc(var(--spacing, 0.25rem) * 8)\",\n};\n\nconst flowVal: Record<GridAutoFlow, string> = {\n row: \"row\",\n column: \"column\",\n \"row-dense\": \"row dense\",\n \"column-dense\": \"column dense\",\n};\n\nconst Grid = React.forwardRef<HTMLDivElement, GridProps>(\n (\n {\n className,\n style,\n columns = 3,\n rows = \"auto\",\n gap = \"md\",\n rowGap,\n columnGap,\n justifyItems = \"stretch\",\n alignItems = \"stretch\",\n justifyContent = \"start\",\n alignContent = \"start\",\n autoFlow = \"row\",\n responsive = false,\n styles,\n children,\n ...props\n },\n ref\n ) => {\n const resolved = resolveGridBaseStyles(styles);\n const responsiveCols = isResponsive<GridTemplateColumns>(columns);\n const responsiveRows = isResponsive<GridRows>(rows);\n const responsiveGap = isResponsive<GridGap>(gap);\n const needsContainer = responsiveCols || responsiveRows || responsiveGap || responsive;\n\n const vars: Record<string, string> = {};\n\n if (responsiveCols) {\n const rc = columns as ResponsiveValue<GridTemplateColumns>;\n if (rc.sm) vars[\"--grid-tpl-sm\"] = colsToTpl(rc.sm);\n if (rc.md) vars[\"--grid-tpl-md\"] = colsToTpl(rc.md);\n if (rc.lg) vars[\"--grid-tpl-lg\"] = colsToTpl(rc.lg);\n if (rc.xl) vars[\"--grid-tpl-xl\"] = colsToTpl(rc.xl);\n } else {\n vars[\"--grid-tpl\"] = colsToTpl(columns as GridTemplateColumns);\n }\n\n if (responsiveRows) {\n const rr = rows as ResponsiveValue<GridRows>;\n if (rr.sm) vars[\"--grid-rows-sm\"] = rowsToTpl(rr.sm);\n if (rr.md) vars[\"--grid-rows-md\"] = rowsToTpl(rr.md);\n if (rr.lg) vars[\"--grid-rows-lg\"] = rowsToTpl(rr.lg);\n if (rr.xl) vars[\"--grid-rows-xl\"] = rowsToTpl(rr.xl);\n } else {\n vars[\"--grid-rows\"] = rowsToTpl(rows as GridRows);\n }\n\n if (responsiveGap) {\n const rg = gap as ResponsiveValue<GridGap>;\n if (rg.sm) vars[\"--grid-gap-sm\"] = gapVal[rg.sm];\n if (rg.md) vars[\"--grid-gap-md\"] = gapVal[rg.md];\n if (rg.lg) vars[\"--grid-gap-lg\"] = gapVal[rg.lg];\n if (rg.xl) vars[\"--grid-gap-xl\"] = gapVal[rg.xl];\n } else {\n vars[\"--grid-gap\"] = gapVal[gap as GridGap];\n }\n\n if (rowGap) vars[\"--grid-row-gap\"] = gapVal[rowGap];\n if (columnGap) vars[\"--grid-col-gap\"] = gapVal[columnGap];\n\n vars[\"--grid-ji\"] = justifyItems;\n vars[\"--grid-ai\"] = alignItems;\n vars[\"--grid-jc\"] = justifyContent;\n vars[\"--grid-ac\"] = alignContent;\n vars[\"--grid-flow\"] = flowVal[autoFlow];\n\n const gridClasses = cn(\n css.grid,\n responsiveCols && css[\"responsive-cols\"],\n responsiveGap && css[\"responsive-gap\"],\n responsiveRows && css[\"responsive-rows\"],\n rowGap && css[\"has-row-gap\"],\n columnGap && css[\"has-col-gap\"],\n );\n\n if (needsContainer) {\n return (\n <div\n ref={ref}\n className={cn(css.container, className, resolved.root)}\n style={style}\n {...props}\n >\n <div className={gridClasses} style={vars as React.CSSProperties}>\n {children}\n </div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(gridClasses, className, resolved.root)}\n style={{ ...vars, ...style } as React.CSSProperties}\n {...props}\n >\n {children}\n </div>\n );\n }\n);\n\nGrid.displayName = \"Grid\";\n\nexport { Grid };\n",
|
|
@@ -4363,14 +4488,14 @@ export const generatedSourceCode = {
|
|
|
4363
4488
|
"cssTypes": "declare const styles: {\n readonly grid: string;\n readonly container: string;\n readonly \"responsive-cols\": string;\n readonly \"responsive-gap\": string;\n readonly \"responsive-rows\": string;\n readonly \"has-row-gap\": string;\n readonly \"has-col-gap\": string;\n};\n\nexport default styles;\n"
|
|
4364
4489
|
},
|
|
4365
4490
|
"group": {
|
|
4366
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { Button, type ButtonProps } from \"../Button\"\nimport { Input, type InputProps } from \"../Input\"\nimport { Select, type SelectProps } from \"../Select\"\nimport css from \"./Group.module.css\"\n\ntype Orientation = \"horizontal\" | \"vertical\"\ntype Spacing = \"none\" | \"xs\" | \"sm\"\ntype Variant = \"primary\" | \"secondary\" | \"outline\" | \"ghost\"\n\ntype GroupItemStyles = {\n first?: StyleValue\n last?: StyleValue\n divider?: StyleValue\n grow?: StyleValue\n}\n\nexport interface GroupStyleSlots {\n root?: StyleValue;\n item?: StyleValue | GroupItemStyles;\n button?: StyleValue;\n input?: StyleValue;\n select?: StyleValue;\n}\n\nexport type GroupStylesProp = StylesProp<GroupStyleSlots>;\n\nexport interface GroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controls the axis that children are arranged along */\n orientation?: Orientation\n /** Controls the gap between group items */\n spacing?: Spacing\n /** Controls the shared visual style applied to group items */\n variant?: Variant\n /** Whether all items in the group are non-interactive */\n isDisabled?: boolean\n /** The currently active button value for toggle group behavior */\n value?: string\n /** Called when a button with a value prop is pressed */\n onChange?: (value: string) => void\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GroupStylesProp\n}\n\ninterface GroupContextValue {\n isInGroup: boolean\n groupVariant: Variant\n groupOrientation: Orientation\n groupSpacing: Spacing\n groupIsDisabled: boolean\n groupValue?: string\n groupOnChange?: (value: string) => void\n groupStyles: ReturnType<typeof resolveGroupStyles>\n}\n\n// Context\nconst GroupContext = React.createContext<GroupContextValue | null>(null)\n\nfunction useGroupContext() {\n const context = React.useContext(GroupContext)\n if (!context) {\n throw new Error(\"Group sub-components must be used within Group\")\n }\n return context\n}\n\nconst resolveGroupBaseStyles = createStylesResolver([\n \"root\",\n \"item\",\n \"itemFirst\",\n \"itemLast\",\n \"itemDivider\",\n \"itemGrow\",\n \"button\",\n \"input\",\n \"select\",\n] as const)\n\nfunction resolveGroupStyles(styles: GroupStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveGroupBaseStyles(styles)\n const { root, item, button, input, select } = styles\n\n let itemResolved: StyleValue | undefined\n let itemFirst: StyleValue | undefined\n let itemLast: StyleValue | undefined\n let itemDivider: StyleValue | undefined\n let itemGrow: StyleValue | undefined\n\n if (item) {\n if (typeof item === \"string\" || Array.isArray(item)) {\n itemResolved = item\n itemFirst = item\n itemLast = item\n itemDivider = item\n itemGrow = item\n } else {\n itemFirst = item.first\n itemLast = item.last\n itemDivider = item.divider\n itemGrow = item.grow\n }\n }\n\n return resolveGroupBaseStyles({\n root,\n item: itemResolved,\n itemFirst,\n itemLast,\n itemDivider,\n itemGrow,\n button,\n input,\n select,\n })\n}\n\n// Variant and orientation maps\nconst orientationMap: Record<Orientation, string> = {\n horizontal: css.horizontal,\n vertical: css.vertical,\n}\n\nconst spacingMap: Record<Spacing, string> = {\n none: css.none,\n xs: css.xs,\n sm: css.sm,\n}\n\nconst variantMap: Record<Variant, string | undefined> = {\n primary: undefined,\n secondary: undefined,\n outline: undefined,\n ghost: css.ghost,\n}\n\n// Detect Divider elements by checking for separator role, orientation prop, or displayName\nfunction isDivider(child: React.ReactNode): boolean {\n if (!React.isValidElement(child)) return false\n const props = (child.props || {}) as Record<string, unknown>\n const type = child.type as any\n return props.role === \"separator\" || \"orientation\" in props || type?.displayName === \"Divider\"\n}\n\n// Root component\n/** Button group that groups related buttons together */\nconst GroupRoot = React.forwardRef<HTMLDivElement, GroupProps>(\n (\n {\n className,\n orientation = \"horizontal\",\n spacing = \"none\",\n variant = \"primary\",\n children,\n isDisabled = false,\n value,\n onChange,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const childrenArray = React.Children.toArray(children).filter(\n (child) => child !== null && child !== undefined\n )\n\n const resolved = resolveGroupStyles(stylesProp)\n\n const contextValue: GroupContextValue = {\n isInGroup: true,\n groupVariant: variant,\n groupOrientation: orientation,\n groupSpacing: spacing,\n groupIsDisabled: isDisabled,\n groupValue: value,\n groupOnChange: onChange,\n groupStyles: resolved,\n }\n\n return (\n <GroupContext.Provider value={contextValue}>\n <div\n ref={ref}\n className={cn(\n 'group',\n orientation,\n variant,\n css.group,\n orientationMap[orientation],\n spacingMap[spacing],\n variantMap[variant],\n resolved.root,\n className\n )}\n role=\"group\"\n aria-disabled={isDisabled || undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n {...props}\n >\n {childrenArray.map((child, index) => {\n const isFirst = index === 0\n const isLast = index === childrenArray.length - 1\n const isDividerChild = isDivider(child)\n\n // Extract layout-related classes from child to apply to the item wrapper\n const childProps = React.isValidElement(child) ? (child.props as any) : {}\n const childClassName = childProps.className || \"\"\n const shouldGrow = childClassName.includes('w-full') || childClassName.includes('flex-1')\n return (\n <div\n key={`item-${index}`}\n className={cn(\n 'item',\n css.item,\n isFirst && resolved.itemFirst,\n isLast && resolved.itemLast,\n isDividerChild && css.divider,\n isDividerChild && resolved.itemDivider,\n shouldGrow && css.grow,\n shouldGrow && resolved.itemGrow,\n resolved.item,\n )}\n >\n {child}\n </div>\n )\n })}\n </div>\n </GroupContext.Provider>\n )\n }\n)\nGroupRoot.displayName = \"Group\"\n\n// Group.Button component\ninterface GroupButtonProps extends ButtonProps {\n /** Whether this button is in an active/pressed state */\n active?: boolean\n /** Identifier used for toggle group behavior when Group has value/onChange */\n value?: string\n}\n\n/** Button styled to merge seamlessly with adjacent group items */\nconst GroupButton = React.forwardRef<HTMLButtonElement, GroupButtonProps>(\n ({ active, value, variant, className, onPress, ...restProps }, ref) => {\n const context = useGroupContext()\n // Merge disabled state from group context\n const isDisabled = restProps.isDisabled ?? context.groupIsDisabled\n\n // Derive active and onPress from toggle group context when value is provided\n const isActive = value !== undefined && context.groupValue !== undefined ? value === context.groupValue : active\n const handlePress = value !== undefined && context.groupOnChange !== undefined ? () => context.groupOnChange!(value) : onPress\n\n let buttonVariant = variant\n if (variant === undefined) {\n if (context.groupVariant === \"ghost\") {\n buttonVariant = isActive ? \"default\" : \"ghost\"\n } else {\n buttonVariant = \"ghost\"\n }\n }\n\n const buttonProps = {\n ...restProps,\n onPress: handlePress,\n variant: buttonVariant,\n isDisabled,\n \"data-selected\": isActive ? \"true\" : \"false\",\n className: cn(\n \"group\",\n \"button\",\n css.button,\n context.groupStyles.button,\n className\n ),\n }\n\n return <Button ref={ref} {...buttonProps} />\n }\n)\nGroupButton.displayName = \"Group.Button\"\n\n// Group.Input component\ninterface GroupInputProps extends InputProps { }\n\n/** Input field integrated into the button group */\nconst GroupInput = React.forwardRef<HTMLInputElement, GroupInputProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(\"group\", \"input\", css.input, context.groupStyles.input, className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInput.displayName = \"Group.Input\"\n\n// Group.InputWrapper component - preserves Input styling (for use with ghost variant)\ninterface GroupInputWrapperProps extends InputProps { }\n\n/** Input variant that preserves Input styling within the group */\nconst GroupInputWrapper = React.forwardRef<HTMLInputElement, GroupInputWrapperProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div className={cn(\"group\", \"input\", css.input, context.groupStyles.input, className)}>\n <Input\n ref={ref}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInputWrapper.displayName = \"Group.InputWrapper\"\n\n// Group.Select component\ninterface GroupSelectProps extends SelectProps<any> { }\n\n/** Select dropdown integrated into the button group */\nconst GroupSelect = React.forwardRef<HTMLDivElement, GroupSelectProps>(\n ({ className, isDisabled, ...props }, ref) => {\n const context = useGroupContext()\n\n // Merge disabled state from group context\n const disabled = isDisabled ?? context.groupIsDisabled\n\n return (\n <Select\n ref={ref}\n {...props}\n isDisabled={disabled}\n className={cn(\"group\", \"select\", css.select, context.groupStyles.select, className)}\n />\n )\n }\n)\nGroupSelect.displayName = \"Group.Select\"\n\n// Assemble compound component\nconst Group = Object.assign(GroupRoot, {\n Button: GroupButton,\n Input: GroupInput,\n InputWrapper: GroupInputWrapper,\n Select: GroupSelect,\n})\n\nexport { Group, GroupContext }\n",
|
|
4367
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --layout-radius-size: calc(var(--spacing) * 1.5);\n --layout-padding-size: var(--layout-radius-size);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-border-width: var(--border-width-base, 1px);\n --background-inner-radius: calc(var(--background-radius) - var(--background-border-width));\n --layout-text-height: calc(0.8em * var(--leading-tight, 1.25));\n --layout-vertical-spacing: calc(var(--spacing) * 4);\n --layout-border-height: calc(var(--background-border-width) * 2);\n --layout-padding-height: calc(var(--layout-padding-size) * 2);\n --layout-control-height: calc(\n var(--layout-text-height) +\n var(--layout-vertical-spacing) +\n var(--layout-border-height)\n );\n --item-height: max(\n calc(\n var(--layout-control-height) -\n var(--layout-padding-height) -\n var(--layout-border-height)\n ),\n 0px\n );\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground, currentColor);\n background-color: var(--background, transparent);\n border: var(--background-border-width) solid var(--background-border, transparent);\n border-radius: var(--background-radius);\n padding: var(--layout-padding-size);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--layout-control-height);\n\n .item.divider {\n margin-block: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n height: 100%;\n }\n }\n\n &.vertical {\n @apply flex-col;\n\n .item .button {\n @apply w-full;\n }\n\n .item.divider {\n margin-inline: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n width: 100%;\n }\n }\n\n &.none {\n --layout-padding-size: 0px;\n @apply gap-0;\n }\n\n &.xs {\n --layout-radius-size: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n\n &.sm {\n --layout-radius-size: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n
|
|
4368
|
-
"cssTypes": "declare const styles: {\n group: string;\n horizontal: string;\n vertical: string;\n none: string;\n xs: string;\n sm: string;\n ghost: string;\n item: string;\n grow: string;\n divider: string;\n button: string;\n input: string;\n select: string;\n};\n\nexport default styles;\n"
|
|
4491
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\"\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\"\nimport { Button, type ButtonProps } from \"../Button\"\nimport { Expand, type ExpandProps } from \"../Expand\"\nimport { Input, type InputProps } from \"../Input\"\nimport { Select, type SelectProps } from \"../Select\"\nimport css from \"./Group.module.css\"\n\ntype Orientation = \"horizontal\" | \"vertical\"\ntype Spacing = \"none\" | \"xs\" | \"sm\"\n\ntype GroupItemStyles = {\n first?: StyleValue\n last?: StyleValue\n divider?: StyleValue\n grow?: StyleValue\n}\n\nexport interface GroupStyleSlots {\n root?: StyleValue;\n item?: StyleValue | GroupItemStyles;\n button?: StyleValue;\n input?: StyleValue;\n select?: StyleValue;\n expand?: StyleValue;\n}\n\nexport type GroupStylesProp = StylesProp<GroupStyleSlots>;\n\nexport interface GroupProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {\n /** Controls the axis that children are arranged along */\n orientation?: Orientation\n /** Controls the gap between group items */\n spacing?: Spacing\n /** Whether all items in the group are non-interactive */\n isDisabled?: boolean\n /** The currently active button value for toggle group behavior */\n value?: string\n /** Called when a button with a value prop is pressed */\n onChange?: (value: string) => void\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: GroupStylesProp\n}\n\ninterface GroupContextValue {\n isInGroup: boolean\n groupOrientation: Orientation\n groupSpacing: Spacing\n groupIsDisabled: boolean\n groupValue?: string\n groupOnChange?: (value: string) => void\n groupStyles: ReturnType<typeof resolveGroupStyles>\n registerInput?: (containerRef: React.RefObject<HTMLDivElement | null>, inputRef: React.RefObject<HTMLInputElement | null>) => void\n unregisterInput?: (containerRef: React.RefObject<HTMLDivElement | null>) => void\n activateInput?: () => void\n registerFocusableSurface?: (ref: React.RefObject<HTMLElement | null>) => void\n unregisterFocusableSurface?: (ref: React.RefObject<HTMLElement | null>) => void\n}\n\n// Context\nconst GroupContext = React.createContext<GroupContextValue | null>(null)\n\nfunction useGroupContext() {\n const context = React.useContext(GroupContext)\n if (!context) {\n throw new Error(\"Group sub-components must be used within Group\")\n }\n return context\n}\n\nconst resolveGroupBaseStyles = createStylesResolver([\n \"root\",\n \"item\",\n \"itemFirst\",\n \"itemLast\",\n \"itemDivider\",\n \"itemGrow\",\n \"button\",\n \"input\",\n \"select\",\n \"expand\",\n] as const)\n\nfunction resolveGroupStyles(styles: GroupStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveGroupBaseStyles(styles)\n const { root, item, button, input, select, expand } = styles\n\n let itemResolved: StyleValue | undefined\n let itemFirst: StyleValue | undefined\n let itemLast: StyleValue | undefined\n let itemDivider: StyleValue | undefined\n let itemGrow: StyleValue | undefined\n\n if (item) {\n if (typeof item === \"string\" || Array.isArray(item)) {\n itemResolved = item\n itemFirst = item\n itemLast = item\n itemDivider = item\n itemGrow = item\n } else {\n itemFirst = item.first\n itemLast = item.last\n itemDivider = item.divider\n itemGrow = item.grow\n }\n }\n\n return resolveGroupBaseStyles({\n root,\n item: itemResolved,\n itemFirst,\n itemLast,\n itemDivider,\n itemGrow,\n button,\n input,\n select,\n expand,\n })\n}\n\n// Orientation and spacing maps\nconst orientationMap: Record<Orientation, string> = {\n horizontal: css.horizontal,\n vertical: css.vertical,\n}\n\nconst spacingMap: Record<Spacing, string> = {\n none: css.none,\n xs: css.xs,\n sm: css.sm,\n}\n\n// Detect Divider elements by checking for separator role, orientation prop, or displayName\nfunction isDivider(child: React.ReactNode): boolean {\n if (!React.isValidElement(child)) return false\n const props = (child.props || {}) as Record<string, unknown>\n const type = child.type as any\n return props.role === \"separator\" || \"orientation\" in props || type?.displayName === \"Divider\"\n}\n\nfunction isHTMLElement(value: Element | null): value is HTMLElement {\n return value instanceof HTMLElement\n}\n\n// Root component\n/** Button group that groups related buttons together */\nconst GroupRoot = React.forwardRef<HTMLDivElement, GroupProps>(\n (\n {\n className,\n orientation = \"horizontal\",\n spacing = \"none\",\n children,\n isDisabled = false,\n value,\n onChange,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const scopeRef = React.useRef<HTMLDivElement>(null)\n const groupRef = React.useRef<HTMLDivElement>(null)\n const inputsRef = React.useRef<Map<React.RefObject<HTMLDivElement | null>, React.RefObject<HTMLInputElement | null>>>(new Map())\n const focusableSurfacesRef = React.useRef<React.RefObject<HTMLElement | null>[]>([])\n\n const childrenArray = React.Children.toArray(children).filter(\n (child) => child !== null && child !== undefined\n )\n\n const resolved = resolveGroupStyles(stylesProp)\n const mergedRef = useMergeRefs<HTMLDivElement>(ref, groupRef)\n\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: groupRef,\n surfaceSelector: '[data-focus-surface=\"true\"]',\n radiusSource: \"item\",\n dependencies: [childrenArray.length, orientation, spacing],\n })\n\n const registerInput = React.useCallback((containerRef: React.RefObject<HTMLDivElement | null>, inputRef: React.RefObject<HTMLInputElement | null>) => {\n inputsRef.current.set(containerRef, inputRef)\n }, [])\n\n const unregisterInput = React.useCallback((containerRef: React.RefObject<HTMLDivElement | null>) => {\n inputsRef.current.delete(containerRef)\n }, [])\n\n const activateInput = React.useCallback(() => {\n const firstEntry = Array.from(inputsRef.current.entries())[0]\n if (firstEntry) {\n const [containerRef, inputRef] = firstEntry\n if (containerRef.current && inputRef.current) {\n // Focus the actual input for keyboard interaction\n inputRef.current.focus()\n\n // Force focus-visible state by finding the input container inside the wrapper\n // The Input component renders a container div with data-input-focus-surface\n const inputContainer = containerRef.current.querySelector('[data-input-focus-surface=\"true\"]') as HTMLElement\n if (inputContainer) {\n inputContainer.setAttribute('data-focus-visible', 'true')\n inputContainer.setAttribute('data-focused', 'true')\n }\n }\n }\n }, [])\n\n const registerFocusableSurface = React.useCallback((ref: React.RefObject<HTMLElement | null>) => {\n focusableSurfacesRef.current.push(ref)\n }, [])\n\n const unregisterFocusableSurface = React.useCallback((ref: React.RefObject<HTMLElement | null>) => {\n focusableSurfacesRef.current = focusableSurfacesRef.current.filter(r => r !== ref)\n }, [])\n\n\n const handleGroupKeyDown = React.useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {\n if (e.key !== \"ArrowRight\" && e.key !== \"ArrowLeft\" && e.key !== \"ArrowDown\" && e.key !== \"ArrowUp\") {\n return\n }\n\n const focusedElement = document.activeElement\n if (!focusedElement || !groupRef.current?.contains(focusedElement as Node)) {\n return\n }\n\n const focusableElements = focusableSurfacesRef.current\n .map(ref => ref.current)\n .filter((el): el is HTMLElement => el !== null && !!groupRef.current?.contains(el))\n\n if (focusableElements.length === 0) {\n return\n }\n\n const currentIndex = focusableElements.findIndex(el => el.contains(focusedElement as Node))\n if (currentIndex === -1) {\n return\n }\n\n const isHorizontal = orientation === \"horizontal\"\n const isNavigatingInline = (e.key === \"ArrowRight\" || e.key === \"ArrowLeft\") && isHorizontal\n const isNavigatingBlock = (e.key === \"ArrowDown\" || e.key === \"ArrowUp\") && !isHorizontal\n\n if (!isNavigatingInline && !isNavigatingBlock) {\n return\n }\n\n e.preventDefault()\n\n let nextIndex = currentIndex\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n nextIndex = (currentIndex + 1) % focusableElements.length\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n nextIndex = currentIndex === 0 ? focusableElements.length - 1 : currentIndex - 1\n }\n\n const nextElement = focusableElements[nextIndex]\n if (nextElement) {\n // Try to focus an interactive element within the surface\n const focusableChild = nextElement.querySelector('button, input, [role=\"button\"]') as HTMLElement | null\n if (focusableChild) {\n focusableChild.focus()\n } else {\n nextElement.focus()\n }\n }\n }, [orientation])\n\n const contextValue: GroupContextValue = {\n isInGroup: true,\n groupOrientation: orientation,\n groupSpacing: spacing,\n groupIsDisabled: isDisabled,\n groupValue: value,\n groupOnChange: onChange,\n groupStyles: resolved,\n registerInput,\n unregisterInput,\n activateInput,\n registerFocusableSurface,\n unregisterFocusableSurface,\n }\n\n return (\n <GroupContext.Provider value={contextValue}>\n <div ref={scopeRef} className={cn(\"group-scope\", scopeProps.className)}>\n <div {...indicatorProps} />\n <div\n ref={mergedRef}\n className={cn(\n 'group',\n orientation,\n css.group,\n orientationMap[orientation],\n spacingMap[spacing],\n resolved.root,\n className\n )}\n role=\"group\"\n aria-disabled={isDisabled || undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n onKeyDown={handleGroupKeyDown}\n {...props}\n >\n {childrenArray.map((child, index) => {\n const isFirst = index === 0\n const isLast = index === childrenArray.length - 1\n const isDividerChild = isDivider(child)\n\n // Extract layout-related classes from child to apply to the item wrapper\n const childProps = React.isValidElement(child) ? (child.props as any) : {}\n const childClassName = childProps.className || \"\"\n const shouldGrow = childClassName.includes('w-full') || childClassName.includes('flex-1')\n return (\n <div\n key={`item-${index}`}\n className={cn(\n 'item',\n css.item,\n isFirst && resolved.itemFirst,\n isLast && resolved.itemLast,\n isDividerChild && css.divider,\n isDividerChild && resolved.itemDivider,\n shouldGrow && css.grow,\n shouldGrow && resolved.itemGrow,\n resolved.item,\n )}\n >\n {child}\n </div>\n )\n })}\n </div>\n </div>\n </GroupContext.Provider>\n )\n }\n)\nGroupRoot.displayName = \"Group\"\n\n// Group.Button component\ninterface GroupButtonProps extends ButtonProps {\n /** Whether this button is in an active/pressed state */\n active?: boolean\n /** Identifier used for toggle group behavior when Group has value/onChange */\n value?: string\n}\n\n/** Button styled to merge seamlessly with adjacent group items */\nconst GroupButton = React.forwardRef<HTMLButtonElement, GroupButtonProps>(\n ({ active, value, variant, className, onPress, onPointerDown, ...restProps }, ref) => {\n const context = useGroupContext()\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const mergedRef = useMergeRefs(buttonRef, ref)\n\n // Merge disabled state from group context\n const isDisabled = restProps.isDisabled ?? context.groupIsDisabled\n\n // Derive active and onPress from toggle group context when value is provided\n const isActive = value !== undefined && context.groupValue !== undefined ? value === context.groupValue : active\n const baseHandlePress = value !== undefined && context.groupOnChange !== undefined ? () => context.groupOnChange!(value) : onPress\n\n const handlePress = React.useCallback((e: { target: EventTarget | null }) => {\n baseHandlePress?.(e)\n }, [baseHandlePress])\n\n // Activate input on pointer down to avoid focus ring flicker\n const handlePointerDown = React.useCallback((e: React.PointerEvent<HTMLButtonElement>) => {\n if (!isDisabled) {\n // Pre-activate the input before button loses focus\n context.activateInput?.()\n }\n onPointerDown?.(e)\n }, [context, isDisabled, onPointerDown])\n\n React.useEffect(() => {\n context.registerFocusableSurface?.(buttonRef)\n return () => {\n context.unregisterFocusableSurface?.(buttonRef)\n }\n }, [context])\n\n const buttonVariant = variant ?? \"ghost\"\n\n const buttonProps = {\n ...restProps,\n onPress: handlePress,\n onPointerDown: handlePointerDown,\n variant: buttonVariant,\n isDisabled,\n \"data-focus-surface\": \"true\",\n \"data-selected\": isActive ? \"true\" : \"false\",\n className: cn(\n \"group\",\n \"button\",\n css.button,\n context.groupStyles.button,\n className\n ),\n }\n\n return <Button ref={mergedRef} {...buttonProps} />\n }\n)\nGroupButton.displayName = \"Group.Button\"\n\n// Group.Input component\ninterface GroupInputProps extends InputProps { }\n\n/** Input field integrated into the button group */\nconst GroupInput = React.forwardRef<HTMLInputElement, GroupInputProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n const inputRef = React.useRef<HTMLInputElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const mergedRef = useMergeRefs(ref, inputRef)\n\n // Register and unregister the input ref with the group context\n React.useEffect(() => {\n context.registerInput?.(containerRef, inputRef)\n return () => {\n context.unregisterInput?.(containerRef)\n }\n }, [context])\n\n React.useEffect(() => {\n context.registerFocusableSurface?.(containerRef)\n return () => {\n context.unregisterFocusableSurface?.(containerRef)\n }\n }, [context])\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div\n ref={containerRef}\n className={cn(\"group\", \"input\", css.input, context.groupStyles.input, className)}\n data-focus-surface=\"true\"\n >\n <Input\n ref={mergedRef}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInput.displayName = \"Group.Input\"\n\n// Group.InputWrapper component - preserves Input styling (for use with ghost variant)\ninterface GroupInputWrapperProps extends InputProps { }\n\n/** Input variant that preserves Input styling within the group */\nconst GroupInputWrapper = React.forwardRef<HTMLInputElement, GroupInputWrapperProps>(\n ({ className, disabled, ...props }, ref) => {\n const context = useGroupContext()\n const inputRef = React.useRef<HTMLInputElement>(null)\n const containerRef = React.useRef<HTMLDivElement>(null)\n const mergedRef = useMergeRefs(ref, inputRef)\n\n // Register and unregister the input ref with the group context\n React.useEffect(() => {\n context.registerInput?.(containerRef, inputRef)\n return () => {\n context.unregisterInput?.(containerRef)\n }\n }, [context])\n\n React.useEffect(() => {\n context.registerFocusableSurface?.(containerRef)\n return () => {\n context.unregisterFocusableSurface?.(containerRef)\n }\n }, [context])\n\n // Merge disabled state from group context\n const inputDisabled = disabled ?? context.groupIsDisabled\n\n return (\n <div\n ref={containerRef}\n className={cn(\"group\", \"input\", css.input, context.groupStyles.input, className)}\n data-focus-surface=\"true\"\n >\n <Input\n ref={mergedRef}\n {...props}\n disabled={inputDisabled}\n className=\"w-full\"\n />\n </div>\n )\n }\n)\nGroupInputWrapper.displayName = \"Group.InputWrapper\"\n\n// Group.Select component\ninterface GroupSelectProps extends SelectProps<any> { }\n\n/** Select dropdown integrated into the button group */\nconst GroupSelect = React.forwardRef<HTMLDivElement, GroupSelectProps>(\n ({ className, isDisabled, ...props }, ref) => {\n const context = useGroupContext()\n const selectRef = React.useRef<HTMLDivElement>(null)\n const mergedRef = useMergeRefs(selectRef, ref)\n\n // Merge disabled state from group context\n const disabled = isDisabled ?? context.groupIsDisabled\n\n React.useEffect(() => {\n context.registerFocusableSurface?.(selectRef)\n return () => {\n context.unregisterFocusableSurface?.(selectRef)\n }\n }, [context])\n\n return (\n <Select\n ref={mergedRef}\n {...props}\n isDisabled={disabled}\n data-focus-surface=\"true\"\n className={cn(\"group\", \"select\", css.select, context.groupStyles.select, className)}\n />\n )\n }\n)\nGroupSelect.displayName = \"Group.Select\"\n\n// Group.Expand component\ninterface GroupExpandProps extends ExpandProps { }\n\n/** Expand primitive integrated into the group */\nconst GroupExpand = React.forwardRef<HTMLDivElement, GroupExpandProps>(\n ({ className, isDisabled, styles: stylesProp, ...props }, ref) => {\n const context = useGroupContext()\n const surfaceRef = React.useRef<HTMLDivElement>(null)\n const disabled = isDisabled ?? context.groupIsDisabled\n const surfaceStyles = typeof stylesProp === \"string\" || Array.isArray(stylesProp) ? stylesProp : undefined\n const expandStyles = surfaceStyles ? undefined : stylesProp\n\n React.useEffect(() => {\n context.registerFocusableSurface?.(surfaceRef)\n return () => {\n context.unregisterFocusableSurface?.(surfaceRef)\n }\n }, [context])\n\n return (\n <div\n ref={surfaceRef}\n data-focus-surface=\"true\"\n className={cn(\"group\", \"expand\", css.expand, context.groupStyles.expand, surfaceStyles)}\n >\n <Expand\n ref={ref}\n {...props}\n className={className}\n styles={expandStyles}\n isDisabled={disabled}\n />\n </div>\n )\n }\n)\nGroupExpand.displayName = \"Group.Expand\"\n\n// Assemble compound component\nconst Group = Object.assign(GroupRoot, {\n Button: GroupButton,\n Input: GroupInput,\n InputWrapper: GroupInputWrapper,\n Select: GroupSelect,\n Expand: GroupExpand,\n})\n\nexport { Group, GroupContext }\n",
|
|
4492
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .group {\n --layout-radius-size: calc(var(--spacing) * 1.5);\n --layout-padding-size: var(--layout-radius-size);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-border-width: var(--border-width-base, 1px);\n --background-inner-radius: calc(var(--background-radius) - var(--background-border-width));\n --layout-text-height: calc(0.8em * var(--leading-tight, 1.25));\n --layout-vertical-spacing: calc(var(--spacing) * 4);\n --layout-border-height: calc(var(--background-border-width) * 2);\n --layout-padding-height: calc(var(--layout-padding-size) * 2);\n --layout-control-height: calc(\n var(--layout-text-height) +\n var(--layout-vertical-spacing) +\n var(--layout-border-height)\n );\n --item-height: max(\n calc(\n var(--layout-control-height) -\n var(--layout-padding-height) -\n var(--layout-border-height)\n ),\n 0px\n );\n\n @apply flex overflow-hidden shrink-0 box-border;\n color: var(--foreground, currentColor);\n background-color: var(--background, transparent);\n border: var(--background-border-width) solid var(--background-border, transparent);\n border-radius: var(--background-radius);\n padding: var(--layout-padding-size);\n\n &.horizontal {\n @apply flex-row items-stretch;\n height: var(--layout-control-height);\n\n .item.divider {\n margin-block: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n height: 100%;\n }\n }\n\n &.vertical {\n @apply flex-col;\n\n .item .button {\n @apply w-full;\n }\n\n .item.divider {\n margin-inline: calc(var(--layout-padding-size) * -1);\n }\n .item.divider > [role=\"separator\"] {\n width: 100%;\n }\n }\n\n &.none {\n --layout-padding-size: 0px;\n @apply gap-0;\n }\n\n &.xs {\n --layout-radius-size: calc(var(--spacing) * 0.875);\n @apply space-x-0.5;\n }\n\n &.sm {\n --layout-radius-size: calc(var(--spacing) * 1.25);\n @apply space-x-1;\n }\n\n }\n\n .item {\n @apply flex items-stretch;\n position: relative;\n isolation: isolate;\n border-radius: var(--group-item-radius, 0);\n overflow: visible;\n\n &.grow {\n flex: 1;\n }\n\n &.divider {\n @apply p-0 shrink-0 flex-none;\n\n > [role=\"separator\"] {\n flex: 0 0 auto;\n }\n }\n }\n\n :is(.button, .input, .select, .expand) {\n height: 100%;\n min-height: var(--item-height);\n position: relative;\n isolation: isolate;\n overflow: visible;\n }\n\n .button {\n @apply flex box-border;\n width: auto;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n\n &[data-selected=\"true\"] {\n @apply relative;\n background-color: var(--button-selected-background, var(--background-800));\n color: var(--button-selected-foreground, var(--foreground-100));\n }\n }\n\n .input {\n @apply flex flex-1 items-stretch overflow-visible;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n\n > [data-ring=\"true\"] {\n border-radius: inherit;\n }\n\n input {\n @apply h-full px-2;\n }\n }\n\n .select {\n @apply flex items-stretch p-0 bg-transparent border-none;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n }\n\n .expand {\n @apply flex items-stretch;\n border-radius: var(--group-item-radius, var(--background-inner-radius));\n }\n\n .expand :global(.expand-scope),\n .expand :global(.expand) {\n @apply flex w-full h-full;\n }\n\n .expand :global(.expand) {\n @apply flex-col;\n border-radius: inherit;\n }\n\n .expand :global(.trigger) {\n @apply min-h-0;\n border-radius: inherit;\n }\n\n .trigger {}\n\n .group {\n .item :is(.button, .select) {\n border: none;\n }\n\n .button[data-selected=\"true\"] {\n font-weight: 500;\n }\n\n .input {\n --border-width-base: 0px;\n --background-border: transparent;\n --background-focused-border: transparent;\n }\n\n &.none {\n .item:not(.divider) {\n overflow: hidden;\n }\n\n :is(.button, .trigger, .select) {\n border-radius: 0;\n --background-radius: 0;\n --background-inner-radius: 0;\n }\n\n .input {\n --radius-sm: 0;\n }\n\n .item:first-child {\n --group-item-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n }\n\n .item:last-child {\n --group-item-radius: 0 var(--background-inner-radius) var(--background-inner-radius) 0;\n }\n\n &.horizontal {\n .item:first-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-left-radius: var(--background-inner-radius);\n border-bottom-left-radius: var(--background-inner-radius);\n }\n\n .item:last-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-right-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n\n .item:last-child .trigger .icon-section {\n border-top-right-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n }\n\n &.vertical {\n .item:first-child {\n --group-item-radius: var(--background-inner-radius) var(--background-inner-radius) 0 0;\n }\n\n .item:last-child {\n --group-item-radius: 0 0 var(--background-inner-radius) var(--background-inner-radius);\n }\n\n .item:first-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-top-left-radius: var(--background-inner-radius);\n border-top-right-radius: var(--background-inner-radius);\n }\n\n .item:last-child :is(\n .button,\n .trigger,\n .input > *,\n .select\n ) {\n border-bottom-left-radius: var(--background-inner-radius);\n border-bottom-right-radius: var(--background-inner-radius);\n }\n }\n }\n\n &:is(.xs, .sm) {\n .item {\n --group-item-radius: var(--background-inner-radius);\n }\n\n :is(.button, .trigger, .select) {\n border-radius: var(--background-inner-radius);\n }\n\n .input {\n --radius-sm: var(--background-inner-radius);\n }\n }\n }\n\n .group [data-ring=\"true\"] {\n --ring-shadow: none;\n --ring-border: transparent;\n --ring-border-visible: transparent;\n }\n\n .group :global(.focus-indicator) {\n display: none;\n }\n\n .group [data-focus-indicator=\"local\"] {\n display: none;\n }\n\n :is(.button[data-focus-visible=\"true\"], .trigger[data-focus-visible=\"true\"]) {\n @apply outline-none;\n box-shadow: none;\n }\n}\n",
|
|
4493
|
+
"cssTypes": "declare const styles: {\n \"focus-scope\": string;\n \"focus-indicator\": string;\n group: string;\n horizontal: string;\n vertical: string;\n none: string;\n xs: string;\n sm: string;\n ghost: string;\n item: string;\n grow: string;\n divider: string;\n button: string;\n input: string;\n select: string;\n expand: string;\n trigger: string;\n};\n\nexport default styles;\n"
|
|
4369
4494
|
},
|
|
4370
4495
|
"input": {
|
|
4371
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { mergeProps, } from \"@react-aria/utils\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport css from \"./Input.module.css\";\n\ntype Variant = \"default\" | \"ghost\";\n\ntype InputIconStyles = {\n left?: StyleValue;\n right?: StyleValue;\n};\n\ntype InputControlsStyles = {\n up?: StyleValue;\n down?: StyleValue;\n};\n\nexport interface InputStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | InputIconStyles;\n controls?: StyleValue | InputControlsStyles;\n}\n\nexport type InputStylesProp = StylesProp<InputStyleSlots>;\n\nexport type InputAction = InputActionDef | React.ReactNode;\ntype InputIconSlots = {\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n};\n\nexport type InputActionDef = {\n icon: React.ReactNode;\n title: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n};\n\ntype InputActionSlots = {\n left?: InputAction[];\n right?: InputAction[];\n};\n\nconst resolveInputBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight', 'controlsUp', 'controlsDown'] as const);\n\nfunction resolveInputStyles(styles: InputStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveInputBaseStyles(styles);\n const { root, icon, controls } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n let controlsUp: StyleValue | undefined;\n let controlsDown: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n if (controls) {\n if (typeof controls === 'string' || Array.isArray(controls)) {\n controlsUp = controls;\n controlsDown = controls;\n } else {\n controlsUp = controls.up;\n controlsDown = controls.down;\n }\n }\n\n return resolveInputBaseStyles({ root, iconLeft, iconRight, controlsUp, controlsDown });\n}\n\nexport interface InputProps extends Omit<ComponentPropsWithoutRef<\"input\">, \"size\"> {\n /** Controls the visual style of the input */\n variant?: Variant;\n /** Whether the input is in an error state */\n error?: boolean;\n /** Icon displayed before the input value by default, or in named prefix/suffix slots */\n icon?: React.ReactNode | InputIconSlots;\n /** Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior. */\n actions?: InputAction[] | InputActionSlots;\n /** Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts. */\n hint?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: InputStylesProp;\n}\n\nfunction isInputIconSlots(icon: InputProps[\"icon\"]): icon is InputIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && (\"prefix\" in icon || \"suffix\" in icon);\n}\n\nfunction resolveInputIcon(icon: InputProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isInputIconSlots(icon)) {\n return icon;\n }\n\n return { prefix: icon };\n}\n\nfunction isInputActionSlots(actions: InputProps[\"actions\"]): actions is InputActionSlots {\n return typeof actions === \"object\" && actions !== null && !Array.isArray(actions) && !React.isValidElement(actions) && (\"left\" in actions || \"right\" in actions);\n}\n\nfunction resolveInputActions(actions: InputProps[\"actions\"]): InputActionSlots {\n if (!actions) {\n return {};\n }\n\n if (isInputActionSlots(actions)) {\n return actions;\n }\n\n return { right: actions };\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n variant = \"default\",\n error = false,\n disabled,\n icon,\n actions,\n hint,\n type = \"text\",\n onFocus,\n onBlur,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const resolvedActions = resolveInputActions(actions);\n const resolvedIcon = resolveInputIcon(icon);\n const leftActions = resolvedActions.left ?? [];\n const rightActions = resolvedActions.right ?? [];\n const hasPrefix = !!resolvedIcon?.prefix;\n const hasSuffix = !!resolvedIcon?.suffix;\n const hasLeftActions = leftActions.length > 0;\n const hasRightActions = rightActions.length > 0;\n const hasHint = hint !== undefined && hint !== null;\n const hasStartAdornment = hasPrefix || hasLeftActions;\n const isNumberType = type === \"number\";\n const [isFocused, setIsFocused] = React.useState(false);\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const mergedRef = useMergedRef(ref, inputRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleSpinClick = (direction: \"up\" | \"down\") => {\n if (!inputRef.current || disabled) return;\n\n const input = inputRef.current;\n\n if (direction === \"up\") {\n input.stepUp();\n } else {\n input.stepDown();\n }\n\n // Dispatch native input event to trigger React onChange handlers\n const event = new Event(\"input\", { bubbles: true });\n input.dispatchEvent(event);\n };\n\n const resolved = resolveInputStyles(stylesProp);\n const hasEndAdornment = hasSuffix || hasRightActions || hasHint || isNumberType;\n const adornmentPadding = \"var(--adornment-offset)\";\n const inputPaddingStyle: React.CSSProperties = {\n ...(hasStartAdornment ? { paddingLeft: adornmentPadding } : {}),\n ...(hasEndAdornment ? { paddingRight: adornmentPadding } : {}),\n };\n\n const renderAction = (action: InputAction, index: number) => {\n const key = React.isValidElement(action) ? index : ((action as InputActionDef).title || index);\n\n return React.isValidElement(action) ? (\n <React.Fragment key={key}>{action}</React.Fragment>\n ) : (\n <Tooltip key={key} content={(action as InputActionDef).title} position=\"top\">\n <button\n type=\"button\"\n className={css.action}\n aria-label={(action as InputActionDef).title}\n onClick={(action as InputActionDef).onClick}\n >\n {(action as InputActionDef).icon}\n </button>\n </Tooltip>\n );\n };\n\n return (\n <div\n className={cn('input', css.container, resolved.root)}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n >\n {hasStartAdornment && (\n <div className={css['start-adornments']} data-start-adornments>\n {hasPrefix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], resolved.iconLeft)}>\n {resolvedIcon?.prefix}\n </div>\n )}\n {hasLeftActions && (\n <div className={css.actions} data-actions data-actions-position=\"left\">\n {leftActions.map(renderAction)}\n </div>\n )}\n </div>\n )}\n <input\n ref={mergedRef}\n type={type}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n className={cn(\n 'input',\n css.input,\n className\n )}\n style={inputPaddingStyle}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...props,\n })}\n />\n {hasEndAdornment && (\n <div className={css['end-adornments']} data-end-adornments>\n {hasSuffix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], resolved.iconRight)}>\n {resolvedIcon?.suffix}\n </div>\n )}\n {hasRightActions && (\n <div className={css.actions} data-actions data-actions-position=\"right\">\n {rightActions.map(renderAction)}\n </div>\n )}\n {hasHint && <span className={css.hint} data-hint>{hint}</span>}\n {isNumberType && (\n <div\n className={(css as any).controls}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'], resolved.controlsUp)}\n onClick={() => handleSpinClick(\"up\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment\"\n >\n <ChevronUp size={12} />\n </button>\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'], resolved.controlsDown)}\n onClick={() => handleSpinClick(\"down\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement\"\n >\n <ChevronDown size={12} />\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n }\n);\n\nInput.displayName = \"Input\";\n",
|
|
4372
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .input {\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n pointer-events: none;\n }\n\n .icon-left {\n @apply relative;\n }\n\n .icon-right {\n @apply relative;\n }\n\n .container {\n --adornment-offset: calc(var(--spacing, 0.25rem) * 1.5);\n\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-
|
|
4373
|
-
"cssTypes": "declare const styles: {\n input: string;\n \"icon-wrapper\": string;\n \"prefix-icon\": string;\n \"suffix-icon\": string;\n container: string;\n \"start-adornments\": string;\n \"end-adornments\": string;\n actions: string;\n hint: string;\n action: string;\n \"number-controls\": string;\n disabled: string;\n \"spin-button\": string;\n};\n\nexport default styles;\n"
|
|
4496
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { mergeProps, } from \"@react-aria/utils\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Tooltip } from \"@/components/Tooltip\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { GroupContext } from \"@/components/Group/Group\";\nimport css from \"./Input.module.css\";\n\ntype Variant = \"default\" | \"ghost\";\n\ntype InputIconStyles = {\n left?: StyleValue;\n right?: StyleValue;\n};\n\ntype InputControlsStyles = {\n up?: StyleValue;\n down?: StyleValue;\n};\n\nexport interface InputStyleSlots {\n root?: StyleValue;\n icon?: StyleValue | InputIconStyles;\n controls?: StyleValue | InputControlsStyles;\n}\n\nexport type InputStylesProp = StylesProp<InputStyleSlots>;\n\nexport type InputAction = InputActionDef | React.ReactNode;\ntype InputIconSlots = {\n prefix?: React.ReactNode;\n suffix?: React.ReactNode;\n};\n\nexport type InputActionDef = {\n icon: React.ReactNode;\n title: string;\n onClick?: React.MouseEventHandler<HTMLButtonElement>;\n};\n\ntype InputActionSlots = {\n left?: InputAction[];\n right?: InputAction[];\n};\n\nconst resolveInputBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight', 'controlsUp', 'controlsDown'] as const);\n\nfunction resolveInputStyles(styles: InputStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveInputBaseStyles(styles);\n const { root, icon, controls } = styles;\n\n let iconLeft: StyleValue | undefined;\n let iconRight: StyleValue | undefined;\n let controlsUp: StyleValue | undefined;\n let controlsDown: StyleValue | undefined;\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon;\n iconRight = icon;\n } else {\n iconLeft = icon.left;\n iconRight = icon.right;\n }\n }\n\n if (controls) {\n if (typeof controls === 'string' || Array.isArray(controls)) {\n controlsUp = controls;\n controlsDown = controls;\n } else {\n controlsUp = controls.up;\n controlsDown = controls.down;\n }\n }\n\n return resolveInputBaseStyles({ root, iconLeft, iconRight, controlsUp, controlsDown });\n}\n\nexport interface InputProps extends Omit<ComponentPropsWithoutRef<\"input\">, \"size\"> {\n /** Controls the visual style of the input */\n variant?: Variant;\n /** Whether the input is in an error state */\n error?: boolean;\n /** Icon displayed before the input value by default, or in named prefix/suffix slots */\n icon?: React.ReactNode | InputIconSlots;\n /** Inline actions rendered on the left or right side of the input. Passing an array keeps the existing right-side behavior. */\n actions?: InputAction[] | InputActionSlots;\n /** Hint content rendered inside a badge on the right side of the input, commonly used for keyboard shortcuts. */\n hint?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: InputStylesProp;\n /** Hides the spinner controls for number inputs */\n \"hide-controls\"?: boolean;\n}\n\nfunction isInputIconSlots(icon: InputProps[\"icon\"]): icon is InputIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && (\"prefix\" in icon || \"suffix\" in icon);\n}\n\nfunction resolveInputIcon(icon: InputProps[\"icon\"]) {\n if (!icon) {\n return undefined;\n }\n\n if (isInputIconSlots(icon)) {\n return icon;\n }\n\n return { prefix: icon };\n}\n\nfunction isInputActionSlots(actions: InputProps[\"actions\"]): actions is InputActionSlots {\n return typeof actions === \"object\" && actions !== null && !Array.isArray(actions) && !React.isValidElement(actions) && (\"left\" in actions || \"right\" in actions);\n}\n\nfunction resolveInputActions(actions: InputProps[\"actions\"]): InputActionSlots {\n if (!actions) {\n return {};\n }\n\n if (isInputActionSlots(actions)) {\n return actions;\n }\n\n return { right: actions };\n}\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n (\n {\n className,\n variant = \"default\",\n error = false,\n disabled,\n icon,\n actions,\n hint,\n type = \"text\",\n onFocus,\n onBlur,\n styles: stylesProp,\n \"hide-controls\": hideControls = false,\n ...props\n },\n ref\n ) => {\n const groupContext = React.useContext(GroupContext);\n const resolvedActions = resolveInputActions(actions);\n const resolvedIcon = resolveInputIcon(icon);\n const leftActions = resolvedActions.left ?? [];\n const rightActions = resolvedActions.right ?? [];\n const hasPrefix = !!resolvedIcon?.prefix;\n const hasSuffix = !!resolvedIcon?.suffix;\n const hasLeftActions = leftActions.length > 0;\n const hasRightActions = rightActions.length > 0;\n const hasHint = hint !== undefined && hint !== null;\n const hasStartAdornment = hasPrefix || hasLeftActions;\n const isNumberType = type === \"number\";\n const [isFocused, setIsFocused] = React.useState(false);\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const scopeRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergeRefs(ref, inputRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef,\n surfaceSelector: '[data-input-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n });\n\n const handleFocus = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleSpinClick = (direction: \"up\" | \"down\") => {\n if (!inputRef.current || disabled) return;\n\n const input = inputRef.current;\n\n if (direction === \"up\") {\n input.stepUp();\n } else {\n input.stepDown();\n }\n\n // Dispatch native input event to trigger React onChange handlers\n const event = new Event(\"input\", { bubbles: true });\n input.dispatchEvent(event);\n };\n\n const resolved = resolveInputStyles(stylesProp);\n const showControls = isNumberType && !hideControls;\n const hasEndAdornment = hasSuffix || hasRightActions || hasHint || showControls;\n const adornmentPadding = \"var(--adornment-offset)\";\n const inputPaddingStyle: React.CSSProperties = {\n ...(hasStartAdornment ? { paddingLeft: adornmentPadding } : {}),\n ...(hasEndAdornment ? { paddingRight: adornmentPadding } : {}),\n };\n\n const renderAction = (action: InputAction, index: number) => {\n const key = React.isValidElement(action) ? index : ((action as InputActionDef).title || index);\n\n return React.isValidElement(action) ? (\n <React.Fragment key={key}>{action}</React.Fragment>\n ) : (\n <Tooltip key={key} content={(action as InputActionDef).title} position=\"top\">\n <button\n type=\"button\"\n className={css.action}\n aria-label={(action as InputActionDef).title}\n onClick={(action as InputActionDef).onClick}\n >\n {(action as InputActionDef).icon}\n </button>\n </Tooltip>\n );\n };\n\n const inputRoot = (\n <div\n ref={containerRef}\n className={cn('input', css.container, resolved.root)}\n data-input-focus-surface=\"true\"\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n >\n {hasStartAdornment && (\n <div className={css['start-adornments']} data-start-adornments>\n {hasPrefix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], resolved.iconLeft)}>\n {resolvedIcon?.prefix}\n </div>\n )}\n {hasLeftActions && (\n <div className={css.actions} data-actions data-actions-position=\"left\">\n {leftActions.map(renderAction)}\n </div>\n )}\n </div>\n )}\n <input\n ref={mergedRef}\n type={type}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-disabled={disabled || undefined}\n data-error={error ? \"true\" : undefined}\n data-variant={variant}\n className={cn(\n 'input',\n css.input,\n className\n )}\n style={inputPaddingStyle}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n ...props,\n })}\n />\n {hasEndAdornment && (\n <div className={css['end-adornments']} data-end-adornments>\n {hasSuffix && (\n <div className={cn('input', 'icon-wrapper', css['icon-wrapper'], resolved.iconRight)}>\n {resolvedIcon?.suffix}\n </div>\n )}\n {hasRightActions && (\n <div className={css.actions} data-actions data-actions-position=\"right\">\n {rightActions.map(renderAction)}\n </div>\n )}\n {hasHint && <span className={css.hint} data-hint>{hint}</span>}\n {showControls && (\n <div\n className={(css as any).controls}\n data-disabled={disabled || undefined}\n >\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'], resolved.controlsUp)}\n onClick={() => handleSpinClick(\"up\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Increment\"\n >\n <ChevronUp size={12} />\n </button>\n <button\n type=\"button\"\n className={cn('input', 'spin-button', css['spin-button'], resolved.controlsDown)}\n onClick={() => handleSpinClick(\"down\")}\n disabled={disabled}\n tabIndex={-1}\n aria-label=\"Decrement\"\n >\n <ChevronDown size={12} />\n </button>\n </div>\n )}\n </div>\n )}\n </div>\n );\n\n if (groupContext) {\n return inputRoot;\n }\n\n return (\n <div\n ref={scopeRef}\n className={cn(\"input-scope\", scopeProps.className, css.scope)}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n {inputRoot}\n </div>\n );\n }\n);\n\nInput.displayName = \"Input\";\n",
|
|
4497
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n @apply flex w-full;\n position: relative;\n overflow: visible;\n }\n\n .input {\n height: fit-content;\n flex: 1;\n min-width: 0;\n @apply py-1.5 px-3;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: transparent;\n border: none;\n outline: none;\n box-sizing: border-box;\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-disabled] {\n color: var(--disabled-foreground);\n cursor: not-allowed;\n }\n\n /* Hide default browser spinners for number inputs */\n &[type=\"number\"] {\n\n &::-webkit-outer-spin-button,\n &::-webkit-inner-spin-button {\n -webkit-appearance: none;\n margin: 0;\n display: none;\n }\n\n /* Firefox */\n &[type=\"number\"] {\n -moz-appearance: textfield;\n }\n }\n }\n\n .icon-wrapper {\n @apply z-10 flex items-center;\n pointer-events: none;\n }\n\n .icon-left {\n @apply relative;\n }\n\n .icon-right {\n @apply relative;\n }\n\n .container {\n --adornment-offset: calc(var(--spacing, 0.25rem) * 1.5);\n\n display: flex;\n align-items: center;\n width: 100%;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n box-sizing: border-box;\n overflow: hidden;\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n cursor: not-allowed;\n opacity: 0.5;\n }\n\n &[data-variant=\"ghost\"] {\n --ring-shadow: none;\n --ring-border: transparent;\n --ring-border-visible: transparent;\n\n background-color: transparent;\n border-color: transparent;\n }\n }\n\n .start-adornments,\n .end-adornments {\n @apply flex items-center gap-1;\n align-self: stretch;\n flex-shrink: 0;\n pointer-events: none;\n }\n\n .start-adornments {\n @apply pl-2.5;\n }\n\n .end-adornments {\n padding-right: var(--adornment-offset);\n\n &:has(.controls) {\n padding-right: 0;\n }\n\n &:has([data-hint]) {\n padding-right: 0;\n }\n }\n\n .actions {\n @apply flex items-center gap-1;\n pointer-events: auto;\n }\n\n .action {\n @apply flex items-center justify-center p-2;\n border-radius: 0.25rem;\n color: var(--action-foreground);\n }\n\n .action:hover {\n background-color: var(--action-background-hover);\n color: var(--action-foreground-hover);\n }\n\n .hint {\n @apply inline-flex items-center justify-center whitespace-nowrap;\n flex-shrink: 0;\n margin-inline-start: calc(var(--spacing, 0.25rem) * 0.5);\n margin-inline-end: var(--adornment-offset);\n font-size: var(--text-sm);\n line-height: 1;\n color: var(--foreground);\n background-color: var(--background);\n pointer-events: auto;\n }\n\n .controls {\n @apply flex w-7.5 flex-col;\n align-self: stretch;\n border-left: 1px solid var(--background-border);\n pointer-events: auto;\n }\n\n .controls[data-disabled] {\n opacity: 0.5;\n cursor: not-allowed;\n }\n\n .spin-button {\n @apply flex w-full flex-1 items-center justify-center p-0 cursor-pointer;\n flex: 1;\n background-color: transparent;\n border: none;\n color: var(--controls-color);\n transition: color 150ms ease-out, background-color 150ms ease-out;\n\n &+.spin-button {\n border-top: 1px solid var(--background-border);\n }\n\n &:hover:not(:disabled) {\n background-color: var(--controls-hover-background);\n color: var(--controls-hover-color);\n }\n\n &:active:not(:disabled) {\n background-color: var(--controls-active-background);\n color: var(--controls-active-color);\n }\n\n &:disabled {\n cursor: not-allowed;\n opacity: 0.5;\n }\n }\n}\n",
|
|
4498
|
+
"cssTypes": "declare const styles: {\n scope: string;\n input: string;\n \"icon-wrapper\": string;\n \"prefix-icon\": string;\n \"suffix-icon\": string;\n container: string;\n \"start-adornments\": string;\n \"end-adornments\": string;\n actions: string;\n hint: string;\n action: string;\n \"number-controls\": string;\n disabled: string;\n \"spin-button\": string;\n};\n\nexport default styles;\n"
|
|
4374
4499
|
},
|
|
4375
4500
|
"label": {
|
|
4376
4501
|
"tsx": "import { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport css from \"./Label.module.css\";\n\ninterface LabelStyleSlots {\n root?: StyleValue;\n requiredIndicator?: StyleValue;\n helperText?: StyleValue;\n}\n\ntype LabelStylesProp = StylesProp<LabelStyleSlots>;\n\nconst resolveLabelBaseStyles = createStylesResolver(['root', 'requiredIndicator', 'helperText'] as const);\n\nexport interface LabelProps\n extends React.LabelHTMLAttributes<HTMLLabelElement> {\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: LabelStylesProp;\n /** Whether to show a required asterisk indicator */\n required?: boolean;\n /** Helper text shown below the label */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Size of the label text */\n size?: \"sm\" | \"md\" | \"lg\" | null;\n /** Whether the label appears disabled */\n disabled?: boolean | null;\n /** Whether to apply error styling */\n error?: boolean | null;\n}\n\nconst Label = ({\n className,\n styles,\n size = \"md\",\n disabled,\n error,\n required,\n helperText,\n helperTextError,\n children,\n ...props\n}: LabelProps) => {\n const resolved = resolveLabelBaseStyles(styles);\n return (\n <div className=\"w-full\">\n <label\n className={cn('label', css.label, className, resolved.root)}\n data-size={size ?? 'md'}\n data-disabled={disabled || undefined}\n data-error={error || undefined}\n {...props}\n >\n {children}\n {required && (\n <span className={cn('label', 'required-indicator', css['required-indicator'], resolved.requiredIndicator)} aria-label=\"required\">\n *\n </span>\n )}\n </label>\n {helperText && (\n <p\n className={cn('label', 'helper-text', css['helper-text'], resolved.helperText)}\n data-error={helperTextError || undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n};\n\nLabel.displayName = \"Label\";\n\nexport { Label };\n",
|
|
@@ -4383,7 +4508,7 @@ export const generatedSourceCode = {
|
|
|
4383
4508
|
"cssTypes": "declare const styles: {\n readonly container: string;\n readonly header: string;\n readonly sticky: string;\n readonly item: string;\n readonly actionGroup: string;\n readonly actions: string;\n readonly action: string;\n readonly checkbox: string;\n readonly control: string;\n readonly media: string;\n readonly title: string;\n readonly desc: string;\n readonly footer: string;\n};\n\nexport default styles;\n"
|
|
4384
4509
|
},
|
|
4385
4510
|
"mask": {
|
|
4386
|
-
"tsx": "import * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport styles from \"./Mask.module.css\";\n\ninterface MaskStyleSlots {\n root?: StyleValue;\n gradient?: StyleValue;\n}\n\ntype MaskStylesProp = StylesProp<MaskStyleSlots>;\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n children: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: MaskStylesProp;\n}\n\ntype MaskFilter =\n | {\n kind: \"fade\";\n direction: NonNullable<MaskFadeProps[\"direction\"]>;\n intensity: number;\n fixed?: boolean;\n }\n | {\n kind: \"scroll-fade\";\n };\n\nconst resolveMaskBaseStyles = createStylesResolver(['root', 'gradient'] as const);\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ asChild = false, className, children, style, styles: stylesProp, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: MaskFilter[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n const supportsScrollFade = hasScrollFadeVariables(style);\n const resolved = resolveMaskBaseStyles(stylesProp);\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (isScrollFade(fadeChild.props) && supportsScrollFade) {\n maskFilters.push({ kind: \"scroll-fade\" });\n } else {\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push({\n kind: \"fade\",\n direction: fadeChild.props.direction ?? \"bottom\",\n intensity: fadeChild.props.intensity ?? 1,\n fixed: fadeChild.props.fixed,\n });\n }\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const resolvedMaskFilters = maskFilters.map((maskFilter) => generateMaskFilter(maskFilter));\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(resolvedMaskFilters.length > 0 ? {\n WebkitMaskImage: resolvedMaskFilters.join(\", \"),\n maskImage: resolvedMaskFilters.join(\", \"),\n WebkitMaskComposite: resolvedMaskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: resolvedMaskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n if (asChild) {\n if (contentChildren.length !== 1 || !React.isValidElement(contentChildren[0])) {\n throw new Error(\"Mask with asChild expects exactly one valid React element child.\");\n }\n\n const child = contentChildren[0] as React.ReactElement<{\n className?: string;\n style?: React.CSSProperties;\n ref?: React.Ref<HTMLDivElement>;\n }>;\n\n return React.cloneElement(child, {\n ...props,\n ref:
|
|
4511
|
+
"tsx": "import * as React from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport styles from \"./Mask.module.css\";\n\ninterface MaskStyleSlots {\n root?: StyleValue;\n gradient?: StyleValue;\n}\n\ntype MaskStylesProp = StylesProp<MaskStyleSlots>;\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n children: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: MaskStylesProp;\n}\n\ntype MaskFilter =\n | {\n kind: \"fade\";\n direction: NonNullable<MaskFadeProps[\"direction\"]>;\n intensity: number;\n fixed?: boolean;\n }\n | {\n kind: \"scroll-fade\";\n };\n\nconst resolveMaskBaseStyles = createStylesResolver(['root', 'gradient'] as const);\n\nconst MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ asChild = false, className, children, style, styles: stylesProp, ...props }, ref) => {\n const childArray = React.Children.toArray(children);\n const maskFilters: MaskFilter[] = [];\n let clipPath: string | undefined;\n let hasFixedFade = false;\n let contentChildren: React.ReactNode[] = [];\n const supportsScrollFade = hasScrollFadeVariables(style);\n const resolved = resolveMaskBaseStyles(stylesProp);\n\n childArray.forEach((child) => {\n if (React.isValidElement(child)) {\n if (child.type === MaskFade) {\n const fadeChild = child as React.ReactElement<MaskFadeProps>;\n if (isScrollFade(fadeChild.props) && supportsScrollFade) {\n maskFilters.push({ kind: \"scroll-fade\" });\n } else {\n if (fadeChild.props.fixed) hasFixedFade = true;\n maskFilters.push({\n kind: \"fade\",\n direction: fadeChild.props.direction ?? \"bottom\",\n intensity: fadeChild.props.intensity ?? 1,\n fixed: fadeChild.props.fixed,\n });\n }\n } else if (child.type === MaskClip) {\n const clipChild = child as React.ReactElement<MaskClipProps>;\n clipPath = clipChild.props.shape;\n } else {\n contentChildren.push(child);\n }\n } else {\n contentChildren.push(child);\n }\n });\n\n const resolvedMaskFilters = maskFilters.map((maskFilter) => generateMaskFilter(maskFilter));\n\n const maskStyles = {\n ...style,\n ...(hasFixedFade ? { maxHeight: \"inherit\", overflow: \"hidden\" as const } : {}),\n ...(clipPath ? { \"--mask-clip-path\": clipPath } as Record<string, string> : {}),\n ...(resolvedMaskFilters.length > 0 ? {\n WebkitMaskImage: resolvedMaskFilters.join(\", \"),\n maskImage: resolvedMaskFilters.join(\", \"),\n WebkitMaskComposite: resolvedMaskFilters.length > 1 ? \"source-in\" : \"source-over\",\n maskComposite: resolvedMaskFilters.length > 1 ? \"intersect\" : \"add\",\n } : {}),\n } as React.CSSProperties;\n\n if (asChild) {\n if (contentChildren.length !== 1 || !React.isValidElement(contentChildren[0])) {\n throw new Error(\"Mask with asChild expects exactly one valid React element child.\");\n }\n\n const child = contentChildren[0] as React.ReactElement<{\n className?: string;\n style?: React.CSSProperties;\n ref?: React.Ref<HTMLDivElement>;\n }>;\n\n return React.cloneElement(child, {\n ...props,\n ref: useMergeRefs(ref, child.props.ref),\n className: cn(\"mask\", styles.mask, resolved.root, className, child.props.className),\n style: {\n ...child.props.style,\n ...maskStyles,\n },\n });\n }\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(\"mask\", styles.mask, resolved.root, className)}\n style={maskStyles}\n >\n {contentChildren}\n </div>\n );\n }\n);\n\nMaskRoot.displayName = \"Mask\";\n\ninterface MaskGradientProps extends React.HTMLAttributes<HTMLDivElement> {\n /** CSS gradient string applied as the mask image */\n gradient: string;\n}\n\nconst MaskGradient = React.forwardRef<HTMLDivElement, MaskGradientProps>(\n ({ className, gradient, style, children, ...props }, ref) => {\n const resolved = resolveMaskBaseStyles(undefined);\n const maskStyles = {\n ...style,\n \"--mask-gradient\": gradient,\n } as React.CSSProperties;\n\n return (\n <div\n {...props}\n ref={ref}\n className={cn(styles.mask, styles[\"mask-gradient\"], resolved.gradient, className)}\n style={maskStyles}\n >\n {children}\n </div>\n );\n }\n);\n\nMaskGradient.displayName = \"MaskGradient\";\n\ninterface MaskFadeProps {\n /** Edge of the container where the fade starts. Omit to use the variable-driven vertical scroll fade preset. */\n direction?: \"top\" | \"bottom\" | \"left\" | \"right\";\n /** Controls the size of the fade — higher values produce a longer fade */\n intensity?: number;\n /** Uses percentage-based fade size instead of pixel-based, for fixed-height containers */\n fixed?: boolean;\n}\n\nconst MaskFade: React.FC<MaskFadeProps> = () => null;\nMaskFade.displayName = \"MaskFade\";\n\nfunction isScrollFade(props: MaskFadeProps): boolean {\n return props.direction === undefined && props.intensity === undefined && props.fixed === undefined;\n}\n\nfunction hasScrollFadeVariables(style: React.CSSProperties | undefined): boolean {\n if (!style) return false;\n\n const styleEntries = style as Record<string, unknown>;\n return \"--mask-top-fade\" in styleEntries || \"--mask-bottom-fade\" in styleEntries;\n}\n\nfunction generateMaskFilter(maskFilter: MaskFilter): string {\n if (maskFilter.kind === \"scroll-fade\") {\n return \"linear-gradient(to bottom, transparent 0%, black var(--mask-top-fade, 0%), black calc(100% - var(--mask-bottom-fade, 0%)), transparent 100%)\";\n }\n\n return generateFadeMask(maskFilter.direction, maskFilter.intensity, maskFilter.fixed);\n}\n\nfunction generateFadeMask(direction: string = \"bottom\", intensity: number = 1, fixed?: boolean): string {\n const fadeSize = fixed ? `${Math.min(50, 15 * intensity)}%` : `${Math.min(200, 40 * intensity)}px`;\n const directionMap = {\n top: `linear-gradient(to bottom, transparent 0, black ${fadeSize})`,\n bottom: `linear-gradient(to bottom, black calc(100% - ${fadeSize}), transparent 100%)`,\n left: `linear-gradient(to right, transparent 0, black ${fadeSize})`,\n right: `linear-gradient(to right, black calc(100% - ${fadeSize}), transparent 100%)`,\n };\n return directionMap[direction as keyof typeof directionMap] || directionMap.bottom;\n}\n\n\ninterface MaskClipProps {\n /** CSS clip-path value applied to the container (e.g. polygon, circle) */\n shape: string;\n}\n\nconst MaskClip: React.FC<MaskClipProps> = () => null;\nMaskClip.displayName = \"MaskClip\";\n\nconst Mask = Object.assign(MaskRoot, {\n Gradient: MaskGradient,\n Fade: MaskFade,\n Clip: MaskClip,\n});\n\nexport { Mask };\n",
|
|
4387
4512
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .mask {\n @apply relative h-full w-full;\n }\n}\n\n.mask[style*=\"mask-image\"],\n.mask[style*=\"-webkit-mask-image\"] {\n -webkit-mask-size: 100% 100%;\n mask-size: 100% 100%;\n}\n\n.mask[style*=\"--mask-clip-path\"] {\n clip-path: var(--mask-clip-path);\n}\n\n.mask-gradient {\n background: var(--mask-gradient);\n -webkit-background-clip: text;\n background-clip: text;\n -webkit-text-fill-color: transparent;\n color: transparent;\n}\n",
|
|
4388
4513
|
"cssTypes": "declare const styles: {\n mask: string;\n \"mask-gradient\": string;\n};\n\nexport default styles;\n"
|
|
4389
4514
|
},
|
|
@@ -4408,12 +4533,12 @@ export const generatedSourceCode = {
|
|
|
4408
4533
|
"cssTypes": "declare const styles: {\n readonly panel: string;\n readonly header: string;\n readonly sticky: string;\n readonly content: string;\n readonly footer: string;\n readonly fixed: string;\n readonly sidebar: string;\n readonly toggle: string;\n readonly group: string;\n readonly resize: string;\n readonly spacingNone: string;\n readonly spacingSm: string;\n readonly spacingMd: string;\n readonly spacingLg: string;\n readonly compact: string;\n readonly stacked: string;\n};\n\nexport default styles;\n"
|
|
4409
4534
|
},
|
|
4410
4535
|
"path": {
|
|
4411
|
-
"tsx": "'use client';\n\nimport React, { ReactNode, forwardRef } from 'react';\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport css from \"./Path.module.css\";\n\nexport interface PathItemProps {\n /** URL this path item links to */\n href?: string;\n /** Called when the path item is pressed */\n onPress?: () => void;\n children: ReactNode;\n /** Whether this is the current/active page */\n isCurrent?: boolean;\n /** Whether the item is non-interactive */\n isDisabled?: boolean;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface PathStyleSlots {\n root?: StyleValue;\n list?: StyleValue;\n separator?: StyleValue;\n}\n\ntype PathStylesProp = StylesProp<PathStyleSlots>;\n\nexport interface PathProps {\n children: ReactNode;\n /** Additional CSS class for the path container */\n className?: string;\n /** Custom separator element between path items */\n separator?: ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PathStylesProp;\n}\n\nconst resolvePathBaseStyles = createStylesResolver(['root', 'list', 'separator'] as const);\n\nconst PathItem = forwardRef<HTMLLIElement, PathItemProps>(\n ({ href, onPress, children, isCurrent = false, isDisabled = false, className }, ref) => {\n const isInteractive = !isCurrent && !isDisabled && (href || onPress);\n\n return (\n <li ref={ref} className={css['path-item']}>\n {isInteractive ? (\n <a\n href={href}\n className={cn(css['path-item-link'], className || '')}\n data-disabled={isDisabled || undefined}\n data-current={isCurrent || undefined}\n aria-current={isCurrent ? 'page' : undefined}\n onClick={(e) => {\n if (onPress) {\n e.preventDefault();\n onPress();\n }\n }}\n >\n {children}\n </a>\n ) : (\n <span\n className={`${css['path-item-link']} ${className || ''}`}\n data-disabled={isDisabled || undefined}\n data-current={isCurrent || undefined}\n aria-current={isCurrent ? 'page' : undefined}\n >\n {children}\n </span>\n )}\n </li>\n );\n }\n);\n\nPathItem.displayName = 'Path.Item';\n\nconst Path = forwardRef<HTMLElement, PathProps>(\n ({ children, className, separator, styles }, ref) => {\n const childArray = React.Children.toArray(children);\n const childCount = childArray.length;\n\n const resolved = resolvePathBaseStyles(styles);\n\n return (\n <nav\n ref={ref}\n className={cn(css.path, className, resolved.root)}\n aria-label=\"Path\"\n >\n <ol className={cn(\n css['path-list'],\n resolved.list,\n separator && css['with-custom-separator']\n )}>\n {React.Children.map(childArray, (child, index) => {\n const isLastChild = index === childCount - 1;\n if (React.isValidElement(child)) {\n const element = React.cloneElement(child as React.ReactElement<PathItemProps>, {\n isCurrent: isLastChild,\n });\n\n // Add separator after each item except the last\n if (separator && !isLastChild) {\n return (\n <React.Fragment key={index}>\n {element}\n <li className={cn(css.separator, resolved.separator)} aria-hidden=\"true\">\n {separator}\n </li>\n </React.Fragment>\n );\n }\n return element;\n }\n return child;\n })}\n </ol>\n </nav>\n );\n }\n);\n\nPath.displayName = 'Path';\n\nexport { Path, PathItem };\n",
|
|
4412
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n
|
|
4413
|
-
"cssTypes": "export interface Styles {\n \"path\": string;\n \"
|
|
4536
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { mergeProps } from \"@react-aria/utils\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport css from \"./Path.module.css\";\n\nexport interface PathItemStyleSlots {\n root?: StyleValue;\n link?: StyleValue;\n}\n\nexport type PathItemStylesProp = StylesProp<PathItemStyleSlots>;\n\nexport interface PathStyleSlots {\n root?: StyleValue;\n list?: StyleValue;\n separator?: StyleValue;\n}\n\nexport type PathStylesProp = StylesProp<PathStyleSlots>;\n\nfunction focusPathSibling(\n currentTarget: HTMLElement,\n direction: \"next\" | \"previous\" | \"first\" | \"last\"\n) {\n const listElement = currentTarget.closest('[data-path-list=\"true\"]');\n if (!listElement) {\n return;\n }\n\n const focusableItems = Array.from(\n listElement.querySelectorAll('[data-path-item-focus-surface=\"true\"]:not([data-disabled=\"true\"])')\n ) as HTMLElement[];\n\n if (!focusableItems.length) {\n return;\n }\n\n const currentIndex = focusableItems.indexOf(currentTarget);\n if (currentIndex === -1) {\n return;\n }\n\n let nextIndex = currentIndex;\n\n if (direction === \"next\") {\n nextIndex = (currentIndex + 1) % focusableItems.length;\n } else if (direction === \"previous\") {\n nextIndex = currentIndex === 0 ? focusableItems.length - 1 : currentIndex - 1;\n } else if (direction === \"first\") {\n nextIndex = 0;\n } else if (direction === \"last\") {\n nextIndex = focusableItems.length - 1;\n }\n\n focusableItems[nextIndex]?.focus();\n}\n\nconst resolvePathBaseStyles = createStylesResolver([\"root\", \"list\", \"separator\"] as const);\nconst resolvePathItemBaseStyles = createStylesResolver([\"root\", \"link\"] as const);\n\nfunction resolvePathStyles(styles: PathStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolvePathBaseStyles(styles);\n const { root, list, separator } = styles;\n return resolvePathBaseStyles({ root, list, separator });\n}\n\nfunction resolvePathItemStyles(styles: PathItemStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolvePathItemBaseStyles(styles);\n const { root, link } = styles;\n return resolvePathItemBaseStyles({ root, link });\n}\n\nexport interface PathItemProps {\n /** Content rendered inside the path item. */\n children: React.ReactNode;\n /** URL this path item navigates to. */\n href?: string;\n /** Called when the item is activated. */\n onPress?: () => void;\n /** Whether this item represents the current page. */\n isCurrent?: boolean;\n /** Whether the item is non-interactive. */\n isDisabled?: boolean;\n /** Additional CSS class names applied to the item root. */\n className?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PathItemStylesProp;\n}\n\nexport interface PathProps {\n /** Path items rendered inside the ordered list. */\n children: React.ReactNode;\n /** Additional CSS class names applied to the path root. */\n className?: string;\n /** Custom separator rendered between path items. */\n separator?: React.ReactNode;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PathStylesProp;\n}\n\nconst PathItem = React.forwardRef<HTMLLIElement, PathItemProps>(\n (\n {\n href,\n onPress,\n children,\n isCurrent = false,\n isDisabled = false,\n className,\n styles,\n },\n ref\n ) => {\n const itemRef = React.useRef<HTMLLIElement>(null);\n const mergedRef = useMergeRefs(ref, itemRef);\n const isInteractive = !isCurrent && !isDisabled && Boolean(href || onPress);\n const [isPressed, setIsPressed] = React.useState(false);\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: !isInteractive });\n const resolved = resolvePathItemStyles(styles);\n\n const handleMouseDown = React.useCallback(() => {\n if (isInteractive) {\n setIsPressed(true);\n }\n }, [isInteractive]);\n\n const handleMouseUp = React.useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const handleMouseLeave = React.useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === \"ArrowRight\") {\n event.preventDefault();\n focusPathSibling(event.currentTarget, \"next\");\n return;\n }\n\n if (event.key === \"ArrowLeft\") {\n event.preventDefault();\n focusPathSibling(event.currentTarget, \"previous\");\n return;\n }\n\n if (event.key === \"Home\") {\n event.preventDefault();\n focusPathSibling(event.currentTarget, \"first\");\n return;\n }\n\n if (event.key === \"End\") {\n event.preventDefault();\n focusPathSibling(event.currentTarget, \"last\");\n return;\n }\n\n if (!isInteractive) {\n return;\n }\n\n if (event.key === \" \" || event.key === \"Enter\") {\n setIsPressed(true);\n }\n },\n [isInteractive]\n );\n\n const handleKeyUp = React.useCallback((event: React.KeyboardEvent<HTMLElement>) => {\n if (event.key === \" \" || event.key === \"Enter\") {\n setIsPressed(false);\n }\n }, []);\n\n const mergedInteractionProps = mergeProps(focusProps, hoverProps, {\n onMouseDown: handleMouseDown,\n onMouseUp: handleMouseUp,\n onMouseLeave: handleMouseLeave,\n onKeyDown: handleKeyDown,\n onKeyUp: handleKeyUp,\n }) as unknown as Record<string, unknown>;\n\n const {\n onChange: _onChange,\n onChangeCapture: _onChangeCapture,\n ...interactionProps\n } = mergedInteractionProps;\n\n const stateProps = {\n \"data-selected\": isCurrent ? \"true\" : undefined,\n \"data-disabled\": isDisabled ? \"true\" : undefined,\n \"data-focused\": isFocused ? \"true\" : undefined,\n \"data-focus-visible\": isFocusVisible ? \"true\" : undefined,\n \"data-hovered\": isHovered ? \"true\" : undefined,\n \"data-pressed\": isPressed ? \"true\" : undefined,\n \"data-path-item-focus-surface\": \"true\" as const,\n \"aria-current\": isCurrent ? (\"page\" as const) : undefined,\n };\n\n const surfaceClassName = cn(\"path-link\", css.link, resolved.link);\n const focusableTabIndex = isDisabled ? -1 : isCurrent ? 0 : undefined;\n\n return (\n <li\n ref={mergedRef}\n className={cn(\"path-item\", css.item, className, resolved.root)}\n data-selected={isCurrent ? \"true\" : undefined}\n data-disabled={isDisabled ? \"true\" : undefined}\n >\n {isInteractive && href ? (\n <a\n {...(interactionProps as React.AnchorHTMLAttributes<HTMLAnchorElement>)}\n href={href}\n className={surfaceClassName}\n tabIndex={focusableTabIndex}\n {...stateProps}\n onClick={(event) => {\n if (onPress) {\n event.preventDefault();\n onPress();\n }\n }}\n >\n {children}\n </a>\n ) : isInteractive ? (\n <button\n {...(interactionProps as React.ButtonHTMLAttributes<HTMLButtonElement>)}\n type=\"button\"\n className={surfaceClassName}\n tabIndex={focusableTabIndex}\n onClick={onPress}\n {...stateProps}\n >\n {children}\n </button>\n ) : (\n <span\n {...(interactionProps as React.HTMLAttributes<HTMLSpanElement>)}\n className={surfaceClassName}\n tabIndex={focusableTabIndex}\n {...stateProps}\n >\n {children}\n </span>\n )}\n </li>\n );\n }\n);\n\nPathItem.displayName = \"Path.Item\";\n\nconst Path = React.forwardRef<HTMLElement, PathProps>(\n ({ children, className, separator, styles }, ref) => {\n const scopeRef = React.useRef<HTMLDivElement>(null);\n const navRef = React.useRef<HTMLElement>(null);\n const mergedRef = useMergeRefs(ref, navRef);\n const childArray = React.Children.toArray(children);\n const childCount = childArray.length;\n const resolved = resolvePathStyles(styles);\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: navRef,\n surfaceSelector: '[data-path-item-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n dependencies: [childCount, Boolean(separator)],\n });\n\n return (\n <div ref={scopeRef} className={cn(\"path-scope\", scopeProps.className)}>\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <nav\n ref={mergedRef}\n className={cn(\"path\", css.path, className, resolved.root)}\n aria-label=\"Path\"\n >\n <ol\n className={cn(\"path-list\", css.list, resolved.list)}\n data-path-list=\"true\"\n data-separator={separator ? \"custom\" : undefined}\n >\n {React.Children.map(childArray, (child, index) => {\n const isLastChild = index === childCount - 1;\n\n if (React.isValidElement(child)) {\n const element = React.cloneElement(\n child as React.ReactElement<PathItemProps>,\n { isCurrent: isLastChild }\n );\n\n if (separator && !isLastChild) {\n return (\n <React.Fragment key={child.key ?? index}>\n {element}\n <li\n className={cn(\"path-separator\", css.separator, resolved.separator)}\n aria-hidden=\"true\"\n >\n {separator}\n </li>\n </React.Fragment>\n );\n }\n\n return element;\n }\n\n return child;\n })}\n </ol>\n </nav>\n </div>\n );\n }\n);\n\nPath.displayName = \"Path\";\n\nexport { Path, PathItem };\n",
|
|
4537
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .path {\n @apply block;\n }\n\n .list {\n @apply m-0 flex flex-wrap items-center gap-2 p-0;\n list-style: none;\n }\n\n .list[data-separator=\"custom\"] .item:not(:last-child)::after {\n content: none;\n }\n\n .item {\n @apply m-0 flex items-center gap-2 p-0;\n }\n\n .item:not(:last-child)::after {\n content: \"/\";\n margin-inline-start: 0.5rem;\n color: var(--separator-foreground);\n pointer-events: none;\n user-select: none;\n }\n\n .separator {\n @apply m-0 flex items-center p-0;\n list-style: none;\n color: var(--separator-foreground);\n pointer-events: none;\n user-select: none;\n }\n\n .link {\n @apply relative cursor-pointer px-2 py-1;\n border: 0;\n background-color: transparent;\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n font-weight: var(--font-weight-medium, 500);\n line-height: var(--leading-normal, 1.5);\n text-decoration: none;\n transition:\n color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n outline: none;\n }\n\n button.link {\n font: inherit;\n }\n\n .link:focus,\n .link:focus-visible {\n outline: none;\n }\n\n .link[data-hovered=\"true\"]:not([data-disabled=\"true\"]):not([data-selected=\"true\"]) {\n background-color: var(--background-hover);\n color: var(--foreground-hover);\n }\n\n .link[data-pressed=\"true\"]:not([data-disabled=\"true\"]):not([data-selected=\"true\"]) {\n background-color: var(--background-pressed);\n }\n\n .link[data-selected=\"true\"] {\n color: var(--foreground-selected);\n cursor: default;\n }\n\n .link[data-selected=\"true\"][data-hovered=\"true\"] {\n background-color: transparent;\n }\n\n .link[data-disabled=\"true\"] {\n color: var(--foreground-disabled);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n .link[data-disabled=\"true\"][data-hovered=\"true\"] {\n background-color: transparent;\n }\n}\n",
|
|
4538
|
+
"cssTypes": "export interface Styles {\n \"path\": string;\n \"list\": string;\n \"item\": string;\n \"separator\": string;\n \"link\": string;\n}\n\nexport default styles;\n"
|
|
4414
4539
|
},
|
|
4415
4540
|
"popover": {
|
|
4416
|
-
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useOverlayTrigger, useDialog, mergeProps } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { type StyleValue } from \"./utils\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport css from \"./Popover.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nconst ARROW_PATH = \"M 0 0 L 6 -12 L 12 0\";\nconst ARROW_WIDTH = 12;\nconst POPOVER_GAP = 8;\nconst ARROW_POSITIONING_SIZE = 6;\n\ntype PopoverPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: PopoverPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\n/**\n * Maps placement to initial transform for directional entrance animation.\n * When animating in, the component slides from its placement direction toward the center.\n * For example, \"top\" placement slides up (-Y) and fades in.\n */\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\ninterface PopoverStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n trigger?: StyleValue;\n frame?: StyleValue;\n}\n\ntype PopoverStylesProp = StylesProp<PopoverStyleSlots>;\n\nexport interface PopoverProps {\n children: React.ReactNode;\n /** Content to display inside the popover panel */\n content: React.ReactNode;\n /** Preferred side of the trigger where the popover appears */\n position?: PopoverPosition;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PopoverStylesProp;\n /** Additional CSS class for the trigger element. */\n className?: string;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the popover opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n}\n\nconst Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n ({ children, content, position = \"bottom\", styles, className: externalClassName, isOpen: controlledIsOpen, onOpenChange, showArrow = false }, ref) => {\n\n const resolvePopoverBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'trigger',\n 'frame',\n ] as const);\n\n const resolved = resolvePopoverBaseStyles(styles);\n\n const triggerRef = React.useRef<HTMLDivElement>(null);\n const popoverContentRef = React.useRef<HTMLDivElement>(null);\n const [isAnimating, setIsAnimating] = React.useState(false);\n const [isExiting, setIsExiting] = React.useState(false);\n\n const state = useOverlayTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange,\n });\n\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: \"dialog\" }, state, triggerRef);\n const { dialogProps } = useDialog({}, popoverContentRef);\n\n const placementMap: Record<PopoverPosition, \"top\" | \"bottom\" | \"left\" | \"right\"> = {\n top: \"top\",\n bottom: \"bottom\",\n left: \"left\",\n right: \"right\",\n };\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: placementMap[position],\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(POPOVER_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n // Trigger animation when popover is opened and positioned\n React.useEffect(() => {\n if (state.isOpen && isPositioned) {\n setIsExiting(false);\n setIsAnimating(true);\n }\n }, [state.isOpen, isPositioned]);\n\n // Handle exit animation when closing\n React.useEffect(() => {\n if (!state.isOpen && isAnimating) {\n // First, enable exit mode so element stays in DOM\n setIsExiting(true);\n\n requestAnimationFrame(() => setIsAnimating(false));\n const timer = setTimeout(() => setIsExiting(false), 50)\n return () => clearTimeout(timer);\n }\n }, [state.isOpen, isAnimating]);\n\n React.useLayoutEffect(() => {\n refs.setReference(triggerRef.current);\n }, [refs]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n triggerRef.current &&\n !triggerRef.current.contains(target) &&\n popoverContentRef.current &&\n !popoverContentRef.current.contains(target)\n ) {\n state.close();\n }\n };\n document.addEventListener(\"click\", handleClickOutside);\n return () => document.removeEventListener(\"click\", handleClickOutside);\n }, [state.isOpen, state]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") state.close();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [state.isOpen, state]);\n\n const mergedRef =
|
|
4541
|
+
"tsx": "\"use client\"\n\nimport React from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useOverlayTrigger, useDialog, mergeProps } from \"react-aria\";\nimport { useOverlayTriggerState } from \"react-stately\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { type StyleValue } from \"./utils\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Frame } from \"../Frame\";\nimport css from \"./Popover.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nconst ARROW_PATH = \"M 0 0 L 6 -12 L 12 0\";\nconst ARROW_WIDTH = 12;\nconst POPOVER_GAP = 8;\nconst ARROW_POSITIONING_SIZE = 6;\n\ntype PopoverPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: PopoverPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\n/**\n * Maps placement to initial transform for directional entrance animation.\n * When animating in, the component slides from its placement direction toward the center.\n * For example, \"top\" placement slides up (-Y) and fades in.\n */\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\ninterface PopoverStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n trigger?: StyleValue;\n frame?: StyleValue;\n}\n\ntype PopoverStylesProp = StylesProp<PopoverStyleSlots>;\n\nexport interface PopoverProps {\n children: React.ReactNode;\n /** Content to display inside the popover panel */\n content: React.ReactNode;\n /** Preferred side of the trigger where the popover appears */\n position?: PopoverPosition;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: PopoverStylesProp;\n /** Additional CSS class for the trigger element. */\n className?: string;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the popover opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n}\n\nconst Popover = React.forwardRef<HTMLDivElement, PopoverProps>(\n ({ children, content, position = \"bottom\", styles, className: externalClassName, isOpen: controlledIsOpen, onOpenChange, showArrow = false }, ref) => {\n\n const resolvePopoverBaseStyles = createStylesResolver([\n 'root',\n 'content',\n 'trigger',\n 'frame',\n ] as const);\n\n const resolved = resolvePopoverBaseStyles(styles);\n\n const triggerRef = React.useRef<HTMLDivElement>(null);\n const popoverContentRef = React.useRef<HTMLDivElement>(null);\n const [isAnimating, setIsAnimating] = React.useState(false);\n const [isExiting, setIsExiting] = React.useState(false);\n\n const state = useOverlayTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange,\n });\n\n const { triggerProps, overlayProps } = useOverlayTrigger({ type: \"dialog\" }, state, triggerRef);\n const { dialogProps } = useDialog({}, popoverContentRef);\n\n const placementMap: Record<PopoverPosition, \"top\" | \"bottom\" | \"left\" | \"right\"> = {\n top: \"top\",\n bottom: \"bottom\",\n left: \"left\",\n right: \"right\",\n };\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: placementMap[position],\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(POPOVER_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n // Trigger animation when popover is opened and positioned\n React.useEffect(() => {\n if (state.isOpen && isPositioned) {\n setIsExiting(false);\n setIsAnimating(true);\n }\n }, [state.isOpen, isPositioned]);\n\n // Handle exit animation when closing\n React.useEffect(() => {\n if (!state.isOpen && isAnimating) {\n // First, enable exit mode so element stays in DOM\n setIsExiting(true);\n\n requestAnimationFrame(() => setIsAnimating(false));\n const timer = setTimeout(() => setIsExiting(false), 50)\n return () => clearTimeout(timer);\n }\n }, [state.isOpen, isAnimating]);\n\n React.useLayoutEffect(() => {\n refs.setReference(triggerRef.current);\n }, [refs]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleClickOutside = (e: MouseEvent) => {\n const target = e.target as Node;\n if (\n triggerRef.current &&\n !triggerRef.current.contains(target) &&\n popoverContentRef.current &&\n !popoverContentRef.current.contains(target)\n ) {\n state.close();\n }\n };\n document.addEventListener(\"click\", handleClickOutside);\n return () => document.removeEventListener(\"click\", handleClickOutside);\n }, [state.isOpen, state]);\n\n React.useEffect(() => {\n if (!state.isOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") state.close();\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => document.removeEventListener(\"keydown\", handleKeyDown);\n }, [state.isOpen, state]);\n\n const mergedRef = useMergeRefs(triggerRef, refs.setReference, ref);\n\n const mergedContentRef = useMergeRefs(popoverContentRef, refs.setFloating);\n\n // Convert React Aria's onPress to onClick for native HTML elements\n const nativeProps = React.useMemo(() => {\n const props: any = { ...triggerProps };\n if (props.onPress && typeof props.onPress === 'function') {\n const onPress = props.onPress;\n props.onClick = (e: React.MouseEvent) => {\n onPress({ target: e.currentTarget, type: 'press', pointerType: 'mouse', ctrlKey: e.ctrlKey, metaKey: e.metaKey, shiftKey: e.shiftKey, altKey: e.altKey });\n };\n delete props.onPress;\n }\n return props;\n }, [triggerProps]);\n\n const triggerElement = React.isValidElement(children)\n ? React.cloneElement(children as React.ReactElement<{ className?: string; ref?: React.Ref<HTMLButtonElement | HTMLDivElement> }>, {\n ...nativeProps,\n className: cn((children as React.ReactElement<{ className?: string }>).props.className, externalClassName, css.trigger, resolved.trigger),\n ref: mergedRef,\n })\n : (\n <span ref={mergedRef} {...nativeProps} className={cn(css.trigger, externalClassName, resolved.trigger)}>\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n {(state.isOpen || isExiting) &&\n createPortal(\n <div\n ref={mergedContentRef}\n {...asElementProps<\"div\">(mergeProps(overlayProps, dialogProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn('popover', 'content', css.content)}\n style={{\n opacity: isAnimating ? 1 : 0,\n transform: isAnimating ? \"scale(1)\" : getInitialTransform(placement),\n pointerEvents: isAnimating ? 'auto' : 'none',\n }}\n >\n <Frame\n role=\"dialog\"\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n className={cn('popover', 'frame', css.frame, resolved.frame)}\n >\n {content}\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nPopover.displayName = \"Popover\";\n\nexport { Popover };\n",
|
|
4417
4542
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n @apply inline-block;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--background);\n --frame-stroke-color: var(--border);\n --frame-radius: 8px;\n opacity: 0;\n transform: scale(0.95);\n transition: opacity 0.2s ease-out, transform 0.2s ease-out;\n pointer-events: none;\n min-width: 200px;\n max-width: 400px;\n padding: 0.75rem;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n transform: scale(1);\n pointer-events: auto;\n }\n\n .content[data-instant] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1;\n color: var(--foreground);\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n @apply whitespace-nowrap;\n }\n}\n",
|
|
4418
4543
|
"cssTypes": "export interface Styles {\n trigger: string;\n root: string;\n content: string;\n frame: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4419
4544
|
},
|
|
@@ -4423,28 +4548,28 @@ export const generatedSourceCode = {
|
|
|
4423
4548
|
"cssTypes": "export interface Styles {\n progress: string;\n sm: string;\n md: string;\n lg: string;\n fill: string;\n wrapper: string;\n \"label-row\": string;\n label: string;\n value: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4424
4549
|
},
|
|
4425
4550
|
"radio": {
|
|
4426
|
-
"tsx": "\"use client\";\n\nimport React, { useId, createContext, useContext } from \"react\";\nimport { useRadioGroupState } from \"react-stately\";\n\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useRadioGroup, useRadio } from \"@react-aria/radio\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport styles from \"./Radio.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\nexport interface RadioStyleSlots {\n root?: StyleValue;\n item?: StyleValue;\n input?: StyleValue;\n dot?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n helperText?: StyleValue;\n}\n\nexport type RadioStylesProp = StylesProp<RadioStyleSlots>;\n\nconst resolveRadioBaseStyles = createStylesResolver([\n \"root\",\n \"item\",\n \"input\",\n \"dot\",\n \"label\",\n \"description\",\n \"helperText\",\n] as const);\n\nfunction resolveRadioStyles(styles: RadioStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveRadioBaseStyles(styles);\n }\n\n const { root, item, input, dot, label, description, helperText } = styles;\n return resolveRadioBaseStyles({ root, item, input, dot, label, description, helperText });\n}\n\ninterface RadioGroupContextType {\n state?: ReturnType<typeof useRadioGroupState>;\n disabled?: boolean;\n size?: Size;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType | undefined>(undefined);\n\nconst useRadioGroupContext = () => {\n const context = useContext(RadioGroupContext);\n return context;\n};\n\nexport interface RadioGroupStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n group?: StyleValue;\n}\n\nexport type RadioGroupStylesProp = StylesProp<RadioGroupStyleSlots>;\n\nconst resolveRadioGroupBaseStyles = createStylesResolver([\n \"root\",\n \"label\",\n \"description\",\n \"group\",\n] as const);\n\nfunction resolveRadioGroupStyles(styles: RadioGroupStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveRadioGroupBaseStyles(styles);\n }\n\n const { root, label, description, group } = styles;\n return resolveRadioGroupBaseStyles({ root, label, description, group });\n}\n\nexport interface RadioGroupProps {\n /** Controlled selected radio value */\n value?: string;\n /** Initial selected value for uncontrolled usage */\n defaultValue?: string;\n /** Called when the selected value changes */\n onValueChange?: (value: string) => void;\n /** Whether all radios in the group are disabled */\n disabled?: boolean;\n /** Size of all radio buttons in the group */\n size?: Size;\n children: React.ReactNode;\n /** Additional CSS class names */\n className?: string;\n /** Accessible label for the radio group */\n label?: string;\n /** Descriptive text shown below the group label */\n description?: string;\n /** Classes applied to the root or named slots */\n styles?: RadioGroupStylesProp;\n}\n\nconst RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(\n (\n {\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n size = \"md\",\n children,\n className,\n label,\n description,\n styles: stylesProp,\n },\n ref\n ) => {\n const state = useRadioGroupState({\n value: controlledValue,\n defaultValue,\n onChange: onValueChange,\n isDisabled: disabled,\n });\n\n useRadioGroup(\n {\n isDisabled: disabled,\n label,\n description,\n },\n state\n );\n\n const resolved = resolveRadioGroupStyles(stylesProp);\n\n return (\n <RadioGroupContext.Provider value={{ state, disabled, size }}>\n <div ref={ref} className={cn(className, resolved.root)} role=\"group\">\n {label && (\n <label\n className={cn(\"radio\", \"radio-label\", styles[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\"radio\", \"radio-description\", styles[\"radio-description\"], resolved.description)}\n >\n {description}\n </p>\n )}\n <div className={cn(styles[\"radio-group\"], resolved.group)}>{children}</div>\n </div>\n </RadioGroupContext.Provider>\n );\n }\n);\n\nRadioGroup.displayName = \"RadioGroup\";\n\nexport interface RadioItemProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Value submitted when this radio is selected */\n value: string;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(\n (\n {\n className,\n size: sizeProp,\n disabled: disabledProp = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n value,\n id,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const radioGroupContext = useRadioGroupContext();\n const generatedId = useId();\n const radioId = id || `radio-${generatedId}`;\n\n if (!radioGroupContext?.state) {\n throw new Error(\"RadioItem must be used within a Radio.Group\");\n }\n\n const { state } = radioGroupContext;\n const size = sizeProp || radioGroupContext?.size || \"md\";\n const disabled = disabledProp ?? radioGroupContext?.disabled ?? false;\n const isSelected = state.selectedValue === value;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const mergedRef = useMergedRef(ref, inputRef);\n\n const ariaLabelFromProps = props[\"aria-label\"];\n const ariaLabelValue = ariaLabelFromProps || (typeof label === \"string\" ? label : undefined);\n\n const { inputProps } = useRadio(\n {\n value,\n isDisabled: disabled,\n ...(ariaLabelValue && { \"aria-label\": ariaLabelValue }),\n },\n state,\n inputRef\n );\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n const resolved = resolveRadioStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div className={cn(styles[\"radio-item\"], resolved.item)} data-disabled={disabled ? \"true\" : undefined}>\n <div className=\"relative\">\n <div\n className={cn(\"radio\", styles.radio, styles[size], className, resolved.root)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n role=\"presentation\"\n >\n <div className={cn(styles[\"radio-dot\"], styles[size], resolved.dot)} />\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(inputProps, focusProps, hoverProps))}\n ref={mergedRef}\n type=\"radio\"\n id={radioId}\n className={cn(styles[\"radio-input\"], resolved.input)}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\"radio\", \"radio-label\", styles[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n \"radio\",\n \"radio-description\",\n styles[\"radio-description\"],\n resolved.description\n )}\n data-error={error ? \"true\" : undefined}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\"radio\", \"helper-text\", styles[\"helper-text\"], resolved.helperText)}\n data-error={helperTextError ? \"true\" : undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioItem.displayName = \"RadioItem\";\n\nexport interface RadioProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(\n (\n {\n className,\n size = \"md\",\n disabled = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n checked: checkedProp,\n defaultChecked,\n onChange,\n id,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const [internalChecked, setInternalChecked] = React.useState(checkedProp ?? defaultChecked ?? false);\n const generatedId = useId();\n\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const radioId = id || `radio-${generatedId}`;\n const inputRef = React.useRef<HTMLInputElement>(null);\n const mergedRef = useMergedRef(ref, inputRef);\n const resolved = resolveRadioStyles(stylesProp);\n\n return (\n <div className=\"w-full\">\n <div className={cn(styles[\"radio-item\"], resolved.item)} data-disabled={disabled ? \"true\" : undefined}>\n <div className=\"relative\">\n <div\n className={cn(\"radio\", styles.radio, styles[size], className, resolved.root)}\n data-selected={checked ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n role=\"presentation\"\n >\n <div className={cn(styles[\"radio-dot\"], styles[size], resolved.dot)} />\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(focusProps, hoverProps))}\n ref={mergedRef}\n type=\"radio\"\n id={radioId}\n checked={checked}\n onChange={handleChange}\n disabled={disabled ?? false}\n className={cn(styles[\"radio-input\"], resolved.input)}\n aria-label={typeof label === \"string\" ? label : undefined}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\"radio\", \"radio-label\", styles[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n \"radio\",\n \"radio-description\",\n styles[\"radio-description\"],\n resolved.description\n )}\n data-error={error ? \"true\" : undefined}\n >\n {description}\n </p>\n )}\n </div>\n )}\n </div>\n {helperText && (\n <p\n className={cn(\"radio\", \"helper-text\", styles[\"helper-text\"], resolved.helperText)}\n data-error={helperTextError ? \"true\" : undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioBase.displayName = \"Radio\";\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((slotRef) => {\n if (typeof slotRef === \"function\") {\n slotRef(value);\n } else if (slotRef && typeof slotRef === \"object\") {\n (slotRef as React.MutableRefObject<T | null>).current = value;\n }\n });\n };\n}\n\nconst Radio = Object.assign(RadioBase, {\n Group: RadioGroup,\n Item: RadioItem,\n});\n\nexport { Radio };\n",
|
|
4427
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n }\n\n .radio-input {\n @apply absolute inset-0 h-full w-full cursor-pointer opacity-0;\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--background);\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-error=\"true\"][data-selected=\"true\"] {\n border-color: var(--background-selected-border);\n }\n\n &[data-focus-visible=\"true\"] {\n
|
|
4428
|
-
"cssTypes": "declare const styles: {\n \"radio-group\": string;\n \"radio-item\": string;\n \"radio-input\": string;\n radio: string;\n \"radio-dot\": string;\n \"radio-label\": string;\n \"radio-description\": string;\n \"helper-text\": string;\n sm: string;\n md: string;\n lg: string;\n};\n\nexport default styles;\n"
|
|
4551
|
+
"tsx": "\"use client\";\n\nimport React, { useId, createContext, useContext } from \"react\";\nimport { useRadioGroupState } from \"react-stately\";\n\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useRadioGroup, useRadio } from \"@react-aria/radio\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport css from \"./Radio.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\nexport interface RadioStyleSlots {\n root?: StyleValue;\n item?: StyleValue;\n input?: StyleValue;\n dot?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n helperText?: StyleValue;\n}\n\nexport type RadioStylesProp = StylesProp<RadioStyleSlots>;\n\nconst resolveRadioBaseStyles = createStylesResolver([\n \"root\",\n \"item\",\n \"input\",\n \"dot\",\n \"label\",\n \"description\",\n \"helperText\",\n] as const);\n\nfunction resolveRadioStyles(styles: RadioStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveRadioBaseStyles(styles);\n }\n\n const { root, item, input, dot, label, description, helperText } = styles;\n return resolveRadioBaseStyles({ root, item, input, dot, label, description, helperText });\n}\n\ninterface RadioGroupContextType {\n state?: ReturnType<typeof useRadioGroupState>;\n disabled?: boolean;\n size?: Size;\n}\n\nconst RadioGroupContext = createContext<RadioGroupContextType | undefined>(undefined);\n\nconst useRadioGroupContext = () => {\n const context = useContext(RadioGroupContext);\n return context;\n};\n\nexport interface RadioGroupStyleSlots {\n root?: StyleValue;\n label?: StyleValue;\n description?: StyleValue;\n group?: StyleValue;\n}\n\nexport type RadioGroupStylesProp = StylesProp<RadioGroupStyleSlots>;\n\nconst resolveRadioGroupBaseStyles = createStylesResolver([\n \"root\",\n \"label\",\n \"description\",\n \"group\",\n] as const);\n\nfunction resolveRadioGroupStyles(styles: RadioGroupStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveRadioGroupBaseStyles(styles);\n }\n\n const { root, label, description, group } = styles;\n return resolveRadioGroupBaseStyles({ root, label, description, group });\n}\n\nexport interface RadioGroupProps {\n /** Controlled selected radio value */\n value?: string;\n /** Initial selected value for uncontrolled usage */\n defaultValue?: string;\n /** Called when the selected value changes */\n onValueChange?: (value: string) => void;\n /** Whether all radios in the group are disabled */\n disabled?: boolean;\n /** Size of all radio buttons in the group */\n size?: Size;\n children: React.ReactNode;\n /** Additional CSS class names */\n className?: string;\n /** Accessible label for the radio group */\n label?: string;\n /** Descriptive text shown below the group label */\n description?: string;\n /** Classes applied to the root or named slots */\n styles?: RadioGroupStylesProp;\n}\n\nconst RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(\n (\n {\n value: controlledValue,\n defaultValue,\n onValueChange,\n disabled = false,\n size = \"md\",\n children,\n className,\n label,\n description,\n styles: stylesProp,\n },\n ref\n ) => {\n const state = useRadioGroupState({\n value: controlledValue,\n defaultValue,\n onChange: onValueChange,\n isDisabled: disabled,\n });\n\n useRadioGroup(\n {\n isDisabled: disabled,\n label,\n description,\n },\n state\n );\n\n const resolved = resolveRadioGroupStyles(stylesProp);\n\n return (\n <RadioGroupContext.Provider value={{ state, disabled, size }}>\n <div ref={ref} className={cn(className, resolved.root)} role=\"group\">\n {label && (\n <label\n className={cn(\"radio\", \"radio-label\", css[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\"radio\", \"radio-description\", css[\"radio-description\"], resolved.description)}\n >\n {description}\n </p>\n )}\n <div className={cn(css[\"radio-group\"], resolved.group)}>{children}</div>\n </div>\n </RadioGroupContext.Provider>\n );\n }\n);\n\nRadioGroup.displayName = \"RadioGroup\";\n\nexport interface RadioItemProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Value submitted when this radio is selected */\n value: string;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(\n (\n {\n className,\n size: sizeProp,\n disabled: disabledProp = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n value,\n id,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const radioGroupContext = useRadioGroupContext();\n const generatedId = useId();\n const radioId = id || `radio-${generatedId}`;\n\n if (!radioGroupContext?.state) {\n throw new Error(\"RadioItem must be used within a Radio.Group\");\n }\n\n const { state } = radioGroupContext;\n const size = sizeProp || radioGroupContext?.size || \"md\";\n const disabled = disabledProp ?? radioGroupContext?.disabled ?? false;\n const isSelected = state.selectedValue === value;\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergeRefs(ref, inputRef);\n\n const ariaLabelFromProps = props[\"aria-label\"];\n const ariaLabelValue = ariaLabelFromProps || (typeof label === \"string\" ? label : undefined);\n\n const { inputProps } = useRadio(\n {\n value,\n isDisabled: disabled,\n ...(ariaLabelValue && { \"aria-label\": ariaLabelValue }),\n },\n state,\n inputRef\n );\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef: rootRef,\n containerRef: rootRef,\n surfaceSelector: '[data-radio-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n });\n const resolved = resolveRadioStyles(stylesProp);\n\n return (\n <div\n ref={rootRef}\n className={cn(\"w-full\", css[\"radio-item\"], scopeProps.className, resolved.item)}\n data-disabled={disabled ? \"true\" : undefined}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n className={cn(\"relative\", css[\"radio-surface\"])}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n data-radio-focus-surface=\"true\"\n >\n <div\n className={cn(\"radio\", css.radio, css[size], className, resolved.root)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n role=\"presentation\"\n >\n <div className={cn(css[\"radio-dot\"], css[size], resolved.dot)} />\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(inputProps, focusProps, hoverProps))}\n ref={mergedRef}\n type=\"radio\"\n id={radioId}\n className={cn(css[\"radio-input\"], resolved.input)}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\"radio\", \"radio-label\", css[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n \"radio\",\n \"radio-description\",\n css[\"radio-description\"],\n resolved.description\n )}\n data-error={error ? \"true\" : undefined}\n >\n {description}\n </p>\n )}\n </div>\n )}\n {helperText && (\n <p\n className={cn(\"radio\", \"helper-text\", css[\"helper-text\"], resolved.helperText)}\n data-error={helperTextError ? \"true\" : undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioItem.displayName = \"RadioItem\";\n\nexport interface RadioProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\"> {\n /** Size of the radio button */\n size?: Size;\n /** Label text or element displayed next to the radio */\n label?: React.ReactNode;\n /** Secondary description shown below the label */\n description?: React.ReactNode;\n /** Helper text shown below the radio item */\n helperText?: React.ReactNode;\n /** Whether to style the helper text as an error */\n helperTextError?: boolean;\n /** Whether to apply error styling */\n error?: boolean;\n /** Classes applied to named slots */\n styles?: RadioStylesProp;\n}\n\nconst RadioBase = React.forwardRef<HTMLInputElement, RadioProps>(\n (\n {\n className,\n size = \"md\",\n disabled = false,\n error = false,\n label,\n description,\n helperText,\n helperTextError = false,\n checked: checkedProp,\n defaultChecked,\n onChange,\n id,\n styles: stylesProp,\n ...props\n },\n ref\n ) => {\n const [internalChecked, setInternalChecked] = React.useState(checkedProp ?? defaultChecked ?? false);\n const generatedId = useId();\n\n const isControlled = checkedProp !== undefined;\n const checked = isControlled ? checkedProp : internalChecked;\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n\n const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n if (!isControlled) {\n setInternalChecked(e.target.checked);\n }\n onChange?.(e);\n };\n\n const radioId = id || `radio-${generatedId}`;\n const inputRef = React.useRef<HTMLInputElement>(null);\n const rootRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergeRefs(ref, inputRef);\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef: rootRef,\n containerRef: rootRef,\n surfaceSelector: '[data-radio-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n });\n const resolved = resolveRadioStyles(stylesProp);\n\n return (\n <div\n ref={rootRef}\n className={cn(\"w-full\", css[\"radio-item\"], scopeProps.className, resolved.item)}\n data-disabled={disabled ? \"true\" : undefined}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n className={cn(\"relative\", css[\"radio-surface\"])}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n data-radio-focus-surface=\"true\"\n >\n <div\n className={cn(\"radio\", css.radio, css[size], className, resolved.root)}\n data-selected={checked ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n role=\"presentation\"\n >\n <div className={cn(css[\"radio-dot\"], css[size], resolved.dot)} />\n </div>\n <input\n {...asElementProps<\"input\">(mergeProps(focusProps, hoverProps))}\n ref={mergedRef}\n type=\"radio\"\n id={radioId}\n checked={checked}\n onChange={handleChange}\n disabled={disabled ?? false}\n className={cn(css[\"radio-input\"], resolved.input)}\n aria-label={typeof label === \"string\" ? label : undefined}\n suppressHydrationWarning\n {...props}\n />\n </div>\n {(label || description) && (\n <div className=\"flex flex-col gap-1\">\n {label && (\n <label\n htmlFor={radioId}\n className={cn(\"radio\", \"radio-label\", css[\"radio-label\"], resolved.label)}\n data-disabled={disabled ? \"true\" : undefined}\n suppressHydrationWarning\n >\n {label}\n </label>\n )}\n {description && (\n <p\n className={cn(\n \"radio\",\n \"radio-description\",\n css[\"radio-description\"],\n resolved.description\n )}\n data-error={error ? \"true\" : undefined}\n >\n {description}\n </p>\n )}\n </div>\n )}\n {helperText && (\n <p\n className={cn(\"radio\", \"helper-text\", css[\"helper-text\"], resolved.helperText)}\n data-error={helperTextError ? \"true\" : undefined}\n >\n {helperText}\n </p>\n )}\n </div>\n );\n }\n);\n\nRadioBase.displayName = \"Radio\";\n\nconst Radio = Object.assign(RadioBase, {\n Group: RadioGroup,\n Item: RadioItem,\n});\n\nexport { Radio };\n",
|
|
4552
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .radio-group {\n @apply flex flex-col gap-3;\n }\n\n .radio-item {\n @apply flex items-start gap-3 cursor-pointer select-none;\n position: relative;\n overflow: visible;\n }\n\n .radio-surface {\n @apply inline-flex shrink-0;\n border-radius: 9999px;\n }\n\n .radio-input {\n @apply absolute inset-0 h-full w-full cursor-pointer opacity-0;\n }\n\n .radio {\n --disabled-opacity: 0.6;\n\n @apply relative flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: 9999px;\n transition: all 200ms var(--ease-snappy-pop), transform 200ms var(--ease-snappy-pop);\n background-color: var(--background);\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n border-color: var(--background-selected-border);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-error=\"true\"][data-selected=\"true\"] {\n border-color: var(--background-selected-border);\n }\n\n &[data-focus-visible=\"true\"] {\n outline: none;\n }\n }\n\n .radio-item:active .radio {\n transform: scale(0.92);\n }\n\n .radio-dot {\n border-radius: 9999px;\n background-color: var(--dot-color);\n transform: scale(0);\n transform-origin: center;\n transition: transform 200ms var(--ease-snappy-pop), background-color 200ms var(--ease-snappy-pop);\n }\n\n .radio[data-selected=\"true\"] .radio-dot {\n background-color: var(--dot-selected-color);\n transform: scale(1);\n }\n\n @media (hover: hover) {\n .radio-item:not([data-disabled=\"true\"]):hover .radio {\n background-color: var(--background-hover);\n border-color: var(--background-hover-border);\n opacity: 0.9;\n }\n }\n\n .radio-item[data-disabled=\"true\"] .radio {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .radio-label {\n @apply cursor-pointer;\n color: var(--foreground);\n font-size: inherit;\n font-weight: var(--font-weight-medium, 500);\n line-height: inherit;\n transition: color 200ms var(--ease-snappy-pop);\n user-select: none;\n\n &[data-disabled=\"true\"] {\n color: var(--foreground-disabled, var(--foreground));\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n }\n\n .radio-description {\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n margin-top: 0.125rem;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-error=\"true\"] {\n color: var(--foreground-error, var(--foreground));\n }\n }\n\n .helper-text {\n color: var(--foreground);\n font-size: var(--text-sm, 0.875rem);\n margin-top: 0.5rem;\n margin-left: 2rem;\n transition: color 200ms var(--ease-snappy-pop);\n\n &[data-error=\"true\"] {\n color: var(--foreground-error, var(--foreground));\n }\n }\n\n .radio.sm {\n @apply h-4 w-4;\n }\n\n .radio.sm .radio-dot {\n width: 0.375rem;\n height: 0.375rem;\n }\n\n .radio.md {\n @apply h-5 w-5;\n }\n\n .radio.md .radio-dot {\n width: 0.625rem;\n height: 0.625rem;\n }\n\n .radio.lg {\n @apply h-6 w-6;\n }\n\n .radio.lg .radio-dot {\n width: 0.75rem;\n height: 0.75rem;\n }\n}\n",
|
|
4553
|
+
"cssTypes": "declare const styles: {\n \"radio-group\": string;\n \"radio-item\": string;\n \"radio-surface\": string;\n \"radio-input\": string;\n radio: string;\n \"radio-dot\": string;\n \"radio-label\": string;\n \"radio-description\": string;\n \"helper-text\": string;\n sm: string;\n md: string;\n lg: string;\n};\n\nexport default styles;\n"
|
|
4429
4554
|
},
|
|
4430
4555
|
"scroll": {
|
|
4431
|
-
"tsx": "\"use client\";\n\nimport React, {\n useRef,\n useLayoutEffect,\n useState,\n useCallback,\n} from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Mask } from \"../Mask\";\nimport css from \"./Scroll.module.css\";\nimport {\n SCROLL_RESTORE_AXIS_ATTR,\n SCROLL_RESTORE_DEBUG_ID_KEY,\n SCROLL_RESTORE_FLAG,\n SCROLL_RESTORE_STORAGE_KEY_ATTR,\n getBootstrapRestoredNode,\n getScrollRestoreDebugId,\n getScrollRestoreMetrics,\n getScrollPositionProperty,\n recordScrollRestoreTrace,\n} from \"./scripts/restore-scroll.constants\";\n\nexport interface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n}\n\nexport type ScrollStylesProp = StylesProp<ScrollStyleSlots>;\n\nexport interface ScrollProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n maxHeight?: string;\n maxWidth?: string;\n direction?: \"vertical\" | \"horizontal\";\n paddingY?: string | number;\n \"fade-y\"?: boolean;\n fadeDistance?: number;\n fadeSize?: number;\n enabled?: boolean;\n hide?: boolean;\n inline?: boolean;\n styles?: ScrollStylesProp;\n storageKey?: string;\n}\n\nconst resolveScrollBaseStyles = createStylesResolver([\n \"root\",\n \"content\",\n \"track\",\n \"thumb\",\n] as const);\n\nfunction resolveScrollStyles(styles: ScrollStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveScrollBaseStyles(styles);\n const { root, content, track, thumb } = styles;\n return resolveScrollBaseStyles({ root, content, track, thumb });\n}\n\nconst SCROLLBAR_VISIBILITY_EPSILON = 1;\n\nfunction getInitialScrollFadeVars(\n direction: ScrollProps[\"direction\"],\n fadeY: boolean,\n fadeSize: number,\n): React.CSSProperties {\n if (direction !== \"vertical\" || !fadeY) {\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": \"0%\",\n } as React.CSSProperties;\n }\n\n // SSR cannot know overflow or scroll position, so default to a bottom-only hint.\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": `${fadeSize}%`,\n } as React.CSSProperties;\n}\n\nfunction readStoredScrollOffset(storageKey: string): number | null {\n if (typeof window === \"undefined\") return null;\n\n try {\n const storedValue = window.sessionStorage.getItem(storageKey);\n if (storedValue === null) return null;\n\n const parsedValue = parseInt(storedValue, 10);\n return Number.isNaN(parsedValue) ? null : parsedValue;\n } catch {\n return null;\n }\n}\n\nfunction persistStoredScrollOffset(storageKey: string, scrollOffset: number): void {\n if (typeof window === \"undefined\") return;\n\n try {\n window.sessionStorage.setItem(storageKey, String(scrollOffset));\n } catch {\n // Ignore storage failures. The live scroll position is already updated.\n }\n}\n\nfunction hasPreHydrationScrollRestore(node: HTMLDivElement): boolean {\n return Boolean((node as HTMLDivElement & Record<string, unknown>)[SCROLL_RESTORE_FLAG]);\n}\n\nconst Scroll = React.forwardRef<HTMLDivElement, ScrollProps>(\n (\n {\n children,\n className,\n maxHeight,\n maxWidth,\n direction = \"vertical\",\n paddingY = 4,\n \"fade-y\": fadeY = false,\n fadeDistance = 5,\n fadeSize = 4,\n enabled = true,\n hide = true,\n inline = false,\n styles,\n storageKey,\n style: propsStyle,\n ...restProps\n },\n ref,\n ) => {\n const isHoriz = direction === \"horizontal\";\n\n // Axis-Agnostic property keys\n const clientSizeKey = isHoriz ? \"clientWidth\" : \"clientHeight\";\n const scrollSizeKey = isHoriz ? \"scrollWidth\" : \"scrollHeight\";\n const scrollPosKey = getScrollPositionProperty(direction);\n const clientPosKey = isHoriz ? \"clientX\" : \"clientY\";\n const trackSizeKey = isHoriz ? \"width\" : \"height\";\n const trackPosKey = isHoriz ? \"left\" : \"top\";\n\n const numPaddingY = typeof paddingY === \"number\" ? paddingY : parseInt(String(paddingY), 10) || 0;\n const strPaddingY = typeof paddingY === \"number\" ? `${paddingY}px` : String(paddingY);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const maskRef = useRef<HTMLDivElement>(null);\n const thumbRef = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, containerRef);\n\n const resolved = resolveScrollStyles(styles);\n\n const [needsScrollbar, setNeedsScrollbar] = useState(false);\n const [isHoveredRight, setIsHoveredRight] = useState(false);\n const [thumbSize, setThumbSize] = useState(0);\n const [thumbPosition, setThumbPosition] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [isScrolling, setIsScrolling] = useState(false);\n\n const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n const dragStartRef = useRef({ origin: 0, scrollOrigin: 0 });\n const thumbSizeRef = useRef(0);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const mutationObserverRef = useRef<MutationObserver | null>(null);\n\n const updateScrollbar = useCallback(() => {\n if (!containerRef.current || !contentRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n\n const viewportSize = content[clientSizeKey] || container[clientSizeKey];\n const contentSize = content[scrollSizeKey] || viewportSize;\n const currentScroll = content[scrollPosKey];\n const trackSize = isHoriz ? container[clientSizeKey] : container[clientSizeKey] - numPaddingY * 2;\n\n const maxScroll = Math.max(0, contentSize - viewportSize);\n const needs = maxScroll > SCROLLBAR_VISIBILITY_EPSILON;\n setNeedsScrollbar(needs);\n\n const scrollRatio = trackSize / Math.max(1, contentSize);\n const newThumbSize = Math.max(20, Math.min(trackSize, trackSize * scrollRatio));\n const scrollProgress = needs && maxScroll > 0 ? currentScroll / maxScroll : 0;\n const maxThumbPos = trackSize - newThumbSize;\n const newThumbPos = scrollProgress * maxThumbPos;\n\n setThumbSize(newThumbSize);\n thumbSizeRef.current = newThumbSize;\n setThumbPosition(newThumbPos);\n\n if (!isHoriz && maskRef.current) {\n const maskNode = maskRef.current;\n if (fadeY && needs) {\n const topP = Math.min(1, Math.max(0, currentScroll / fadeDistance));\n const botP = Math.min(1, Math.max(0, (maxScroll - currentScroll) / fadeDistance));\n maskNode.style.setProperty(\"--mask-top-fade\", `${topP * fadeSize}%`);\n maskNode.style.setProperty(\"--mask-bottom-fade\", `${botP * fadeSize}%`);\n } else {\n maskNode.style.setProperty(\"--mask-top-fade\", \"0%\");\n maskNode.style.setProperty(\"--mask-bottom-fade\", \"0%\");\n }\n }\n }, [isHoriz, clientSizeKey, scrollSizeKey, scrollPosKey, numPaddingY, fadeY, fadeDistance, fadeSize]);\n\n const cleanupScrollTimeout = useCallback(() => {\n if (scrollTimeoutRef.current) {\n clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = null;\n }\n }, []);\n\n const cleanupObservers = useCallback(() => {\n resizeObserverRef.current?.disconnect();\n mutationObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n mutationObserverRef.current = null;\n }, []);\n\n const cleanupDragListeners = useCallback(() => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"\";\n }, []);\n\n const restoreStoredScrollPosition = useCallback(() => {\n if (!storageKey || !contentRef.current) return;\n\n const contentNode = contentRef.current;\n const bootstrapNode = getBootstrapRestoredNode(storageKey);\n const sameNodeAsBootstrap = Boolean(bootstrapNode) && bootstrapNode === contentNode;\n const currentNodeId = getScrollRestoreDebugId(contentNode);\n const bootstrapNodeId = getScrollRestoreDebugId(bootstrapNode);\n const beforeMetrics = getScrollRestoreMetrics(contentNode, direction);\n const hasPreHydrationRestore = hasPreHydrationScrollRestore(contentNode);\n\n recordScrollRestoreTrace(\"client:layout-effect\", {\n storageKey,\n hasPreHydrationRestore,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n clientSize: beforeMetrics.clientSize,\n scrollSize: beforeMetrics.scrollSize,\n maxScroll: beforeMetrics.maxScroll,\n scrollOffset: beforeMetrics.scrollOffset,\n });\n\n if (hasPreHydrationRestore) {\n recordScrollRestoreTrace(\"client:skip-prehydrated\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n const savedOffset = readStoredScrollOffset(storageKey);\n if (savedOffset === null) {\n recordScrollRestoreTrace(\"client:no-stored-offset\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n contentNode[scrollPosKey] = savedOffset;\n\n const afterMetrics = getScrollRestoreMetrics(contentNode, direction);\n recordScrollRestoreTrace(\"client:fallback-restore\", {\n storageKey,\n storedOffset: savedOffset,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n beforeScrollOffset: beforeMetrics.scrollOffset,\n afterScrollOffset: afterMetrics.scrollOffset,\n clientSize: afterMetrics.clientSize,\n scrollSize: afterMetrics.scrollSize,\n maxScroll: afterMetrics.maxScroll,\n clamped: savedOffset !== afterMetrics.scrollOffset,\n clampedToZero: savedOffset > 0 && afterMetrics.scrollOffset === 0,\n });\n }, [direction, scrollPosKey, storageKey]);\n\n const connectObservers = useCallback(() => {\n cleanupObservers();\n updateScrollbar();\n\n const resizeObserver = new ResizeObserver(() => requestAnimationFrame(updateScrollbar));\n const mutationObserver = new MutationObserver(() => requestAnimationFrame(updateScrollbar));\n\n if (containerRef.current) resizeObserver.observe(containerRef.current);\n if (contentRef.current) {\n resizeObserver.observe(contentRef.current);\n mutationObserver.observe(contentRef.current, { childList: true, subtree: true });\n }\n\n resizeObserverRef.current = resizeObserver;\n mutationObserverRef.current = mutationObserver;\n }, [cleanupObservers, updateScrollbar]);\n\n const handleContentRef = useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node;\n if (!node) {\n cleanupObservers();\n cleanupDragListeners();\n cleanupScrollTimeout();\n return;\n }\n\n recordScrollRestoreTrace(\"client:content-ref\", {\n storageKey: storageKey ?? null,\n currentNodeId: getScrollRestoreDebugId(node),\n preHydrationFlag: hasPreHydrationScrollRestore(node),\n bootstrapDebugId: storageKey ? getScrollRestoreDebugId(getBootstrapRestoredNode(storageKey)) : null,\n debugIdPropertyKey: SCROLL_RESTORE_DEBUG_ID_KEY,\n });\n connectObservers();\n },\n [cleanupDragListeners, cleanupObservers, cleanupScrollTimeout, connectObservers, storageKey]\n );\n\n const handleScroll = useCallback(() => {\n updateScrollbar();\n if (storageKey && contentRef.current) {\n persistStoredScrollOffset(storageKey, contentRef.current[scrollPosKey]);\n }\n setIsScrolling(true);\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 1500);\n }, [updateScrollbar, storageKey, scrollPosKey]);\n\n const handleContainerMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!containerRef.current) return;\n const rect = containerRef.current.getBoundingClientRect();\n const mousePos = isHoriz ? e.clientY - rect.top : e.clientX - rect.left;\n const rectSize = isHoriz ? rect.height : rect.width;\n\n const newIsHovered = mousePos > rectSize - 20;\n if (newIsHovered !== isHoveredRight) setIsHoveredRight(newIsHovered);\n },\n [isHoriz, isHoveredRight]\n );\n\n const handleContainerMouseLeave = useCallback(() => setIsHoveredRight(false), []);\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!contentRef.current || !containerRef.current) return;\n\n const delta = e[clientPosKey] - dragStartRef.current.origin;\n const viewportSize = contentRef.current[clientSizeKey] || containerRef.current[clientSizeKey];\n const maxScroll = Math.max(0, contentRef.current[scrollSizeKey] - viewportSize);\n const scrollRatio = maxScroll / Math.max(1, viewportSize - thumbSizeRef.current);\n\n contentRef.current[scrollPosKey] = Math.max(\n 0,\n Math.min(maxScroll, dragStartRef.current.scrollOrigin + delta * scrollRatio)\n );\n },\n [clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey]\n );\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n cleanupDragListeners();\n }, [cleanupDragListeners]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!contentRef.current) return;\n e.preventDefault();\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [clientPosKey, scrollPosKey, handleMouseMove, handleMouseUp]\n );\n\n const handleTrackClick = useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current || !contentRef.current || !thumbRef.current) return;\n\n const rect = containerRef.current.getBoundingClientRect();\n const thumbRect = thumbRef.current.getBoundingClientRect();\n const rectStartKey = isHoriz ? \"left\" : \"top\";\n const rectEndKey = isHoriz ? \"right\" : \"bottom\";\n const padOffset = isHoriz ? 0 : numPaddingY;\n\n const clickPos = e[clientPosKey] - rect[rectStartKey] - padOffset;\n const relThumbStart = thumbRect[rectStartKey] - rect[rectStartKey] - padOffset;\n const relThumbEnd = thumbRect[rectEndKey] - rect[rectStartKey] - padOffset;\n\n // Ignore clicks directly on the thumb (handled by handleMouseDown)\n if (clickPos >= relThumbStart && clickPos <= relThumbEnd) return;\n\n const viewportSize = contentRef.current[clientSizeKey] || containerRef.current[clientSizeKey];\n const contentSize = contentRef.current[scrollSizeKey];\n const maxScroll = Math.max(0, contentSize - viewportSize);\n const trackSize = isHoriz\n ? containerRef.current[clientSizeKey]\n : containerRef.current[clientSizeKey] - numPaddingY * 2;\n\n const newThumbSize = Math.max(20, trackSize * (trackSize / contentSize));\n const targetThumbStart = clickPos - newThumbSize / 2;\n const maxThumbPos = trackSize - newThumbSize;\n const clampedThumbStart = Math.max(0, Math.min(maxThumbPos, targetThumbStart));\n\n const scrollProgress = maxThumbPos > 0 ? clampedThumbStart / maxThumbPos : 0;\n contentRef.current[scrollPosKey] = Math.max(0, Math.min(maxScroll, scrollProgress * maxScroll));\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [isHoriz, numPaddingY, clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey, handleMouseMove, handleMouseUp]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!contentRef.current || !isHoriz) return;\n e.preventDefault();\n\n const content = contentRef.current;\n const scrollAmount = e.deltaY || e.deltaX;\n const maxScroll = content.scrollWidth - content.clientWidth;\n\n content.scrollLeft = Math.max(0, Math.min(maxScroll, content.scrollLeft + scrollAmount));\n },\n [isHoriz]\n );\n\n useLayoutEffect(() => {\n restoreStoredScrollPosition();\n connectObservers();\n }, [restoreStoredScrollPosition, connectObservers, enabled]);\n\n const axisConstraintStyle = {\n ...(isHoriz\n ? (maxWidth ? { maxWidth } : {})\n : (maxHeight ? { maxHeight } : {})),\n };\n\n if (!enabled) {\n return (\n <div\n ref={ref}\n className={cn(\"scroll\", css.root, resolved.root, className)}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n ...axisConstraintStyle,\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = needsScrollbar && (!hide || isHoveredRight || isDragging || isScrolling) ? 1 : 0;\n\n return (\n <div\n ref={mergedRef}\n className={cn(\n \"scroll\",\n css.root,\n isHoriz ? css.horizontal : css.vertical,\n className,\n resolved.root\n )}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n ...axisConstraintStyle,\n ...(!isHoriz && strPaddingY ? { \"--scroll-padding-y\": strPaddingY } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-pressed={isDragging || undefined}\n data-inline={String(inline && needsScrollbar)}\n {...restProps}\n >\n <Mask\n ref={maskRef}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n overflow: \"hidden\",\n ...getInitialScrollFadeVars(direction, fadeY, fadeSize),\n } as React.CSSProperties}\n >\n {!isHoriz && fadeY ? <Mask.Fade /> : null}\n <div\n ref={handleContentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={isHoriz ? handleWheel : undefined}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n minHeight: 0,\n minWidth: 0,\n }}\n {...(storageKey\n ? {\n [SCROLL_RESTORE_STORAGE_KEY_ATTR]: storageKey,\n [SCROLL_RESTORE_AXIS_ATTR]: direction,\n }\n : {})}\n >\n {children}\n </div>\n </Mask>\n\n <div\n className={cn(\"scroll\", \"track\", css.track, resolved.track)}\n data-hide={String(hide)}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {needsScrollbar && (\n <div\n ref={thumbRef}\n className={cn(\"scroll\", \"thumb\", css.thumb, resolved.thumb)}\n style={{\n [trackSizeKey]: `${thumbSize}px`,\n [trackPosKey]: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nScroll.displayName = \"Scroll\";\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return (value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\n\nexport { Scroll };\n",
|
|
4556
|
+
"tsx": "\"use client\";\n\nimport React, {\n useRef,\n useLayoutEffect,\n useState,\n useCallback,\n} from \"react\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Mask } from \"../Mask\";\nimport css from \"./Scroll.module.css\";\nimport {\n SCROLL_RESTORE_AXIS_ATTR,\n SCROLL_RESTORE_DEBUG_ID_KEY,\n SCROLL_RESTORE_FLAG,\n SCROLL_RESTORE_STORAGE_KEY_ATTR,\n getBootstrapRestoredNode,\n getScrollRestoreDebugId,\n getScrollRestoreMetrics,\n getScrollPositionProperty,\n recordScrollRestoreTrace,\n} from \"./scripts/restore-scroll.constants\";\n\nexport interface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n}\n\nexport type ScrollStylesProp = StylesProp<ScrollStyleSlots>;\n\nexport interface ScrollProps extends React.HTMLAttributes<HTMLDivElement> {\n children: React.ReactNode;\n maxHeight?: string;\n maxWidth?: string;\n direction?: \"vertical\" | \"horizontal\";\n paddingY?: string | number;\n \"fade-y\"?: boolean;\n fadeDistance?: number;\n fadeSize?: number;\n enabled?: boolean;\n hide?: boolean;\n inline?: boolean;\n styles?: ScrollStylesProp;\n storageKey?: string;\n}\n\nconst resolveScrollBaseStyles = createStylesResolver([\n \"root\",\n \"content\",\n \"track\",\n \"thumb\",\n] as const);\n\nfunction resolveScrollStyles(styles: ScrollStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveScrollBaseStyles(styles);\n const { root, content, track, thumb } = styles;\n return resolveScrollBaseStyles({ root, content, track, thumb });\n}\n\nconst SCROLLBAR_VISIBILITY_EPSILON = 1;\n\nfunction getInitialScrollFadeVars(\n direction: ScrollProps[\"direction\"],\n fadeY: boolean,\n fadeSize: number,\n): React.CSSProperties {\n if (direction !== \"vertical\" || !fadeY) {\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": \"0%\",\n } as React.CSSProperties;\n }\n\n // SSR cannot know overflow or scroll position, so default to a bottom-only hint.\n return {\n \"--mask-top-fade\": \"0%\",\n \"--mask-bottom-fade\": `${fadeSize}%`,\n } as React.CSSProperties;\n}\n\nfunction readStoredScrollOffset(storageKey: string): number | null {\n if (typeof window === \"undefined\") return null;\n\n try {\n const storedValue = window.sessionStorage.getItem(storageKey);\n if (storedValue === null) return null;\n\n const parsedValue = parseInt(storedValue, 10);\n return Number.isNaN(parsedValue) ? null : parsedValue;\n } catch {\n return null;\n }\n}\n\nfunction persistStoredScrollOffset(storageKey: string, scrollOffset: number): void {\n if (typeof window === \"undefined\") return;\n\n try {\n window.sessionStorage.setItem(storageKey, String(scrollOffset));\n } catch {\n // Ignore storage failures. The live scroll position is already updated.\n }\n}\n\nfunction hasPreHydrationScrollRestore(node: HTMLDivElement): boolean {\n return Boolean((node as HTMLDivElement & Record<string, unknown>)[SCROLL_RESTORE_FLAG]);\n}\n\nconst Scroll = React.forwardRef<HTMLDivElement, ScrollProps>(\n (\n {\n children,\n className,\n maxHeight,\n maxWidth,\n direction = \"vertical\",\n paddingY = 4,\n \"fade-y\": fadeY = false,\n fadeDistance = 5,\n fadeSize = 4,\n enabled = true,\n hide = true,\n inline = false,\n styles,\n storageKey,\n style: propsStyle,\n ...restProps\n },\n ref,\n ) => {\n const isHoriz = direction === \"horizontal\";\n\n // Axis-Agnostic property keys\n const clientSizeKey = isHoriz ? \"clientWidth\" : \"clientHeight\";\n const scrollSizeKey = isHoriz ? \"scrollWidth\" : \"scrollHeight\";\n const scrollPosKey = getScrollPositionProperty(direction);\n const clientPosKey = isHoriz ? \"clientX\" : \"clientY\";\n const trackSizeKey = isHoriz ? \"width\" : \"height\";\n const trackPosKey = isHoriz ? \"left\" : \"top\";\n\n const numPaddingY = typeof paddingY === \"number\" ? paddingY : parseInt(String(paddingY), 10) || 0;\n const strPaddingY = typeof paddingY === \"number\" ? `${paddingY}px` : String(paddingY);\n\n const containerRef = useRef<HTMLDivElement>(null);\n const contentRef = useRef<HTMLDivElement>(null);\n const maskRef = useRef<HTMLDivElement>(null);\n const thumbRef = useRef<HTMLDivElement>(null);\n const mergedRef = useMergeRefs(ref, containerRef);\n\n const resolved = resolveScrollStyles(styles);\n\n const [needsScrollbar, setNeedsScrollbar] = useState(false);\n const [isHoveredRight, setIsHoveredRight] = useState(false);\n const [thumbSize, setThumbSize] = useState(0);\n const [thumbPosition, setThumbPosition] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [isScrolling, setIsScrolling] = useState(false);\n\n const scrollTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n const dragStartRef = useRef({ origin: 0, scrollOrigin: 0 });\n const thumbSizeRef = useRef(0);\n const resizeObserverRef = useRef<ResizeObserver | null>(null);\n const mutationObserverRef = useRef<MutationObserver | null>(null);\n\n const updateScrollbar = useCallback(() => {\n if (!containerRef.current || !contentRef.current) return;\n\n const container = containerRef.current;\n const content = contentRef.current;\n\n const viewportSize = content[clientSizeKey] || container[clientSizeKey];\n const contentSize = content[scrollSizeKey] || viewportSize;\n const currentScroll = content[scrollPosKey];\n const trackSize = isHoriz ? container[clientSizeKey] : container[clientSizeKey] - numPaddingY * 2;\n\n const maxScroll = Math.max(0, contentSize - viewportSize);\n const needs = maxScroll > SCROLLBAR_VISIBILITY_EPSILON;\n setNeedsScrollbar(needs);\n\n const scrollRatio = trackSize / Math.max(1, contentSize);\n const newThumbSize = Math.max(20, Math.min(trackSize, trackSize * scrollRatio));\n const scrollProgress = needs && maxScroll > 0 ? currentScroll / maxScroll : 0;\n const maxThumbPos = trackSize - newThumbSize;\n const newThumbPos = scrollProgress * maxThumbPos;\n\n setThumbSize(newThumbSize);\n thumbSizeRef.current = newThumbSize;\n setThumbPosition(newThumbPos);\n\n if (!isHoriz && maskRef.current) {\n const maskNode = maskRef.current;\n if (fadeY && needs) {\n const topP = Math.min(1, Math.max(0, currentScroll / fadeDistance));\n const botP = Math.min(1, Math.max(0, (maxScroll - currentScroll) / fadeDistance));\n maskNode.style.setProperty(\"--mask-top-fade\", `${topP * fadeSize}%`);\n maskNode.style.setProperty(\"--mask-bottom-fade\", `${botP * fadeSize}%`);\n } else {\n maskNode.style.setProperty(\"--mask-top-fade\", \"0%\");\n maskNode.style.setProperty(\"--mask-bottom-fade\", \"0%\");\n }\n }\n }, [isHoriz, clientSizeKey, scrollSizeKey, scrollPosKey, numPaddingY, fadeY, fadeDistance, fadeSize]);\n\n const cleanupScrollTimeout = useCallback(() => {\n if (scrollTimeoutRef.current) {\n clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = null;\n }\n }, []);\n\n const cleanupObservers = useCallback(() => {\n resizeObserverRef.current?.disconnect();\n mutationObserverRef.current?.disconnect();\n resizeObserverRef.current = null;\n mutationObserverRef.current = null;\n }, []);\n\n const cleanupDragListeners = useCallback(() => {\n document.removeEventListener(\"mousemove\", handleMouseMove);\n document.removeEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"\";\n }, []);\n\n const restoreStoredScrollPosition = useCallback(() => {\n if (!storageKey || !contentRef.current) return;\n\n const contentNode = contentRef.current;\n const bootstrapNode = getBootstrapRestoredNode(storageKey);\n const sameNodeAsBootstrap = Boolean(bootstrapNode) && bootstrapNode === contentNode;\n const currentNodeId = getScrollRestoreDebugId(contentNode);\n const bootstrapNodeId = getScrollRestoreDebugId(bootstrapNode);\n const beforeMetrics = getScrollRestoreMetrics(contentNode, direction);\n const hasPreHydrationRestore = hasPreHydrationScrollRestore(contentNode);\n\n recordScrollRestoreTrace(\"client:layout-effect\", {\n storageKey,\n hasPreHydrationRestore,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n clientSize: beforeMetrics.clientSize,\n scrollSize: beforeMetrics.scrollSize,\n maxScroll: beforeMetrics.maxScroll,\n scrollOffset: beforeMetrics.scrollOffset,\n });\n\n if (hasPreHydrationRestore) {\n recordScrollRestoreTrace(\"client:skip-prehydrated\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n const savedOffset = readStoredScrollOffset(storageKey);\n if (savedOffset === null) {\n recordScrollRestoreTrace(\"client:no-stored-offset\", {\n storageKey,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n });\n return;\n }\n\n contentNode[scrollPosKey] = savedOffset;\n\n const afterMetrics = getScrollRestoreMetrics(contentNode, direction);\n recordScrollRestoreTrace(\"client:fallback-restore\", {\n storageKey,\n storedOffset: savedOffset,\n sameNodeAsBootstrap,\n nodeReplaced: Boolean(bootstrapNode) && bootstrapNode !== contentNode,\n currentNodeId,\n bootstrapNodeId,\n beforeScrollOffset: beforeMetrics.scrollOffset,\n afterScrollOffset: afterMetrics.scrollOffset,\n clientSize: afterMetrics.clientSize,\n scrollSize: afterMetrics.scrollSize,\n maxScroll: afterMetrics.maxScroll,\n clamped: savedOffset !== afterMetrics.scrollOffset,\n clampedToZero: savedOffset > 0 && afterMetrics.scrollOffset === 0,\n });\n }, [direction, scrollPosKey, storageKey]);\n\n const connectObservers = useCallback(() => {\n cleanupObservers();\n updateScrollbar();\n\n const resizeObserver = new ResizeObserver(() => requestAnimationFrame(updateScrollbar));\n const mutationObserver = new MutationObserver(() => requestAnimationFrame(updateScrollbar));\n\n if (containerRef.current) resizeObserver.observe(containerRef.current);\n if (contentRef.current) {\n resizeObserver.observe(contentRef.current);\n mutationObserver.observe(contentRef.current, { childList: true, subtree: true });\n }\n\n resizeObserverRef.current = resizeObserver;\n mutationObserverRef.current = mutationObserver;\n }, [cleanupObservers, updateScrollbar]);\n\n const handleContentRef = useCallback(\n (node: HTMLDivElement | null) => {\n contentRef.current = node;\n if (!node) {\n cleanupObservers();\n cleanupDragListeners();\n cleanupScrollTimeout();\n return;\n }\n\n recordScrollRestoreTrace(\"client:content-ref\", {\n storageKey: storageKey ?? null,\n currentNodeId: getScrollRestoreDebugId(node),\n preHydrationFlag: hasPreHydrationScrollRestore(node),\n bootstrapDebugId: storageKey ? getScrollRestoreDebugId(getBootstrapRestoredNode(storageKey)) : null,\n debugIdPropertyKey: SCROLL_RESTORE_DEBUG_ID_KEY,\n });\n connectObservers();\n },\n [cleanupDragListeners, cleanupObservers, cleanupScrollTimeout, connectObservers, storageKey]\n );\n\n const handleScroll = useCallback(() => {\n updateScrollbar();\n if (storageKey && contentRef.current) {\n persistStoredScrollOffset(storageKey, contentRef.current[scrollPosKey]);\n }\n setIsScrolling(true);\n if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);\n scrollTimeoutRef.current = setTimeout(() => setIsScrolling(false), 1500);\n }, [updateScrollbar, storageKey, scrollPosKey]);\n\n const handleContainerMouseMove = useCallback(\n (e: React.MouseEvent<HTMLDivElement>) => {\n if (!containerRef.current) return;\n const rect = containerRef.current.getBoundingClientRect();\n const mousePos = isHoriz ? e.clientY - rect.top : e.clientX - rect.left;\n const rectSize = isHoriz ? rect.height : rect.width;\n\n const newIsHovered = mousePos > rectSize - 20;\n if (newIsHovered !== isHoveredRight) setIsHoveredRight(newIsHovered);\n },\n [isHoriz, isHoveredRight]\n );\n\n const handleContainerMouseLeave = useCallback(() => setIsHoveredRight(false), []);\n\n const handleMouseMove = useCallback(\n (e: MouseEvent) => {\n if (!contentRef.current || !containerRef.current) return;\n\n const delta = e[clientPosKey] - dragStartRef.current.origin;\n const viewportSize = contentRef.current[clientSizeKey] || containerRef.current[clientSizeKey];\n const maxScroll = Math.max(0, contentRef.current[scrollSizeKey] - viewportSize);\n const scrollRatio = maxScroll / Math.max(1, viewportSize - thumbSizeRef.current);\n\n contentRef.current[scrollPosKey] = Math.max(\n 0,\n Math.min(maxScroll, dragStartRef.current.scrollOrigin + delta * scrollRatio)\n );\n },\n [clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey]\n );\n\n const handleMouseUp = useCallback(() => {\n setIsDragging(false);\n cleanupDragListeners();\n }, [cleanupDragListeners]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n if (!contentRef.current) return;\n e.preventDefault();\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [clientPosKey, scrollPosKey, handleMouseMove, handleMouseUp]\n );\n\n const handleTrackClick = useCallback(\n (e: React.MouseEvent) => {\n if (!containerRef.current || !contentRef.current || !thumbRef.current) return;\n\n const rect = containerRef.current.getBoundingClientRect();\n const thumbRect = thumbRef.current.getBoundingClientRect();\n const rectStartKey = isHoriz ? \"left\" : \"top\";\n const rectEndKey = isHoriz ? \"right\" : \"bottom\";\n const padOffset = isHoriz ? 0 : numPaddingY;\n\n const clickPos = e[clientPosKey] - rect[rectStartKey] - padOffset;\n const relThumbStart = thumbRect[rectStartKey] - rect[rectStartKey] - padOffset;\n const relThumbEnd = thumbRect[rectEndKey] - rect[rectStartKey] - padOffset;\n\n // Ignore clicks directly on the thumb (handled by handleMouseDown)\n if (clickPos >= relThumbStart && clickPos <= relThumbEnd) return;\n\n const viewportSize = contentRef.current[clientSizeKey] || containerRef.current[clientSizeKey];\n const contentSize = contentRef.current[scrollSizeKey];\n const maxScroll = Math.max(0, contentSize - viewportSize);\n const trackSize = isHoriz\n ? containerRef.current[clientSizeKey]\n : containerRef.current[clientSizeKey] - numPaddingY * 2;\n\n const newThumbSize = Math.max(20, trackSize * (trackSize / contentSize));\n const targetThumbStart = clickPos - newThumbSize / 2;\n const maxThumbPos = trackSize - newThumbSize;\n const clampedThumbStart = Math.max(0, Math.min(maxThumbPos, targetThumbStart));\n\n const scrollProgress = maxThumbPos > 0 ? clampedThumbStart / maxThumbPos : 0;\n contentRef.current[scrollPosKey] = Math.max(0, Math.min(maxScroll, scrollProgress * maxScroll));\n\n dragStartRef.current = {\n origin: e[clientPosKey],\n scrollOrigin: contentRef.current[scrollPosKey],\n };\n setIsDragging(true);\n document.addEventListener(\"mousemove\", handleMouseMove);\n document.addEventListener(\"mouseup\", handleMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [isHoriz, numPaddingY, clientPosKey, clientSizeKey, scrollPosKey, scrollSizeKey, handleMouseMove, handleMouseUp]\n );\n\n const handleWheel = useCallback(\n (e: React.WheelEvent) => {\n if (!contentRef.current || !isHoriz) return;\n e.preventDefault();\n\n const content = contentRef.current;\n const scrollAmount = e.deltaY || e.deltaX;\n const maxScroll = content.scrollWidth - content.clientWidth;\n\n content.scrollLeft = Math.max(0, Math.min(maxScroll, content.scrollLeft + scrollAmount));\n },\n [isHoriz]\n );\n\n useLayoutEffect(() => {\n restoreStoredScrollPosition();\n connectObservers();\n }, [restoreStoredScrollPosition, connectObservers, enabled]);\n\n const axisConstraintStyle = {\n ...(isHoriz\n ? (maxWidth ? { maxWidth } : {})\n : (maxHeight ? { maxHeight } : {})),\n };\n\n if (!enabled) {\n return (\n <div\n ref={ref}\n className={cn(\"scroll\", css.root, resolved.root, className)}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n ...axisConstraintStyle,\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = needsScrollbar && (!hide || isHoveredRight || isDragging || isScrolling) ? 1 : 0;\n\n return (\n <div\n ref={mergedRef}\n className={cn(\n \"scroll\",\n css.root,\n isHoriz ? css.horizontal : css.vertical,\n className,\n resolved.root\n )}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n ...axisConstraintStyle,\n ...(!isHoriz && strPaddingY ? { \"--scroll-padding-y\": strPaddingY } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-pressed={isDragging || undefined}\n data-inline={String(inline && needsScrollbar)}\n {...restProps}\n >\n <Mask\n ref={maskRef}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n overflow: \"hidden\",\n ...getInitialScrollFadeVars(direction, fadeY, fadeSize),\n } as React.CSSProperties}\n >\n {!isHoriz && fadeY ? <Mask.Fade /> : null}\n <div\n ref={handleContentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={isHoriz ? handleWheel : undefined}\n style={{\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\",\n minHeight: 0,\n minWidth: 0,\n }}\n {...(storageKey\n ? {\n [SCROLL_RESTORE_STORAGE_KEY_ATTR]: storageKey,\n [SCROLL_RESTORE_AXIS_ATTR]: direction,\n }\n : {})}\n >\n {children}\n </div>\n </Mask>\n\n <div\n className={cn(\"scroll\", \"track\", css.track, resolved.track)}\n data-hide={String(hide)}\n style={{\n opacity: showOpacity,\n pointerEvents: needsScrollbar ? \"auto\" : \"none\",\n }}\n onMouseDown={handleTrackClick}\n >\n {needsScrollbar && (\n <div\n ref={thumbRef}\n className={cn(\"scroll\", \"thumb\", css.thumb, resolved.thumb)}\n style={{\n [trackSizeKey]: `${thumbSize}px`,\n [trackPosKey]: `${thumbPosition}px`,\n }}\n onMouseDown={handleMouseDown}\n />\n )}\n </div>\n </div>\n );\n }\n);\n\nScroll.displayName = \"Scroll\";\n\nexport { Scroll };\n",
|
|
4432
4557
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n min-height: 0;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n }\n\n .horizontal {\n --scrollbar-height: 12px;\n }\n\n .content {\n @apply h-full w-full;\n overflow: auto;\n }\n\n .vertical .content {\n overflow-y: auto;\n overflow-x: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .vertical[data-inline=\"true\"] .content {\n padding-right: 16px;\n }\n\n .horizontal .content {\n overflow-x: auto;\n overflow-y: hidden;\n scrollbar-width: none;\n -webkit-overflow-scrolling: touch;\n }\n\n .horizontal[data-inline=\"true\"] .content {\n padding-bottom: 16px;\n }\n\n .vertical .content::-webkit-scrollbar,\n .horizontal .content::-webkit-scrollbar { display: none; }\n\n .track {\n @apply absolute;\n z-index: 10;\n background-color: var(--track-background);\n }\n\n .track[data-hide=\"true\"] {\n transition-property: opacity;\n transition-duration: 200ms;\n }\n\n .vertical .track {\n right: 4px;\n top: var(--scroll-padding-y, 0);\n width: 12px;\n height: calc(100% - 2 * var(--scroll-padding-y, 0));\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs, 0.25rem) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover {\n background-color: var(--thumb-background-hover);\n }\n\n .root[data-pressed] .thumb {\n background-color: var(--thumb-background-pressed);\n }\n\n .vertical .thumb {\n width: 6px;\n margin-left: 6px;\n transition-property: background-color, width, margin-left;\n transition-duration: 150ms;\n }\n\n .vertical .thumb:hover,\n .vertical[data-pressed] .thumb {\n width: 8px;\n margin-left: 4px;\n }\n\n .horizontal .thumb {\n height: 6px;\n margin-top: 6px;\n transition-property: background-color, height, margin-top;\n transition-duration: 150ms;\n }\n\n .horizontal .thumb:hover,\n .horizontal[data-pressed] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
|
|
4433
4558
|
"cssTypes": "export const root: string;\nexport const vertical: string;\nexport const horizontal: string;\nexport const content: string;\nexport const track: string;\nexport const thumb: string;\n\ndeclare const styles: {\n root: string;\n vertical: string;\n horizontal: string;\n content: string;\n track: string;\n thumb: string;\n};\n\nexport default styles;\n"
|
|
4434
4559
|
},
|
|
4435
4560
|
"select": {
|
|
4436
|
-
"tsx": "import * as React from \"react\"\nimport { Key } from \"@react-types/shared\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport styles from \"./Select.module.css\"\nimport { useListNavigation, useMergedRef, handleListKeyDown, focusAdjacentTabStop, type ItemData } from \"./Select.shared\"\n\nexport type SelectItemData = ItemData\n\nexport type SelectTriggerMode = \"click\" | \"hover\"\nexport type SelectMode = \"single\" | \"multiple\"\n\nexport interface SelectStyleSlots {\n root?: StyleValue;\n}\n\nexport type SelectStylesProp = StylesProp<SelectStyleSlots>;\n\nexport interface SelectContextValue {\n isOpen: boolean\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>\n contentPlacement: \"top\" | \"bottom\"\n setContentPlacement: React.Dispatch<React.SetStateAction<\"top\" | \"bottom\">>\n triggerType: \"button\" | \"input\"\n mode: SelectMode\n selectedKey: Key | null\n selectedKeys?: Set<Key>\n selectedTextValue: string\n onSelect: (key: Key) => void\n onToggle?: (key: Key) => void\n triggerRef: React.MutableRefObject<HTMLElement | null>\n wrapperRef: React.MutableRefObject<HTMLElement | null>\n contentRef: React.MutableRefObject<HTMLElement | null>\n triggerProps: any\n isFocused: boolean\n isFocusVisible: boolean\n isPressed: boolean\n isHovered: boolean\n isDisabled: boolean\n items: SelectItemData[]\n registerItem: (key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => void\n unregisterItem: (key: Key) => void\n searchValue: string\n setSearchValue: React.Dispatch<React.SetStateAction<string>>\n filteredItems: SelectItemData[]\n visibleKeys: Set<Key>\n focusedKey: Key | null\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>\n navigateToNextItem: () => void\n navigateToPrevItem: () => void\n selectFocusedItem: () => void\n isFocusedItemSubmenu: () => boolean\n maxItems: number\n triggerMode: SelectTriggerMode\n handleHoverIntent: (isHovering: boolean) => void\n mouseMoveDetectedRef: React.MutableRefObject<boolean>\n keyboardScrollIntentRef: React.MutableRefObject<boolean>\n markKeyboardNavigation: () => void\n moveFocusFromTrigger: (direction: 1 | -1) => boolean\n filter?: (item: any) => boolean\n contentId: string\n hasExternalValue: boolean\n restoreFocus: (target?: \"auto\" | \"trigger\" | \"row\") => void\n}\n\nconst SelectContext = React.createContext<SelectContextValue | null>(null)\n\nexport function useSelectContext() {\n const context = React.useContext(SelectContext)\n if (!context) {\n throw new Error(\"Select component must be used within Select root\")\n }\n return context\n}\n\nexport interface SelectProps<T = any> extends React.PropsWithChildren {\n /** Selection mode: \"single\" for one item, \"multiple\" for multi-item selection */\n mode?: SelectMode\n /** External items array — used when items are provided as data rather than JSX */\n items?: Array<T>\n /** Controlled selected key for single-select mode */\n selectedKey?: Key | null\n /** Default selected key for uncontrolled single-select */\n defaultSelectedKey?: Key | null\n /** Controlled selected keys for multi-select mode */\n selectedKeys?: Key[]\n /** Default selected keys for uncontrolled multi-select */\n defaultSelectedKeys?: Key[]\n /** Default display text shown in the trigger when nothing is selected */\n defaultValue?: string | null\n /** Display text for the currently selected value — used for SSR/SSG to avoid\n * flash of placeholder before items register. Provide alongside selectedKey or\n * defaultSelectedKey so the correct label renders on the first pass. */\n valueLabel?: string\n /** Called when selection changes; receives a single key (single) or key array (multiple) */\n onSelectionChange?: (value: any) => void\n /** Disables the entire select and prevents interaction */\n isDisabled?: boolean\n /** Focuses the trigger automatically on mount */\n autoFocus?: boolean\n /** Maximum number of items visible before the dropdown scrolls */\n maxItems?: number\n /** Additional CSS class for the root wrapper */\n className?: string\n /** How the dropdown opens: \"click\" (default) or \"hover\" */\n trigger?: SelectTriggerMode\n /** Custom filter predicate applied to the items array */\n filter?: (item: T) => boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SelectStylesProp;\n}\n\nconst resolveSelectBaseStyles = createStylesResolver(['root'] as const);\n\nfunction resolveSelectStyles(styles: SelectStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveSelectBaseStyles(styles)\n const { root } = styles\n return resolveSelectBaseStyles({ root })\n}\n\nconst Select = React.forwardRef<HTMLDivElement, SelectProps<any>>(\n (\n {\n mode = \"single\",\n items: propItems = [],\n selectedKey: controlledSelectedKey,\n defaultSelectedKey,\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys = [],\n defaultValue,\n valueLabel,\n onSelectionChange,\n isDisabled = false,\n autoFocus = false,\n maxItems = 6,\n children,\n className,\n trigger: triggerMode = \"click\",\n filter,\n styles: stylesProp,\n },\n ref\n ) => {\n const triggerRef = React.useRef<HTMLElement>(null)\n const wrapperRef = React.useRef<HTMLElement>(null)\n const contentRef = React.useRef<HTMLElement>(null)\n const mouseMoveDetectedRef = React.useRef(true)\n const itemExtrasRef = React.useRef<Map<Key, { onSelect?: () => void; isSubmenuTrigger?: boolean }>>(new Map())\n const [isOpen, setIsOpen] = React.useState(false)\n const [contentPlacement, setContentPlacement] = React.useState<\"top\" | \"bottom\">(\"bottom\")\n const contentId = React.useId()\n const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const keyboardScrollIntentRef = React.useRef(false)\n\n const handleHoverIntent = React.useCallback((isHovering: boolean) => {\n if (triggerMode !== \"hover\" || isDisabled) return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n hoverTimeoutRef.current = null\n }\n\n if (isHovering) {\n setIsOpen(true)\n } else {\n hoverTimeoutRef.current = setTimeout(() => {\n setIsOpen(false)\n }, 100)\n }\n }, [triggerMode, isDisabled])\n\n React.useEffect(() => {\n if (!isOpen || triggerMode !== \"hover\" || isDisabled) return\n\n const handleMouseMove = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const isOver = wrapperRef.current?.contains(target) ||\n contentRef.current?.contains(target)\n\n if (!isOver) {\n handleHoverIntent(false)\n } else {\n handleHoverIntent(true)\n }\n }\n\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [isOpen, triggerMode, isDisabled, handleHoverIntent])\n\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = React.useState<Key | null>(\n defaultSelectedKey ?? null\n )\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n new Set(defaultSelectedKeys)\n )\n const [selectedTextValue, setSelectedTextValue] = React.useState(valueLabel ?? defaultValue ?? \"\")\n const selectedKey = controlledSelectedKey !== undefined ? controlledSelectedKey : uncontrolledSelectedKey\n const selectedKeys = controlledSelectedKeys !== undefined ? new Set(controlledSelectedKeys) : uncontrolledSelectedKeys\n\n const nav = useListNavigation({\n isOpen,\n externalItems: propItems.length > 0 ? propItems : undefined,\n filter: filter ? (item: any) => filter({ ...item, label: item.textValue } as any) : undefined\n })\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n return itemExtrasRef.current.get(nav.focusedKey)?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const markKeyboardNavigation = React.useCallback(() => {\n mouseMoveDetectedRef.current = false\n keyboardScrollIntentRef.current = true\n }, [])\n\n const moveFocusFromTrigger = React.useCallback((direction: 1 | -1) => {\n const triggerElement = triggerRef.current\n if (!triggerElement) return false\n return focusAdjacentTabStop(triggerElement, direction, wrapperRef.current)\n }, [])\n\n const restoreFocus = React.useCallback((target: \"auto\" | \"trigger\" | \"row\" = \"auto\") => {\n const triggerElement = triggerRef.current\n if (!triggerElement) return\n\n const ownerRow = triggerElement.closest<HTMLElement>('[data-list-focus-owner=\"true\"]')\n const focusTarget = target === \"row\"\n ? ownerRow\n : target === \"trigger\"\n ? triggerElement\n : ownerRow ?? triggerElement\n\n focusTarget?.focus({ preventScroll: true })\n }, [])\n\n const onSelect = React.useCallback((key: Key) => {\n const item = nav.items.find(i => i.key === key)\n if (item) {\n setSelectedTextValue(item.textValue)\n }\n if (controlledSelectedKey === undefined) {\n setUncontrolledSelectedKey(key)\n }\n onSelectionChange?.(key)\n setIsOpen(false)\n nav.setSearchValue(\"\")\n restoreFocus()\n }, [controlledSelectedKey, onSelectionChange, nav.items, restoreFocus])\n\n const onToggle = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(newKeys)\n }\n onSelectionChange?.(Array.from(newKeys))\n }, [selectedKeys, controlledSelectedKeys, onSelectionChange])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey !== null) {\n const item = nav.enabledFilteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n if (extras?.onSelect) {\n extras.onSelect()\n } else if (mode === \"multiple\") {\n onToggle(nav.focusedKey)\n } else {\n onSelect(nav.focusedKey)\n }\n }\n }\n }, [nav.focusedKey, nav.enabledFilteredItems, onSelect, onToggle, mode])\n\n React.useEffect(() => {\n if (isOpen) {\n // Only initialize focusedKey if it's not already valid\n if (nav.focusedKey !== null && nav.visibleKeys.has(nav.focusedKey)) {\n const item = nav.filteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n return // Keep current keyboard focus, don't reset it\n }\n }\n\n const focusKey = mode === \"multiple\" && selectedKeys.size > 0\n ? Array.from(selectedKeys)[0]\n : selectedKey\n\n if (focusKey !== null && nav.visibleKeys.has(focusKey)) {\n const item = nav.filteredItems.find(item => item.key === focusKey)\n if (item && !item.isDisabled) {\n nav.setFocusedKey(focusKey)\n return\n }\n }\n if (nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n } else {\n nav.setFocusedKey(null)\n }\n }\n }, [isOpen, selectedKey, selectedKeys, nav.visibleKeys, nav.enabledFilteredItems, nav.filteredItems, mode, nav.focusedKey])\n\n const { buttonProps, isPressed } = useButton({\n isDisabled,\n onPress: (e) => {\n if (isDisabled) return\n // Keyboard interactions are handled by onKeyDown to prevent conflicts\n if (e.pointerType !== 'keyboard') {\n setIsOpen(prev => !prev)\n }\n },\n }, triggerRef)\n const { focusProps, isFocused, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({\n isDisabled,\n onHoverStart: () => handleHoverIntent(true),\n onHoverEnd: () => handleHoverIntent(false),\n })\n\n const triggerProps = mergeProps(buttonProps, focusProps, hoverProps, {\n 'aria-haspopup': 'listbox' as const,\n 'aria-expanded': isOpen,\n 'aria-controls': isOpen ? contentId : undefined,\n 'aria-disabled': isDisabled || undefined,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || (e.key === ' ' && !isDisabled)) {\n e.preventDefault()\n setIsOpen(true)\n }\n return\n }\n\n if (e.key === 'Tab') {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n const direction = e.shiftKey ? -1 : 1\n if (moveFocusFromTrigger(direction as 1 | -1)) {\n e.preventDefault()\n }\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n markKeyboardNavigation()\n }\n\n handleListKeyDown(e, {\n navigateNext: nav.navigateToNextItem,\n navigatePrev: nav.navigateToPrevItem,\n confirm: selectFocusedItem,\n close: () => {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n restoreFocus()\n },\n filteredItems: nav.filteredItems,\n setFocusedKey: nav.setFocusedKey,\n })\n },\n })\n\n React.useEffect(() => {\n if (autoFocus && triggerRef.current) {\n triggerRef.current.focus({ preventScroll: true })\n }\n }, [autoFocus])\n\n React.useEffect(() => {\n if (mode === \"single\") {\n if (selectedKey === null) {\n setSelectedTextValue(\"\")\n } else {\n const selectedItem = nav.items.find(item => item.key === selectedKey)\n if (selectedItem) {\n setSelectedTextValue(selectedItem.textValue)\n } else if (valueLabel !== undefined) {\n setSelectedTextValue(valueLabel)\n } else if (defaultValue !== undefined && defaultValue !== null) {\n setSelectedTextValue(defaultValue)\n }\n }\n }\n }, [selectedKey, nav.items, mode, defaultValue, valueLabel])\n\n const rootRef = useMergedRef<HTMLDivElement>(wrapperRef, ref)\n\n const childrenArray = React.Children.toArray(children)\n const trigger = childrenArray.find(child => React.isValidElement(child) && (\n (child.type as any)?.displayName === 'SelectTrigger' ||\n (child.type as any)?.displayName === 'SearchableTrigger'\n ))\n const contentItems = childrenArray.filter(child => React.isValidElement(child) && ((child.type as any)?.displayName === 'SelectContent' || (child.type as any)?.displayName === 'SearchableContent'))\n const otherContent = childrenArray.filter(child => !React.isValidElement(child) || (\n (child.type as any)?.displayName !== 'SelectTrigger' &&\n (child.type as any)?.displayName !== 'SearchableTrigger' &&\n (child.type as any)?.displayName !== 'SelectContent' &&\n (child.type as any)?.displayName !== 'SearchableContent'\n ))\n const hasExternalValue = otherContent.some(child => (\n React.isValidElement(child) && (child.type as any)?.displayName === 'SelectValue'\n ))\n const triggerType = React.isValidElement(trigger) && (trigger.type as any)?.displayName === 'SearchableTrigger'\n ? 'input'\n : 'button'\n\n const resolvedStyles = resolveSelectStyles(stylesProp);\n\n return (\n <SelectContext.Provider\n value={{\n isOpen,\n setIsOpen,\n contentPlacement,\n setContentPlacement,\n triggerType,\n mode,\n selectedKey,\n selectedKeys: mode === \"multiple\" ? selectedKeys : undefined,\n selectedTextValue,\n onSelect,\n onToggle: mode === \"multiple\" ? onToggle : undefined,\n triggerRef,\n wrapperRef,\n contentRef,\n triggerProps,\n isFocused,\n isFocusVisible,\n isPressed,\n isHovered,\n isDisabled,\n items: nav.items,\n registerItem,\n unregisterItem,\n searchValue: nav.searchValue,\n setSearchValue: nav.setSearchValue,\n filteredItems: nav.filteredItems,\n visibleKeys: nav.visibleKeys,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n maxItems,\n triggerMode,\n handleHoverIntent,\n mouseMoveDetectedRef,\n keyboardScrollIntentRef,\n markKeyboardNavigation,\n moveFocusFromTrigger,\n filter,\n contentId,\n hasExternalValue,\n restoreFocus,\n }}\n >\n <div\n ref={rootRef}\n className={cn('select', styles.select, className, resolvedStyles.root)}\n data-mode={mode}\n >\n {otherContent}\n {trigger}\n {contentItems}\n </div>\n </SelectContext.Provider>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, SelectContext }\n",
|
|
4437
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n --trigger-padding-inline: calc(var(--spacing) * 2);\n --trigger-padding-block: calc(var(--spacing) * 1.75);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n font-size: var(--foreground-size);\n\n @apply p-0 gap-0 w-full flex-row items-center;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n }\n\n &[data-open=\"true\"] {\n background-color: var(--background-hover);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not([data-disabled]):hover .icon-section,\n &:not([data-disabled]):hover .value-section:not(:empty) {\n background-color: var(--background-hover);\n }\n }\n\n &[data-focus-visible=\"true\"] {\n
|
|
4438
|
-
"cssTypes": "declare const styles: {\n select: string;\n \"select-split\": string;\n trigger: string;\n \"trigger-compact\": string;\n input: string;\n \"search-trigger\": string;\n \"search-value-section\": string;\n \"search-content-input\": string;\n \"search-icon-section\": string;\n \"search-wrapper\": string;\n \"value-section\": string;\n \"icon-section\": string;\n icon: string;\n value: string;\n \"value-icon\": string;\n \"value-text\": string;\n \"value-chevron\": string;\n \"content-root\": string;\n content: string;\n viewport: string;\n list: string;\n item: string;\n \"item-icon\": string;\n \"item-indicator\": string;\n \"item-text\": string;\n \"item-content\": string;\n \"item-description\": string;\n \"item-with-description\": string;\n \"item-icon-with-description\": string;\n \"item-indicator-with-description\": string;\n separator: string;\n \"scroll-button\": string;\n placeholder: string;\n \"icon-prefix\": string;\n \"sub-trigger\": string;\n \"sub-trigger-chevron\": string;\n \"sub-content-root\": string;\n \"sub-content\": string;\n};\n\nexport default styles;\n"
|
|
4561
|
+
"tsx": "import * as React from \"react\"\nimport { Key } from \"@react-types/shared\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useButton } from \"@react-aria/button\";\n\nimport { cn, type StyleValue } from \"./utils\"\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport styles from \"./Select.module.css\"\nimport { useListNavigation, handleListKeyDown, focusAdjacentTabStop, type ItemData } from \"./Select.shared\"\n\nexport type SelectItemData = ItemData\n\nexport type SelectTriggerMode = \"click\" | \"hover\"\nexport type SelectMode = \"single\" | \"multiple\"\n\nexport interface SelectStyleSlots {\n root?: StyleValue;\n}\n\nexport type SelectStylesProp = StylesProp<SelectStyleSlots>;\n\nexport interface SelectContextValue {\n isOpen: boolean\n setIsOpen: React.Dispatch<React.SetStateAction<boolean>>\n contentPlacement: \"top\" | \"bottom\"\n setContentPlacement: React.Dispatch<React.SetStateAction<\"top\" | \"bottom\">>\n triggerType: \"button\" | \"input\"\n mode: SelectMode\n selectedKey: Key | null\n selectedKeys?: Set<Key>\n selectedTextValue: string\n onSelect: (key: Key) => void\n onToggle?: (key: Key) => void\n triggerRef: React.MutableRefObject<HTMLElement | null>\n wrapperRef: React.MutableRefObject<HTMLElement | null>\n contentRef: React.MutableRefObject<HTMLElement | null>\n triggerProps: any\n isFocused: boolean\n isFocusVisible: boolean\n isPressed: boolean\n isHovered: boolean\n isDisabled: boolean\n items: SelectItemData[]\n registerItem: (key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => void\n unregisterItem: (key: Key) => void\n searchValue: string\n setSearchValue: React.Dispatch<React.SetStateAction<string>>\n filteredItems: SelectItemData[]\n visibleKeys: Set<Key>\n focusedKey: Key | null\n setFocusedKey: React.Dispatch<React.SetStateAction<Key | null>>\n navigateToNextItem: () => void\n navigateToPrevItem: () => void\n selectFocusedItem: () => void\n isFocusedItemSubmenu: () => boolean\n maxItems: number\n triggerMode: SelectTriggerMode\n handleHoverIntent: (isHovering: boolean) => void\n mouseMoveDetectedRef: React.MutableRefObject<boolean>\n keyboardScrollIntentRef: React.MutableRefObject<boolean>\n markKeyboardNavigation: () => void\n moveFocusFromTrigger: (direction: 1 | -1) => boolean\n filter?: (item: any) => boolean\n contentId: string\n hasExternalValue: boolean\n restoreFocus: (target?: \"auto\" | \"trigger\" | \"row\") => void\n}\n\nconst SelectContext = React.createContext<SelectContextValue | null>(null)\n\nexport function useSelectContext() {\n const context = React.useContext(SelectContext)\n if (!context) {\n throw new Error(\"Select component must be used within Select root\")\n }\n return context\n}\n\nexport interface SelectProps<T = any> extends React.HTMLAttributes<HTMLDivElement> {\n /** Selection mode: \"single\" for one item, \"multiple\" for multi-item selection */\n mode?: SelectMode\n /** External items array — used when items are provided as data rather than JSX */\n items?: Array<T>\n /** Controlled selected key for single-select mode */\n selectedKey?: Key | null\n /** Default selected key for uncontrolled single-select */\n defaultSelectedKey?: Key | null\n /** Controlled selected keys for multi-select mode */\n selectedKeys?: Key[]\n /** Default selected keys for uncontrolled multi-select */\n defaultSelectedKeys?: Key[]\n /** Default display text shown in the trigger when nothing is selected */\n defaultValue?: string\n /** Display text for the currently selected value — used for SSR/SSG to avoid\n * flash of placeholder before items register. Provide alongside selectedKey or\n * defaultSelectedKey so the correct label renders on the first pass. */\n valueLabel?: string\n /** Called when selection changes; receives a single key (single) or key array (multiple) */\n onSelectionChange?: (value: any) => void\n /** Disables the entire select and prevents interaction */\n isDisabled?: boolean\n /** Focuses the trigger automatically on mount */\n autoFocus?: boolean\n /** Maximum number of items visible before the dropdown scrolls */\n maxItems?: number\n /** Additional CSS class for the root wrapper */\n className?: string\n /** How the dropdown opens: \"click\" (default) or \"hover\" */\n trigger?: SelectTriggerMode\n /** Custom filter predicate applied to the items array */\n filter?: (item: T) => boolean\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SelectStylesProp;\n}\n\nconst resolveSelectBaseStyles = createStylesResolver(['root'] as const);\n\nfunction resolveSelectStyles(styles: SelectStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveSelectBaseStyles(styles)\n const { root } = styles\n return resolveSelectBaseStyles({ root })\n}\n\nconst Select = React.forwardRef<HTMLDivElement, SelectProps<any>>(\n (\n {\n mode = \"single\",\n items: propItems = [],\n selectedKey: controlledSelectedKey,\n defaultSelectedKey,\n selectedKeys: controlledSelectedKeys,\n defaultSelectedKeys = [],\n defaultValue,\n valueLabel,\n onSelectionChange,\n isDisabled = false,\n autoFocus = false,\n maxItems = 6,\n children,\n className,\n trigger: triggerMode = \"click\",\n filter,\n styles: stylesProp,\n ...domProps\n },\n ref\n ) => {\n const triggerRef = React.useRef<HTMLElement>(null)\n const scopeRef = React.useRef<HTMLDivElement>(null)\n const wrapperRef = React.useRef<HTMLDivElement>(null)\n const contentRef = React.useRef<HTMLElement>(null)\n const mouseMoveDetectedRef = React.useRef(true)\n const itemExtrasRef = React.useRef<Map<Key, { onSelect?: () => void; isSubmenuTrigger?: boolean }>>(new Map())\n const [isOpen, setIsOpen] = React.useState(false)\n const [contentPlacement, setContentPlacement] = React.useState<\"top\" | \"bottom\">(\"bottom\")\n const contentId = React.useId()\n const hoverTimeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null)\n const keyboardScrollIntentRef = React.useRef(false)\n\n const handleHoverIntent = React.useCallback((isHovering: boolean) => {\n if (triggerMode !== \"hover\" || isDisabled) return\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n hoverTimeoutRef.current = null\n }\n\n if (isHovering) {\n setIsOpen(true)\n } else {\n hoverTimeoutRef.current = setTimeout(() => {\n setIsOpen(false)\n }, 100)\n }\n }, [triggerMode, isDisabled])\n\n React.useEffect(() => {\n if (!isOpen || triggerMode !== \"hover\" || isDisabled) return\n\n const handleMouseMove = (e: MouseEvent) => {\n const target = e.target as HTMLElement\n const isOver = wrapperRef.current?.contains(target) ||\n contentRef.current?.contains(target)\n\n if (!isOver) {\n handleHoverIntent(false)\n } else {\n handleHoverIntent(true)\n }\n }\n\n window.addEventListener(\"mousemove\", handleMouseMove)\n return () => window.removeEventListener(\"mousemove\", handleMouseMove)\n }, [isOpen, triggerMode, isDisabled, handleHoverIntent])\n\n React.useEffect(() => {\n return () => {\n if (hoverTimeoutRef.current) {\n clearTimeout(hoverTimeoutRef.current)\n }\n }\n }, [])\n\n const [uncontrolledSelectedKey, setUncontrolledSelectedKey] = React.useState<Key | null>(\n defaultSelectedKey ?? null\n )\n const [uncontrolledSelectedKeys, setUncontrolledSelectedKeys] = React.useState<Set<Key>>(\n new Set(defaultSelectedKeys)\n )\n const [selectedTextValue, setSelectedTextValue] = React.useState(valueLabel ?? defaultValue ?? \"\")\n const selectedKey = controlledSelectedKey !== undefined ? controlledSelectedKey : uncontrolledSelectedKey\n const selectedKeys = controlledSelectedKeys !== undefined ? new Set(controlledSelectedKeys) : uncontrolledSelectedKeys\n\n const nav = useListNavigation({\n isOpen,\n externalItems: propItems.length > 0 ? propItems : undefined,\n filter: filter ? (item: any) => filter({ ...item, label: item.textValue } as any) : undefined\n })\n\n const registerItem = React.useCallback((key: Key, textValue: string, isDisabled?: boolean, onSelect?: () => void, isSubmenuTrigger?: boolean) => {\n nav.registerItem(key, textValue, isDisabled)\n itemExtrasRef.current.set(key, { onSelect, isSubmenuTrigger })\n }, [nav.registerItem])\n\n const unregisterItem = React.useCallback((key: Key) => {\n nav.unregisterItem(key)\n itemExtrasRef.current.delete(key)\n }, [nav.unregisterItem])\n\n const isFocusedItemSubmenu = React.useCallback(() => {\n if (nav.focusedKey === null) return false\n return itemExtrasRef.current.get(nav.focusedKey)?.isSubmenuTrigger ?? false\n }, [nav.focusedKey])\n\n const markKeyboardNavigation = React.useCallback(() => {\n mouseMoveDetectedRef.current = false\n keyboardScrollIntentRef.current = true\n }, [])\n\n const moveFocusFromTrigger = React.useCallback((direction: 1 | -1) => {\n const triggerElement = triggerRef.current\n if (!triggerElement) return false\n return focusAdjacentTabStop(triggerElement, direction, wrapperRef.current)\n }, [])\n\n const restoreFocus = React.useCallback((target: \"auto\" | \"trigger\" | \"row\" = \"auto\") => {\n const triggerElement = triggerRef.current\n if (!triggerElement) return\n\n const ownerRow = triggerElement.closest<HTMLElement>('[data-list-focus-owner=\"true\"]')\n const focusTarget = target === \"row\"\n ? ownerRow\n : target === \"trigger\"\n ? triggerElement\n : ownerRow ?? triggerElement\n\n focusTarget?.focus({ preventScroll: true })\n }, [])\n\n const onSelect = React.useCallback((key: Key) => {\n const item = nav.items.find(i => i.key === key)\n if (item) {\n setSelectedTextValue(item.textValue)\n }\n if (controlledSelectedKey === undefined) {\n setUncontrolledSelectedKey(key)\n }\n onSelectionChange?.(key)\n setIsOpen(false)\n nav.setSearchValue(\"\")\n restoreFocus()\n }, [controlledSelectedKey, onSelectionChange, nav.items, restoreFocus])\n\n const onToggle = React.useCallback((key: Key) => {\n const newKeys = new Set(selectedKeys)\n if (newKeys.has(key)) {\n newKeys.delete(key)\n } else {\n newKeys.add(key)\n }\n if (controlledSelectedKeys === undefined) {\n setUncontrolledSelectedKeys(newKeys)\n }\n onSelectionChange?.(Array.from(newKeys))\n }, [selectedKeys, controlledSelectedKeys, onSelectionChange])\n\n const selectFocusedItem = React.useCallback(() => {\n if (nav.focusedKey !== null) {\n const item = nav.enabledFilteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n const extras = itemExtrasRef.current.get(nav.focusedKey)\n if (extras?.onSelect) {\n extras.onSelect()\n } else if (mode === \"multiple\") {\n onToggle(nav.focusedKey)\n } else {\n onSelect(nav.focusedKey)\n }\n }\n }\n }, [nav.focusedKey, nav.enabledFilteredItems, onSelect, onToggle, mode])\n\n React.useEffect(() => {\n if (isOpen) {\n // Only initialize focusedKey if it's not already valid\n if (nav.focusedKey !== null && nav.visibleKeys.has(nav.focusedKey)) {\n const item = nav.filteredItems.find(item => item.key === nav.focusedKey)\n if (item && !item.isDisabled) {\n return // Keep current keyboard focus, don't reset it\n }\n }\n\n const focusKey = mode === \"multiple\" && selectedKeys.size > 0\n ? Array.from(selectedKeys)[0]\n : selectedKey\n\n if (focusKey !== null && nav.visibleKeys.has(focusKey)) {\n const item = nav.filteredItems.find(item => item.key === focusKey)\n if (item && !item.isDisabled) {\n nav.setFocusedKey(focusKey)\n return\n }\n }\n if (nav.enabledFilteredItems.length > 0) {\n nav.setFocusedKey(nav.enabledFilteredItems[0].key)\n } else {\n nav.setFocusedKey(null)\n }\n }\n }, [isOpen, selectedKey, selectedKeys, nav.visibleKeys, nav.enabledFilteredItems, nav.filteredItems, mode, nav.focusedKey])\n\n const { buttonProps, isPressed } = useButton({\n isDisabled,\n onPress: (e) => {\n if (isDisabled) return\n // Keyboard interactions are handled by onKeyDown to prevent conflicts\n if (e.pointerType !== 'keyboard') {\n setIsOpen(prev => !prev)\n }\n },\n }, triggerRef)\n const { focusProps, isFocused, isFocusVisible } = useFocusRing()\n const { hoverProps, isHovered } = useHover({\n isDisabled,\n onHoverStart: () => handleHoverIntent(true),\n onHoverEnd: () => handleHoverIntent(false),\n })\n\n const triggerProps = mergeProps(buttonProps, focusProps, hoverProps, {\n 'aria-haspopup': 'listbox' as const,\n 'aria-expanded': isOpen,\n 'aria-controls': isOpen ? contentId : undefined,\n 'aria-disabled': isDisabled || undefined,\n onKeyDown: (e: React.KeyboardEvent) => {\n if (!isOpen) {\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Enter' || (e.key === ' ' && !isDisabled)) {\n e.preventDefault()\n setIsOpen(true)\n }\n return\n }\n\n if (e.key === 'Tab') {\n e.preventDefault()\n const direction = e.shiftKey ? -1 : 1\n setIsOpen(false)\n nav.setSearchValue(\"\")\n moveFocusFromTrigger(direction as 1 | -1)\n return\n }\n\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp' || e.key === 'Home' || e.key === 'End') {\n markKeyboardNavigation()\n }\n\n handleListKeyDown(e, {\n navigateNext: nav.navigateToNextItem,\n navigatePrev: nav.navigateToPrevItem,\n confirm: selectFocusedItem,\n close: () => {\n setIsOpen(false)\n nav.setSearchValue(\"\")\n restoreFocus()\n },\n filteredItems: nav.filteredItems,\n setFocusedKey: nav.setFocusedKey,\n })\n },\n })\n\n React.useEffect(() => {\n if (autoFocus && triggerRef.current) {\n triggerRef.current.focus({ preventScroll: true })\n }\n }, [autoFocus])\n\n React.useEffect(() => {\n if (mode === \"single\") {\n if (selectedKey === null) {\n setSelectedTextValue(\"\")\n } else {\n const selectedItem = nav.items.find(item => item.key === selectedKey)\n if (selectedItem) {\n setSelectedTextValue(selectedItem.textValue)\n } else if (valueLabel !== undefined) {\n setSelectedTextValue(valueLabel)\n } else if (defaultValue !== undefined && defaultValue !== null) {\n setSelectedTextValue(defaultValue)\n }\n }\n }\n }, [selectedKey, nav.items, mode, defaultValue, valueLabel])\n\n const childrenArray = React.Children.toArray(children)\n const trigger = childrenArray.find(child => React.isValidElement(child) && (\n (child.type as any)?.displayName === 'SelectTrigger' ||\n (child.type as any)?.displayName === 'SearchableTrigger'\n ))\n const contentItems = childrenArray.filter(child => React.isValidElement(child) && ((child.type as any)?.displayName === 'SelectContent' || (child.type as any)?.displayName === 'SearchableContent'))\n const otherContent = childrenArray.filter(child => !React.isValidElement(child) || (\n (child.type as any)?.displayName !== 'SelectTrigger' &&\n (child.type as any)?.displayName !== 'SearchableTrigger' &&\n (child.type as any)?.displayName !== 'SelectContent' &&\n (child.type as any)?.displayName !== 'SearchableContent'\n ))\n const hasExternalValue = otherContent.some(child => (\n React.isValidElement(child) && (child.type as any)?.displayName === 'SelectValue'\n ))\n const triggerType = React.isValidElement(trigger) && (trigger.type as any)?.displayName === 'SearchableTrigger'\n ? 'input'\n : 'button'\n\n const resolvedStyles = resolveSelectStyles(stylesProp);\n const mergedRootRef = useMergeRefs<HTMLDivElement>(scopeRef, wrapperRef, ref)\n const { indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: wrapperRef,\n surfaceSelector: '[data-select-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"self\",\n dependencies: [mode],\n });\n\n return (\n <SelectContext.Provider\n value={{\n isOpen,\n setIsOpen,\n contentPlacement,\n setContentPlacement,\n triggerType,\n mode,\n selectedKey,\n selectedKeys: mode === \"multiple\" ? selectedKeys : undefined,\n selectedTextValue,\n onSelect,\n onToggle: mode === \"multiple\" ? onToggle : undefined,\n triggerRef,\n wrapperRef,\n contentRef,\n triggerProps,\n isFocused,\n isFocusVisible,\n isPressed,\n isHovered,\n isDisabled,\n items: nav.items,\n registerItem,\n unregisterItem,\n searchValue: nav.searchValue,\n setSearchValue: nav.setSearchValue,\n filteredItems: nav.filteredItems,\n visibleKeys: nav.visibleKeys,\n focusedKey: nav.focusedKey,\n setFocusedKey: nav.setFocusedKey,\n navigateToNextItem: nav.navigateToNextItem,\n navigateToPrevItem: nav.navigateToPrevItem,\n selectFocusedItem,\n isFocusedItemSubmenu,\n maxItems,\n triggerMode,\n handleHoverIntent,\n mouseMoveDetectedRef,\n keyboardScrollIntentRef,\n markKeyboardNavigation,\n moveFocusFromTrigger,\n filter,\n contentId,\n hasExternalValue,\n restoreFocus,\n }}\n >\n <div\n ref={mergedRootRef}\n className={cn('select', styles.select, className, resolvedStyles.root)}\n data-mode={mode}\n data-select-focus-surface=\"true\"\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n {...domProps}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n {otherContent}\n {trigger}\n {contentItems}\n </div>\n </SelectContext.Provider>\n )\n }\n)\nSelect.displayName = \"Select\"\n\nexport { Select, SelectContext }\n",
|
|
4562
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .scope {\n @apply flex w-full;\n position: relative;\n overflow: visible;\n }\n\n .select {\n --disabled-opacity: 0.5;\n --trigger-padding-inline: calc(var(--spacing) * 2);\n --trigger-padding-block: calc(var(--spacing) * 1.75);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n font-size: var(--foreground-size);\n\n @apply p-0 gap-0 w-full flex-row items-center;\n position: relative;\n overflow: visible;\n\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n\n @apply select-none cursor-pointer;\n\n &[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n &[data-pressed=\"true\"]:not([data-disabled]) {\n background-color: var(--background-pressed, var(--background-hover, var(--background)));\n }\n\n &[data-open=\"true\"] {\n background-color: var(--background-hover);\n }\n }\n\n .trigger {\n @apply flex items-stretch flex-1 gap-0 w-full h-full min-h-0;\n\n background: transparent;\n\n @apply border-none cursor-pointer select-none;\n\n @media (hover: hover) {\n &:not([data-disabled]):hover .icon-section,\n &:not([data-disabled]):hover .value-section:not(:empty) {\n background-color: var(--background-hover);\n }\n }\n\n &[data-focus-visible=\"true\"] {\n @apply outline-none;\n }\n }\n\n .trigger-compact {\n @apply flex-none w-auto;\n }\n\n button.trigger { @apply p-0; }\n\n .value-section {\n @apply flex items-center flex-1 min-w-0 gap-0.5;\n\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n border-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n font-size: var(--foreground-size);\n\n &:only-child {\n border-radius: var(--background-inner-radius);\n justify-content: center;\n }\n &:empty {\n flex: 0;\n padding: 0;\n min-width: auto;\n }\n }\n\n .icon-section {\n @apply flex items-center justify-center shrink-0;\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n border-radius: 0 var(--background-inner-radius) var(--background-inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[data-open=\"true\"] .icon {\n transform: rotate(180deg);\n }\n\n .value {\n @apply flex items-center flex-1 min-w-0 gap-2 bg-transparent border-none;\n cursor: inherit;\n }\n\n .value-icon {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n color: var(--foreground);\n }\n\n .value-text {\n font-weight: var(--font-weight-medium);\n @apply overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .content,\n .sub-content {\n --item-padding-inline: calc(var(--spacing) * 1.5);\n --item-padding-block: var(--spacing);\n --background-radius: var(--radius-sm, 0.375rem);\n --background-inner-radius: calc(var(--background-radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--background);\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--background-radius);\n }\n\n .content-root,\n .sub-content-root {\n position: absolute;\n }\n\n .content {\n &[data-state=\"open\"][data-placement=\"bottom\"] { animation: slide-in-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"open\"][data-placement=\"top\"] { animation: slide-in-from-bottom 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"bottom\"] { animation: slide-out-from-top 0.15s var(--ease-snappy-pop); }\n &[data-state=\"closed\"][data-placement=\"top\"] { animation: slide-out-from-bottom 0.15s var(--ease-snappy-pop); }\n }\n\n .list {\n @apply space-y-1;\n }\n\n .item,\n .sub-trigger {\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n border-radius: var(--background-inner-radius);\n font-size: var(--foreground-size);\n font-weight: var(--font-weight-medium);\n color: var(--foreground);\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n }\n\n .item {\n --item-padding-inline: var(--trigger-padding-inline);\n --item-padding-block: calc(var(--trigger-padding-block) * 1.15);\n\n padding: var(--item-padding-block) var(--item-padding-inline);\n\n &[data-selected=\"true\"] {\n color: var(--foreground);\n }\n\n &[data-highlighted=\"true\"] {\n background-color: var(--background-highlighted);\n }\n }\n\n .item-content {\n @apply flex flex-col flex-1 min-w-0;\n }\n\n .item-text {\n @apply min-w-0 overflow-hidden text-ellipsis whitespace-nowrap;\n }\n\n .item-description {\n font-size: var(--foreground-size);\n font-weight: var(--font-weight-medium);\n color: var(--foreground-muted);\n @apply min-w-0 whitespace-normal break-words;\n }\n\n .item-icon, .item-indicator {\n @apply flex items-center justify-center shrink-0 w-4 h-4;\n }\n\n .item-icon { color: var(--icon-foreground); }\n .item-indicator { color: var(--indicator-foreground); margin-left: auto; }\n\n .item-with-description { @apply items-start py-2; }\n .item-icon-with-description, .item-indicator-with-description { @apply mt-0.5; }\n\n .separator {\n @apply my-1 -mx-1 h-px;\n background-color: var(--background-border);\n }\n\n .placeholder {\n color: var(--foreground-muted);\n }\n\n .icon-prefix {\n @apply inline-flex items-center shrink-0;\n }\n\n .select[data-mode=\"multiple\"] .item { gap: 0.5rem; }\n\n .search-trigger {\n @apply flex items-stretch relative bg-transparent cursor-text overflow-hidden;\n border-radius: var(--background-inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n &:focus-within {\n @apply outline-none;\n z-index: 1;\n }\n }\n\n .search-trigger :global(.focus-indicator) {\n display: none;\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--background-inner-radius) 0 0 var(--background-inner-radius);\n }\n\n .input {\n padding: var(--trigger-padding-block) calc(var(--trigger-padding-inline) * 1.5);\n padding-right: calc(var(--trigger-padding-inline) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-focused], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--trigger-padding-inline) * 1.5);\n @apply border-none rounded-none bg-transparent;\n }\n\n .search-icon-section {\n @apply absolute right-0 top-0 bottom-0 flex items-center justify-center bg-transparent pointer-events-none;\n padding-inline: var(--trigger-padding-inline);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .content[data-placement=\"top\"] .search-wrapper {\n border-radius: 0;\n border-bottom: none;\n border-top: var(--border-width-base, 1px) solid var(--background-border);\n }\n\n .sub-trigger {\n padding: var(--trigger-padding-block) var(--trigger-padding-inline);\n\n &[data-highlighted=\"true\"],\n &[data-open=\"true\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--background-highlighted);\n }\n }\n\n .sub-trigger-chevron {\n @apply shrink-0 ml-auto w-4 h-4 opacity-60;\n }\n\n .sub-content {\n min-width: 160px;\n max-width: 320px;\n }\n\n @keyframes slide-in-from-top { from { opacity: 0; translate: 0 -2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-in-from-bottom { from { opacity: 0; translate: 0 2px; } to { opacity: 1; translate: 0 0; } }\n @keyframes slide-out-from-top { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 -2px; } }\n @keyframes slide-out-from-bottom { from { opacity: 1; translate: 0 0; } to { opacity: 0; translate: 0 2px; } }\n}\n",
|
|
4563
|
+
"cssTypes": "declare const styles: {\n scope: string;\n select: string;\n \"select-split\": string;\n trigger: string;\n \"trigger-compact\": string;\n input: string;\n \"search-trigger\": string;\n \"search-value-section\": string;\n \"search-content-input\": string;\n \"search-icon-section\": string;\n \"search-wrapper\": string;\n \"value-section\": string;\n \"icon-section\": string;\n icon: string;\n value: string;\n \"value-icon\": string;\n \"value-text\": string;\n \"value-chevron\": string;\n \"content-root\": string;\n content: string;\n viewport: string;\n list: string;\n item: string;\n \"item-icon\": string;\n \"item-indicator\": string;\n \"item-text\": string;\n \"item-content\": string;\n \"item-description\": string;\n \"item-with-description\": string;\n \"item-icon-with-description\": string;\n \"item-indicator-with-description\": string;\n separator: string;\n \"scroll-button\": string;\n placeholder: string;\n \"icon-prefix\": string;\n \"sub-trigger\": string;\n \"sub-trigger-chevron\": string;\n \"sub-content-root\": string;\n \"sub-content\": string;\n};\n\nexport default styles;\n"
|
|
4439
4564
|
},
|
|
4440
4565
|
"slider": {
|
|
4441
|
-
"tsx": "\"use client\"\n\nimport * as React from 'react';\n\nimport { useFocusRing } from '@react-aria/focus';\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport { asElementProps } from '@/lib/react-aria';\n\nimport css from \"./Slider.module.css\";\n\ntype SliderSize = 'sm' | 'md' | 'lg';\n\nexport interface SliderStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n range?: StyleValue;\n thumb?: StyleValue;\n}\n\nexport type SliderStylesProp = StylesProp<SliderStyleSlots>;\n\nconst resolveSliderBaseStyles = createStylesResolver(['root', 'track', 'range', 'thumb'] as const);\n\nfunction resolveSliderStyles(styles: SliderStylesProp | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveSliderBaseStyles(styles);\n const { root, track, range, thumb } = styles;\n return resolveSliderBaseStyles({ root, track, range, thumb });\n}\n\ninterface SliderRootProps {\n /** Size of the slider track and thumb */\n size?: SliderSize;\n /** Whether the slider is disabled */\n disabled?: boolean;\n /** Additional CSS class for the slider container */\n className?: string;\n /** Inline styles for the slider container */\n style?: React.CSSProperties;\n /** Minimum value of the slider range */\n min?: number;\n /** Maximum value of the slider range */\n max?: number;\n /** Step increment between values */\n step?: number;\n /** Initial value(s) for uncontrolled usage */\n defaultValue?: number | number[];\n /** Controlled value(s) for the slider thumb(s) */\n value?: number | number[];\n /** Called when the value changes */\n onValueChange?: (value: number[]) => void;\n /** Orientation of the slider track */\n orientation?: 'horizontal' | 'vertical';\n /** Accessible label for the slider */\n 'aria-label'?: string;\n /** ID of an element that labels the slider */\n 'aria-labelledby'?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SliderStylesProp;\n}\n\nconst SliderContext = React.createContext<{\n size: SliderSize;\n disabled?: boolean;\n} | null>(null);\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction snapToStep(value: number, min: number, max: number, step: number): number {\n const snapped = Math.round((value - min) / step) * step + min;\n return clamp(snapped, min, max);\n}\n\ninterface ThumbProps {\n index: number;\n value: number;\n min: number;\n max: number;\n step: number;\n disabled?: boolean;\n trackRef: React.RefObject<HTMLDivElement | null>;\n onValueChange: (index: number, value: number) => void;\n 'aria-label'?: string;\n 'aria-labelledby'?: string;\n className?: string;\n}\n\nfunction SliderThumbInternal({\n index,\n value,\n min,\n max,\n step,\n disabled,\n trackRef,\n onValueChange,\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n className,\n}: ThumbProps) {\n const thumbRef = React.useRef<HTMLDivElement>(null);\n const [isDragging, setIsDragging] = React.useState(false);\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const percent = ((value - min) / (max - min)) * 100;\n\n const getValueFromPointer = React.useCallback((clientX: number) => {\n const track = trackRef.current;\n if (!track) return value;\n\n const rect = track.getBoundingClientRect();\n const percent = clamp((clientX - rect.left) / rect.width, 0, 1);\n const rawValue = percent * (max - min) + min;\n return snapToStep(rawValue, min, max, step);\n }, [trackRef, min, max, step, value]);\n\n const handlePointerDown = (e: React.PointerEvent) => {\n if (disabled) return;\n e.preventDefault();\n setIsDragging(true);\n thumbRef.current?.setPointerCapture(e.pointerId);\n thumbRef.current?.focus();\n };\n\n const handlePointerMove = (e: React.PointerEvent) => {\n if (!isDragging || disabled) return;\n const newValue = getValueFromPointer(e.clientX);\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n };\n\n const handlePointerUp = (e: React.PointerEvent) => {\n if (isDragging) {\n setIsDragging(false);\n thumbRef.current?.releasePointerCapture(e.pointerId);\n }\n };\n\n const handleKeyDown = (e: React.KeyboardEvent) => {\n if (disabled) return;\n\n let newValue = value;\n const largeStep = step * 10;\n\n switch (e.key) {\n case 'ArrowRight':\n case 'ArrowUp':\n newValue = clamp(value + step, min, max);\n break;\n case 'ArrowLeft':\n case 'ArrowDown':\n newValue = clamp(value - step, min, max);\n break;\n case 'PageUp':\n newValue = clamp(value + largeStep, min, max);\n break;\n case 'PageDown':\n newValue = clamp(value - largeStep, min, max);\n break;\n case 'Home':\n newValue = min;\n break;\n case 'End':\n newValue = max;\n break;\n default:\n return;\n }\n\n e.preventDefault();\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n };\n\n return (\n <div\n ref={thumbRef}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-disabled={disabled || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={cn('slider thumb', css.thumb, className)}\n style={{ left: `${percent}%` }}\n data-dragging={isDragging || undefined}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerUp}\n onPointerCancel={handlePointerUp}\n onKeyDown={handleKeyDown}\n {...asElementProps<\"div\">(focusProps)}\n />\n );\n}\n\n/** Horizontal slider for selecting a value within a range */\nconst Root = React.forwardRef<HTMLDivElement, SliderRootProps>(\n (\n {\n className,\n styles,\n size = 'md',\n disabled,\n style,\n defaultValue,\n value: controlledValue,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n orientation = 'horizontal',\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledBy,\n ...props\n },\n ref\n ) => {\n const trackRef = React.useRef<HTMLDivElement>(null);\n\n // Normalize to arrays\n const normalizeValue = (v: number | number[] | undefined): number[] | undefined => {\n if (v === undefined) return undefined;\n return Array.isArray(v) ? v : [v];\n };\n\n const [internalValues, setInternalValues] = React.useState<number[]>(() => {\n return normalizeValue(defaultValue) ?? normalizeValue(controlledValue) ?? [min];\n });\n\n const isControlled = controlledValue !== undefined;\n const values = isControlled ? normalizeValue(controlledValue)! : internalValues;\n\n const resolved = resolveSliderStyles(styles);\n\n const handleValueChange = React.useCallback((index: number, newValue: number) => {\n const newValues = [...values];\n newValues[index] = newValue;\n\n if (!isControlled) {\n setInternalValues(newValues);\n }\n onValueChange?.(newValues);\n }, [values, isControlled, onValueChange]);\n\n const handleTrackClick = (e: React.PointerEvent) => {\n if (disabled) return;\n // Only handle clicks directly on the track, not on thumbs\n if (e.target !== trackRef.current) return;\n\n const track = trackRef.current;\n if (!track) return;\n\n const rect = track.getBoundingClientRect();\n const percent = clamp((e.clientX - rect.left) / rect.width, 0, 1);\n const rawValue = percent * (max - min) + min;\n const newValue = snapToStep(rawValue, min, max, step);\n\n // Find the closest thumb and update it\n let closestIndex = 0;\n let closestDistance = Math.abs(values[0] - newValue);\n for (let i = 1; i < values.length; i++) {\n const distance = Math.abs(values[i] - newValue);\n if (distance < closestDistance) {\n closestDistance = distance;\n closestIndex = i;\n }\n }\n\n handleValueChange(closestIndex, newValue);\n };\n\n return (\n <SliderContext.Provider value={{ size, disabled }}>\n <div\n ref={ref}\n data-size={size}\n data-disabled={disabled || undefined}\n data-orientation={orientation}\n style={style}\n className={cn('slider', css.slider, className, resolved.root)}\n {...props}\n >\n <div\n ref={trackRef}\n className={cn('slider track', css.track, resolved.track)}\n onPointerDown={handleTrackClick}\n >\n <div\n className={cn('slider range', css.range, resolved.range)}\n style={{\n left: `${values.length === 1 ? 0 : ((values[0] - min) / (max - min)) * 100}%`,\n right: `${values.length === 1 ? 100 - ((values[0] - min) / (max - min)) * 100 : 100 - ((values[values.length - 1] - min) / (max - min)) * 100}%`,\n }}\n />\n {values.map((value, index) => (\n <SliderThumbInternal\n key={index}\n index={index}\n value={value}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n trackRef={trackRef}\n onValueChange={handleValueChange}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={resolved.thumb}\n />\n ))}\n </div>\n </div>\n </SliderContext.Provider>\n );\n }\n);\nRoot.displayName = 'SliderRoot';\n\nexport { Root };\n",
|
|
4442
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n\n @apply relative flex w-full items-center;\n touch-action: none;\n user-select: none;\n }\n\n .
|
|
4566
|
+
"tsx": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { mergeProps } from \"@react-aria/utils\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\n\nimport css from \"./Slider.module.css\";\n\ntype SliderOrientation = \"horizontal\" | \"vertical\";\n\nexport interface SliderStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n range?: StyleValue;\n thumb?: StyleValue;\n}\n\nexport type SliderStylesProp = StylesProp<SliderStyleSlots>;\n\nconst resolveSliderBaseStyles = createStylesResolver([\n \"root\",\n \"track\",\n \"range\",\n \"thumb\",\n] as const);\n\nfunction resolveSliderStyles(styles: SliderStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveSliderBaseStyles(styles);\n }\n\n const { root, track, range, thumb } = styles;\n\n return resolveSliderBaseStyles({\n root,\n track,\n range,\n thumb,\n });\n}\n\nexport interface SliderProps\n extends Omit<React.HTMLAttributes<HTMLDivElement>, \"defaultValue\" | \"value\" | \"onChange\"> {\n /** Whether the slider is disabled. */\n disabled?: boolean;\n /** Minimum value of the slider range. */\n min?: number;\n /** Maximum value of the slider range. */\n max?: number;\n /** Step increment between values. */\n step?: number;\n /** Initial value or values for uncontrolled usage. */\n defaultValue?: number | number[];\n /** Controlled value or values for the slider thumbs. */\n value?: number | number[];\n /** Called when the slider value changes. */\n onValueChange?: (value: number[]) => void;\n /** Orientation of the slider track. */\n orientation?: SliderOrientation;\n /** Accessible label for the slider. */\n \"aria-label\"?: string;\n /** ID of an element that labels the slider. */\n \"aria-labelledby\"?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SliderStylesProp;\n}\n\ninterface SliderThumbProps {\n index: number;\n value: number;\n min: number;\n max: number;\n step: number;\n disabled?: boolean;\n orientation: SliderOrientation;\n trackRef: React.RefObject<HTMLDivElement | null>;\n onValueChange: (index: number, value: number) => void;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n className?: string;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\nfunction snapToStep(value: number, min: number, max: number, step: number): number {\n const snapped = Math.round((value - min) / step) * step + min;\n return clamp(snapped, min, max);\n}\n\nfunction normalizeValue(value: number | number[] | undefined): number[] | undefined {\n if (value === undefined) return undefined;\n return Array.isArray(value) ? value : [value];\n}\n\nfunction getValuePercent(value: number, min: number, max: number): number {\n if (max <= min) return 0;\n return ((value - min) / (max - min)) * 100;\n}\n\nfunction getValueFromPointer(\n clientX: number,\n clientY: number,\n track: HTMLDivElement,\n orientation: SliderOrientation,\n min: number,\n max: number,\n step: number\n) {\n const rect = track.getBoundingClientRect();\n\n const percent =\n orientation === \"vertical\"\n ? clamp((rect.bottom - clientY) / rect.height, 0, 1)\n : clamp((clientX - rect.left) / rect.width, 0, 1);\n\n const rawValue = percent * (max - min) + min;\n return snapToStep(rawValue, min, max, step);\n}\n\nfunction SliderThumb({\n index,\n value,\n min,\n max,\n step,\n disabled,\n orientation,\n trackRef,\n onValueChange,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n className,\n}: SliderThumbProps) {\n const thumbRef = React.useRef<HTMLDivElement>(null);\n const [isDragging, setIsDragging] = React.useState(false);\n const [isPressed, setIsPressed] = React.useState(false);\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef: thumbRef,\n containerRef: thumbRef,\n surfaceSelector: '[data-slider-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"self\",\n dependencies: [value, orientation, disabled],\n });\n\n const percent = getValuePercent(value, min, max);\n\n const updateValueFromPointer = React.useCallback(\n (clientX: number, clientY: number) => {\n const track = trackRef.current;\n if (!track) return;\n\n const newValue = getValueFromPointer(clientX, clientY, track, orientation, min, max, step);\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n },\n [index, max, min, onValueChange, orientation, step, trackRef, value]\n );\n\n const handlePointerDown = React.useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (disabled) return;\n\n event.preventDefault();\n setIsDragging(true);\n setIsPressed(true);\n thumbRef.current?.setPointerCapture(event.pointerId);\n thumbRef.current?.focus();\n updateValueFromPointer(event.clientX, event.clientY);\n },\n [disabled, updateValueFromPointer]\n );\n\n const handlePointerMove = React.useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (!isDragging || disabled) return;\n updateValueFromPointer(event.clientX, event.clientY);\n },\n [disabled, isDragging, updateValueFromPointer]\n );\n\n const handlePointerEnd = React.useCallback((event: React.PointerEvent<HTMLDivElement>) => {\n setIsDragging(false);\n setIsPressed(false);\n thumbRef.current?.releasePointerCapture(event.pointerId);\n }, []);\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (disabled) return;\n\n let newValue = value;\n const largeStep = step * 10;\n\n switch (event.key) {\n case \"ArrowRight\":\n newValue = orientation === \"horizontal\" ? clamp(value + step, min, max) : value;\n break;\n case \"ArrowUp\":\n newValue = clamp(value + step, min, max);\n break;\n case \"ArrowLeft\":\n newValue = orientation === \"horizontal\" ? clamp(value - step, min, max) : value;\n break;\n case \"ArrowDown\":\n newValue = clamp(value - step, min, max);\n break;\n case \"PageUp\":\n newValue = clamp(value + largeStep, min, max);\n break;\n case \"PageDown\":\n newValue = clamp(value - largeStep, min, max);\n break;\n case \"Home\":\n newValue = min;\n break;\n case \"End\":\n newValue = max;\n break;\n default:\n return;\n }\n\n event.preventDefault();\n setIsPressed(true);\n\n if (newValue !== value) {\n onValueChange(index, newValue);\n }\n },\n [disabled, index, max, min, onValueChange, orientation, step, value]\n );\n\n const handleKeyUp = React.useCallback(() => {\n setIsPressed(false);\n }, []);\n\n const positionStyle =\n orientation === \"vertical\"\n ? { bottom: `${percent}%` }\n : { left: `${percent}%` };\n\n return (\n <div\n ref={thumbRef}\n role=\"slider\"\n tabIndex={disabled ? -1 : 0}\n aria-orientation={orientation}\n aria-valuemin={min}\n aria-valuemax={max}\n aria-valuenow={value}\n aria-disabled={disabled || undefined}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={cn(\"thumb\", scopeProps.className, css.thumb, className)}\n style={positionStyle}\n data-disabled={disabled ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n data-pressed={isPressed ? \"true\" : undefined}\n data-dragging={isDragging ? \"true\" : undefined}\n data-slider-focus-surface=\"true\"\n onPointerDown={handlePointerDown}\n onPointerMove={handlePointerMove}\n onPointerUp={handlePointerEnd}\n onPointerCancel={handlePointerEnd}\n onKeyDown={handleKeyDown}\n onKeyUp={handleKeyUp}\n {...asElementProps<\"div\">(mergeProps(focusProps, hoverProps))}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n </div>\n );\n}\n\nconst Slider = React.forwardRef<HTMLDivElement, SliderProps>(\n (\n {\n className,\n styles,\n disabled = false,\n style,\n defaultValue,\n value: controlledValue,\n onValueChange,\n min = 0,\n max = 100,\n step = 1,\n orientation = \"horizontal\",\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n },\n ref\n ) => {\n const rootRef = React.useRef<HTMLDivElement>(null);\n const trackRef = React.useRef<HTMLDivElement>(null);\n\n const [internalValues, setInternalValues] = React.useState<number[]>(\n () => normalizeValue(defaultValue) ?? normalizeValue(controlledValue) ?? [min]\n );\n\n const isControlled = controlledValue !== undefined;\n const values = isControlled\n ? normalizeValue(controlledValue) ?? [min]\n : internalValues;\n\n const mergedRef = useMergeRefs(ref, rootRef);\n const resolved = resolveSliderStyles(styles);\n\n const handleValueChange = React.useCallback(\n (index: number, newValue: number) => {\n const nextValues = [...values];\n nextValues[index] = newValue;\n\n if (!isControlled) {\n setInternalValues(nextValues);\n }\n\n onValueChange?.(nextValues);\n },\n [isControlled, onValueChange, values]\n );\n\n const handleTrackPointerDown = React.useCallback(\n (event: React.PointerEvent<HTMLDivElement>) => {\n if (disabled || event.target !== trackRef.current) return;\n\n const track = trackRef.current;\n if (!track) return;\n\n const newValue = getValueFromPointer(\n event.clientX,\n event.clientY,\n track,\n orientation,\n min,\n max,\n step\n );\n\n let closestIndex = 0;\n let closestDistance = Math.abs(values[0] - newValue);\n\n for (let index = 1; index < values.length; index += 1) {\n const distance = Math.abs(values[index] - newValue);\n if (distance < closestDistance) {\n closestDistance = distance;\n closestIndex = index;\n }\n }\n\n handleValueChange(closestIndex, newValue);\n },\n [disabled, handleValueChange, max, min, orientation, step, values]\n );\n\n const rangeStartPercent =\n values.length > 1 ? getValuePercent(values[0], min, max) : 0;\n const rangeEndPercent = getValuePercent(values[values.length - 1], min, max);\n\n const rangeStyle =\n orientation === \"vertical\"\n ? {\n bottom: `${rangeStartPercent}%`,\n height: `${Math.max(rangeEndPercent - rangeStartPercent, 0)}%`,\n }\n : {\n left: `${rangeStartPercent}%`,\n width: `${Math.max(rangeEndPercent - rangeStartPercent, 0)}%`,\n };\n\n return (\n <div\n ref={mergedRef}\n data-disabled={disabled ? \"true\" : undefined}\n data-orientation={orientation}\n style={style}\n className={cn(\n \"slider\",\n css.slider,\n className,\n resolved.root\n )}\n {...props}\n >\n <div\n ref={trackRef}\n className={cn(\"track\", css.track, resolved.track)}\n data-disabled={disabled ? \"true\" : undefined}\n data-orientation={orientation}\n onPointerDown={handleTrackPointerDown}\n >\n <div\n className={cn(\"range\", css.range, resolved.range)}\n data-disabled={disabled ? \"true\" : undefined}\n style={rangeStyle}\n />\n {values.map((sliderValue, index) => (\n <SliderThumb\n key={index}\n index={index}\n value={sliderValue}\n min={min}\n max={max}\n step={step}\n disabled={disabled}\n orientation={orientation}\n trackRef={trackRef}\n onValueChange={handleValueChange}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy}\n className={resolved.thumb}\n />\n ))}\n </div>\n </div>\n );\n }\n);\n\nSlider.displayName = \"Slider\";\n\nconst Root = Slider;\n\nexport { Root, Slider };\n",
|
|
4567
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .slider {\n --disabled-opacity: 0.6;\n --slider-track-size: 0.375rem;\n --slider-thumb-size: 1rem;\n\n @apply relative flex w-full items-center;\n min-inline-size: 12rem;\n min-height: 2rem;\n touch-action: none;\n user-select: none;\n }\n\n .track {\n @apply relative flex grow items-center;\n flex-grow: 1;\n height: var(--slider-track-size);\n overflow: visible;\n border-radius: var(--radius-xs, 0.25rem);\n background-color: var(--background);\n }\n\n .range {\n @apply absolute;\n border-radius: var(--radius-xs, 0.25rem);\n background-color: var(--background);\n transition: background-color 200ms var(--ease-snappy-pop);\n }\n\n .thumb {\n @apply absolute block;\n top: 50%;\n width: var(--slider-thumb-size);\n height: var(--slider-thumb-size);\n transform: translate(-50%, -50%);\n border-radius: var(--radius-full, 9999px);\n outline: none;\n background-color: var(--background);\n transition:\n background-color 200ms var(--ease-snappy-pop),\n transform 200ms var(--ease-snappy-pop);\n }\n\n .slider[data-orientation=\"horizontal\"] .range {\n top: 0;\n height: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] {\n justify-content: center;\n min-height: 10rem;\n min-inline-size: auto;\n width: fit-content;\n }\n\n .slider[data-orientation=\"vertical\"] .track {\n width: var(--slider-track-size);\n height: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] .range {\n left: 0;\n width: 100%;\n }\n\n .slider[data-orientation=\"vertical\"] .thumb {\n left: 50%;\n top: auto;\n transform: translate(-50%, 50%);\n }\n\n .slider[data-disabled=\"true\"] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .slider[data-disabled=\"true\"] .range {\n background-color: var(--background-disabled, var(--background));\n }\n\n .thumb[data-disabled=\"true\"] {\n cursor: not-allowed;\n background-color: var(--background-disabled, var(--background));\n }\n\n .thumb[data-pressed=\"true\"] {\n transform: translate(-50%, -50%) scale(1.08);\n }\n\n .slider[data-orientation=\"vertical\"] .thumb[data-pressed=\"true\"] {\n transform: translate(-50%, 50%) scale(1.08);\n }\n}\n",
|
|
4443
4568
|
"cssTypes": "declare const styles: {\n readonly slider: string;\n readonly track: string;\n readonly range: string;\n readonly thumb: string;\n};\n\nexport default styles;\n"
|
|
4444
4569
|
},
|
|
4445
4570
|
"switch": {
|
|
4446
|
-
"tsx": "\"use client\";\n\nimport React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useSwitch } from \"@react-aria/switch\";\n\nimport { useToggleState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\n\nimport styles from \"./Switch.module.css\";\n\n\n\ninterface SwitchStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n}\n\ntype SwitchStylesProp = StylesProp<SwitchStyleSlots>;\n\nconst resolveSwitchBaseStyles = createStylesResolver(['root', 'track', 'thumb'] as const) as (stylesProp?: SwitchStylesProp) => SwitchStyleSlots;\n\nexport interface SwitchProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"onChange\" | \"checked\" | \"defaultChecked\"> {\n /** Controlled selected (on) state */\n isSelected?: boolean;\n /** Called when the switch is toggled */\n onChange?: (isSelected: boolean) => void;\n /** Initial selected state for uncontrolled usage */\n defaultSelected?: boolean;\n\n /** Whether the switch is disabled */\n isDisabled?: boolean;\n /** Size of the switch */\n size?: \"default\" | \"sm\";\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SwitchStylesProp;\n}\n\n\nconst Switch = React.forwardRef<HTMLInputElement, SwitchProps>(\n ({\n className,\n styles: stylesProp,\n isDisabled = false,\n isSelected: controlledSelected,\n onChange,\n defaultSelected,\n size = \"default\",\n ...props\n },\n ref\n ) => {\n const state = useToggleState({\n isSelected: controlledSelected,\n defaultSelected: defaultSelected ?? false,\n onChange,\n });\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n\n // Extract aria-label from props if provided\n const { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledby, ...otherProps } = props;\n\n const { inputProps, isSelected } = useSwitch(\n {\n isDisabled,\n ...(ariaLabel && { \"aria-label\": ariaLabel }),\n ...(ariaLabelledby && { \"aria-labelledby\": ariaLabelledby }),\n },\n state,\n inputRef\n );\n const { focusProps, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n\n\n React.useImperativeHandle(ref, () => inputRef.current!);\n\n const resolved = resolveSwitchBaseStyles(stylesProp);\n\n return (\n <div\n className={cn(\n 'switch',\n styles.switch,\n size === \"sm\" && styles[\"switch-sm\"],\n className,\n resolved.root\n )}\n data-selected={isSelected || undefined}\n data-disabled={isDisabled || undefined}\n data-focus-visible={isFocusVisible
|
|
4447
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border-color);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--foreground);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--background-active);\n border-color: var(--border-color-active);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--foreground-active);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-color-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-color-pressed);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n
|
|
4571
|
+
"tsx": "\"use client\";\n\nimport React from \"react\";\n\nimport { mergeProps, } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\"\nimport { useSwitch } from \"@react-aria/switch\";\n\nimport { useToggleState } from \"react-stately\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\n\nimport styles from \"./Switch.module.css\";\n\n\n\ninterface SwitchStyleSlots {\n root?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n}\n\ntype SwitchStylesProp = StylesProp<SwitchStyleSlots>;\n\nconst resolveSwitchBaseStyles = createStylesResolver(['root', 'track', 'thumb'] as const) as (stylesProp?: SwitchStylesProp) => SwitchStyleSlots;\n\nexport interface SwitchProps\n extends Omit<React.InputHTMLAttributes<HTMLInputElement>, \"type\" | \"size\" | \"onChange\" | \"checked\" | \"defaultChecked\"> {\n /** Controlled selected (on) state */\n isSelected?: boolean;\n /** Called when the switch is toggled */\n onChange?: (isSelected: boolean) => void;\n /** Initial selected state for uncontrolled usage */\n defaultSelected?: boolean;\n\n /** Whether the switch is disabled */\n isDisabled?: boolean;\n /** Size of the switch */\n size?: \"default\" | \"sm\";\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: SwitchStylesProp;\n}\n\n\nconst Switch = React.forwardRef<HTMLInputElement, SwitchProps>(\n ({\n className,\n styles: stylesProp,\n isDisabled = false,\n isSelected: controlledSelected,\n onChange,\n defaultSelected,\n size = \"default\",\n ...props\n },\n ref\n ) => {\n const state = useToggleState({\n isSelected: controlledSelected,\n defaultSelected: defaultSelected ?? false,\n onChange,\n });\n\n const inputRef = React.useRef<HTMLInputElement>(null);\n const rootRef = React.useRef<HTMLDivElement>(null);\n\n // Extract aria-label from props if provided\n const { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledby, ...otherProps } = props;\n\n const { inputProps, isSelected } = useSwitch(\n {\n isDisabled,\n ...(ariaLabel && { \"aria-label\": ariaLabel }),\n ...(ariaLabelledby && { \"aria-labelledby\": ariaLabelledby }),\n },\n state,\n inputRef\n );\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled });\n\n const { indicatorProps } = useFocusIndicator({\n scopeRef: rootRef,\n containerRef: rootRef,\n surfaceSelector: '[data-switch-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"self\",\n });\n\n React.useImperativeHandle(ref, () => inputRef.current!);\n\n const resolved = resolveSwitchBaseStyles(stylesProp);\n\n return (\n <div\n ref={rootRef}\n className={cn(\n 'switch',\n styles.switch,\n size === \"sm\" && styles[\"switch-sm\"],\n className,\n resolved.root\n )}\n data-switch-focus-surface=\"true\"\n data-selected={isSelected || undefined}\n data-disabled={isDisabled || undefined}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n data-hovered={isHovered || undefined}\n >\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n className={cn(\n 'switch-track',\n styles[\"switch-track\"],\n resolved.track\n )}\n />\n <div\n className={cn(\n 'switch-thumb',\n styles[\"switch-thumb\"],\n resolved.thumb\n )}\n />\n <input\n ref={inputRef}\n type=\"checkbox\"\n className=\"absolute inset-0 w-full h-full opacity-0 cursor-pointer\"\n aria-checked={isSelected}\n {...mergeProps(inputProps, focusProps, hoverProps)}\n {...otherProps}\n />\n </div>\n );\n }\n);\n\nSwitch.displayName = \"Switch\";\n\nexport { Switch };\n",
|
|
4572
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .switch {\n --radius: 9999px;\n --inner-radius: calc(var(--radius) - var(--border-width-base));\n\n --width: 2.75rem;\n --height: 1.5rem;\n --thumb-size: 1rem;\n --thumb-offset: 0.25rem;\n\n --disabled-opacity: 0.6;\n\n @apply relative inline-flex cursor-pointer items-center;\n user-select: none;\n border-radius: var(--radius);\n width: var(--width);\n height: var(--height);\n }\n\n .switch-track {\n @apply absolute inset-0;\n transition: background-color 180ms var(--ease-snappy-pop), border-color 180ms var(--ease-snappy-pop), transform 180ms var(--ease-snappy-pop);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border-color);\n border-radius: var(--radius);\n }\n\n .switch:active:not([data-disabled]) .switch-track {\n transform: scale(0.98);\n }\n\n .switch-thumb {\n @apply absolute top-0 bottom-0 my-auto;\n left: var(--thumb-offset);\n width: var(--thumb-size);\n height: var(--thumb-size);\n transition: left 180ms var(--ease-snappy-pop), background-color 180ms var(--ease-snappy-pop);\n background-color: var(--foreground);\n border-radius: var(--inner-radius);\n z-index: 1;\n pointer-events: none;\n }\n\n .switch[data-selected] .switch-track {\n background-color: var(--background-active);\n border-color: var(--border-color-active);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--foreground-active);\n left: calc(var(--width) - var(--thumb-size) - var(--thumb-offset));\n }\n\n @media (hover: hover) {\n .switch[data-selected]:not([data-disabled]):hover .switch-track {\n border-color: var(--border-color-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-color-pressed);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n\n .switch-sm {\n --width: 1.75rem;\n --height: 1rem;\n --thumb-size: 0.625rem;\n --thumb-offset: 0.1875rem;\n }\n}\n",
|
|
4448
4573
|
"cssTypes": "export interface Styles {\n switch: string;\n \"switch-track\": string;\n \"switch-thumb\": string;\n \"switch-sm\": string;\n\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4449
4574
|
},
|
|
4450
4575
|
"table": {
|
|
@@ -4453,14 +4578,14 @@ export const generatedSourceCode = {
|
|
|
4453
4578
|
"cssTypes": "declare const styles: {\n root: string;\n container: string;\n table: string;\n thead: string;\n headerRow: string;\n headerCell: string;\n tbody: string;\n bodyRow: string;\n interactive: string;\n cell: string;\n emptyState: string;\n filterBar: string;\n filterGrid: string;\n filterLabel: string;\n filterInput: string;\n};\n\nexport default styles;\n"
|
|
4454
4579
|
},
|
|
4455
4580
|
"tabs": {
|
|
4456
|
-
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing } from \"react-aria\"\nimport { cn } from \"./utils\"\nimport { StyleValue } from \"./utils\"\nimport { asElementProps } from \"@/lib/react-aria\"\nimport { StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport css from \"./Tabs.module.css\"\n\ntype TabsVariant = \"underline\"\ntype TabsOrientation = \"horizontal\" | \"vertical\"\n\ninterface IndicatorPosition {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface ListDimensions {\n width: number\n height: number\n}\n\ninterface TabsContextValue {\n selectedValue: string\n setSelectedValue: (value: string) => void\n variant?: TabsVariant\n orientation: TabsOrientation\n isDisabledTab: (value: string) => boolean\n indicatorReady: boolean\n setIndicatorReady: (ready: boolean) => void\n}\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null)\nconst TABS_INDICATOR_INSET = 4\nconst TABS_UNDERLINE_THICKNESS = 2\nconst TABS_UNDERLINE_OFFSET = 2\nconst TABS_UNDERLINE_GUTTER = TABS_UNDERLINE_THICKNESS + TABS_UNDERLINE_OFFSET\nconst TABS_UNDERLINE_FALLBACK_GUTTER = TABS_UNDERLINE_GUTTER + TABS_UNDERLINE_THICKNESS\n\ninterface TabsListContextValue {\n indicatorClassName: string\n}\n\nconst TabsListContext = React.createContext<TabsListContextValue | null>(null)\n\nfunction useTabsContext() {\n const context = React.useContext(TabsContext)\n if (!context) {\n throw new Error(\"Tabs component must be used within Tabs root\")\n }\n return context\n}\n\nfunction useTabsListContext() {\n return React.useContext(TabsListContext)\n}\n\nfunction getTabsIndicatorClassName(indicator: string, variant?: TabsVariant) {\n return cn(\n \"tabs\",\n \"indicator\",\n variant === \"underline\" && \"underline\",\n variant === \"underline\" && \"indicator-underline\",\n css.indicator,\n {\n [css[\"indicator-underline\"]]: variant === \"underline\",\n },\n indicator\n )\n}\n\nexport interface TabsStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsProps {\n /** Optional alternate visual style of the tab list indicator */\n variant?: TabsVariant\n /** Direction of the tab list layout */\n orientation?: TabsOrientation\n /** Initially selected tab value for uncontrolled usage */\n default?: string\n /** Controlled selected tab value */\n value?: string\n /** Called when the selected tab changes */\n onValueChange?: (value: string) => void\n /** Additional CSS class for the tabs root */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsBaseStyles = createStylesResolver(['root'] as const)\n\nconst TabsRoot = React.forwardRef<HTMLDivElement, TabsProps>(\n (\n {\n variant,\n orientation = \"horizontal\",\n default: defaultTab,\n value: controlledValue,\n onValueChange,\n className,\n styles: stylesProp,\n children,\n },\n ref\n ) => {\n const { root } = resolveTabsBaseStyles(stylesProp)\n\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultTab || \"\")\n const [disabledTabs, setDisabledTabs] = React.useState<Set<string>>(new Set())\n\n const selectedValue = controlledValue !== undefined ? controlledValue : uncontrolledValue\n const isDisabledTab = React.useCallback(\n (value: string) => disabledTabs.has(value),\n [disabledTabs]\n )\n\n const setSelectedValue = React.useCallback(\n (newValue: string) => {\n if (!isDisabledTab(newValue)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n }\n },\n [controlledValue, isDisabledTab, onValueChange]\n )\n\n const registerDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => new Set(prev).add(value))\n }, [])\n\n const unregisterDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => {\n const newSet = new Set(prev)\n newSet.delete(value)\n return newSet\n })\n }, [])\n\n const [indicatorReady, setIndicatorReady] = React.useState(false)\n\n return (\n <TabsContext.Provider\n value={{\n selectedValue,\n setSelectedValue,\n variant,\n orientation,\n isDisabledTab,\n indicatorReady,\n setIndicatorReady,\n }}\n >\n <div\n ref={ref}\n className={cn(\"tabs\", css.tabs, root, className)}\n data-orientation={orientation}\n >\n {React.Children.map(children, (child) =>\n React.isValidElement(child) && child.type === TabsTrigger\n ? React.cloneElement(child, {\n _registerDisabled: registerDisabledTab,\n _unregisterDisabled: unregisterDisabledTab,\n } as any)\n : child\n )}\n </div>\n </TabsContext.Provider>\n )\n }\n)\nTabsRoot.displayName = \"Tabs\"\n\nexport interface TabsListStyleSlots {\n root?: StyleValue\n indicator?: StyleValue\n}\n\ninterface TabsListProps {\n /** Additional CSS class names */\n className?: string\n children?: React.ReactNode\n /** Accessible label for the tab list */\n \"aria-label\"?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsListStyleSlots>\n}\n\nconst resolveTabsListBaseStyles = createStylesResolver(['root', 'indicator'] as const);\n\n/** Container for the row of tab trigger buttons */\nconst TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(\n ({ className, children, \"aria-label\": ariaLabel, styles: stylesProp }, ref) => {\n const { selectedValue, variant, orientation, setIndicatorReady } = useTabsContext()\n const { root, indicator } = resolveTabsListBaseStyles(stylesProp)\n const listRef = React.useRef<HTMLDivElement>(null)\n const [indicatorPosition, setIndicatorPosition] = React.useState<IndicatorPosition>({\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n })\n const [listDimensions, setListDimensions] = React.useState<ListDimensions>({\n width: 0,\n height: 0,\n })\n\n const indicatorClassName = React.useMemo(\n () => getTabsIndicatorClassName(indicator, variant),\n [indicator, variant]\n )\n const tabsListContext = React.useMemo(\n () => ({ indicatorClassName }),\n [indicatorClassName]\n )\n\n const measureTrigger = React.useCallback((element: HTMLElement | null) => {\n if (!element) return null\n\n const rect = element.getBoundingClientRect()\n const listRect = listRef.current?.getBoundingClientRect()\n\n if (!listRect) return null\n\n const relativeLeft = rect.left - listRect.left\n const relativeTop = rect.top - listRect.top\n\n return {\n left: relativeLeft,\n top: relativeTop,\n width: rect.width,\n height: rect.height,\n }\n }, [])\n\n const measureList = React.useCallback(() => {\n if (!listRef.current) return\n const rect = listRef.current.getBoundingClientRect()\n setListDimensions({\n width: rect.width,\n height: rect.height,\n })\n }, [])\n\n const updateIndicator = React.useCallback(\n (value: string) => {\n if (!listRef.current) return\n\n const trigger = listRef.current.querySelector(\n `[data-tabs-value=\"${value}\"]`\n ) as HTMLElement | null\n\n if (trigger) {\n const position = measureTrigger(trigger)\n if (position) {\n setIndicatorPosition(position)\n }\n }\n },\n [measureTrigger]\n )\n\n React.useLayoutEffect(() => {\n measureList()\n updateIndicator(selectedValue)\n setIndicatorReady(true)\n }, [selectedValue, updateIndicator, measureList, setIndicatorReady])\n\n React.useEffect(() => {\n if (!listRef.current) return\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n })\n\n resizeObserver.observe(listRef.current)\n return () => resizeObserver.disconnect()\n }, [selectedValue, updateIndicator, measureList])\n\n React.useEffect(() => {\n const handleWindowResize = () => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n }\n\n window.addEventListener(\"resize\", handleWindowResize)\n return () => window.removeEventListener(\"resize\", handleWindowResize)\n }, [selectedValue, updateIndicator, measureList])\n\n const getIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n transition: \"all 0.2s cubic-bezier(0.4, 0, 0.2, 1)\",\n willChange: \"transform\",\n pointerEvents: \"none\",\n margin: 0,\n opacity: indicatorPosition.width === 0 && indicatorPosition.height === 0 ? 0 : 1,\n }\n\n if (indicatorPosition.width === 0 && indicatorPosition.height === 0) {\n return baseStyle\n }\n\n if (orientation === \"vertical\") {\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left - TABS_UNDERLINE_GUTTER,\n top: indicatorPosition.top,\n width: TABS_UNDERLINE_THICKNESS,\n height: indicatorPosition.height,\n }\n }\n const horizontalPadding = TABS_INDICATOR_INSET\n const adjustedWidth = Math.max(0, listDimensions.width - horizontalPadding * 2)\n return {\n ...baseStyle,\n left: horizontalPadding,\n top: indicatorPosition.top,\n width: adjustedWidth,\n height: indicatorPosition.height,\n }\n }\n\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top + indicatorPosition.height + TABS_UNDERLINE_OFFSET,\n width: indicatorPosition.width,\n height: TABS_UNDERLINE_THICKNESS,\n }\n }\n\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top,\n width: indicatorPosition.width,\n height: indicatorPosition.height,\n }\n }, [indicatorPosition, listDimensions, variant, orientation])\n\n const mergedRef = React.useCallback(\n (el: HTMLDivElement | null) => {\n listRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <TabsListContext.Provider value={tabsListContext}>\n <div\n ref={mergedRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n className={cn(\"tabs\", \"list\", css.list, root, className)}\n data-variant={variant}\n data-orientation={orientation}\n style={{ position: \"relative\" }}\n >\n {children}\n {indicatorPosition.width > 0 && (\n <div\n aria-hidden=\"true\"\n className={indicatorClassName}\n style={getIndicatorStyle}\n />\n )}\n </div>\n </TabsListContext.Provider>\n )\n }\n)\nTabsList.displayName = \"TabsList\"\n\ninterface TabsTriggerIconStyles {\n left?: StyleValue\n right?: StyleValue\n}\n\ninterface TabsTriggerIconSlots {\n left?: React.ReactNode\n right?: React.ReactNode\n}\n\nexport interface TabsTriggerStyleSlots {\n root?: StyleValue\n icon?: StyleValue | TabsTriggerIconStyles\n}\n\ninterface TabsTriggerProps {\n /** Unique identifier matching the associated TabsContent value */\n value: string\n /** Whether the tab trigger is disabled */\n disabled?: boolean\n /** Icon element(s) rendered inside the trigger. Pass a node for left-only, or { left, right } for both sides. */\n icon?: React.ReactNode | TabsTriggerIconSlots\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsTriggerStyleSlots>\n children?: React.ReactNode\n _registerDisabled?: (value: string) => void\n _unregisterDisabled?: (value: string) => void\n}\n\nconst resolveTabsTriggerBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction isTabsTriggerIconSlots(icon: TabsTriggerProps[\"icon\"]): icon is TabsTriggerIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon)\n}\n\nfunction resolveTabsTriggerIcon(icon: TabsTriggerProps[\"icon\"]): TabsTriggerIconSlots | undefined {\n if (!icon) return undefined\n if (isTabsTriggerIconSlots(icon)) return icon\n return { left: icon }\n}\n\nfunction resolveTabsTriggerStyles(styles: StylesProp<TabsTriggerStyleSlots> | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveTabsTriggerBaseStyles(styles)\n const { root, icon } = styles\n\n let iconLeft: StyleValue | undefined\n let iconRight: StyleValue | undefined\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon\n iconRight = icon\n } else {\n iconLeft = icon.left\n iconRight = icon.right\n }\n }\n\n return resolveTabsTriggerBaseStyles({ root, iconLeft, iconRight })\n}\n\n/** A tab button that activates its associated content panel */\nconst TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(\n (\n {\n value,\n disabled = false,\n icon,\n className,\n styles: stylesProp,\n children,\n _registerDisabled,\n _unregisterDisabled,\n },\n ref\n ) => {\n const { selectedValue, setSelectedValue, indicatorReady, orientation, variant } = useTabsContext()\n const tabsListContext = useTabsListContext()\n const resolved = resolveTabsTriggerStyles(stylesProp)\n const resolvedIcon = resolveTabsTriggerIcon(icon)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const isSelected = value === selectedValue\n const showIndicatorFallback = isSelected && !indicatorReady && !!tabsListContext\n const fallbackIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n if (variant === \"underline\") {\n if (orientation === \"vertical\") {\n return {\n top: 0,\n bottom: 0,\n left: -TABS_UNDERLINE_FALLBACK_GUTTER,\n width: TABS_UNDERLINE_THICKNESS,\n height: \"100%\",\n margin: 0,\n }\n }\n\n return {\n left: 0,\n right: 0,\n bottom: -TABS_UNDERLINE_FALLBACK_GUTTER,\n width: \"100%\",\n height: TABS_UNDERLINE_THICKNESS,\n margin: 0,\n }\n }\n\n return {\n inset: 0,\n margin: 0,\n }\n }, [orientation, variant])\n\n const { focusProps, isFocusVisible } = useFocusRing()\n\n React.useEffect(() => {\n if (disabled) {\n _registerDisabled?.(value)\n } else {\n _unregisterDisabled?.(value)\n }\n }, [disabled, value, _registerDisabled, _unregisterDisabled])\n\n const handleClick = React.useCallback(() => {\n if (!disabled) {\n setSelectedValue(value)\n }\n }, [disabled, value, setSelectedValue])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return\n\n const listElement = buttonRef.current?.parentElement\n if (!listElement) return\n\n const triggers = Array.from(\n listElement.querySelectorAll('[data-tabs-value]')\n ) as HTMLButtonElement[]\n const currentIndex = triggers.findIndex((el) => el.getAttribute(\"data-tabs-value\") === value)\n\n let nextValue: string | null = null\n\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n e.preventDefault()\n const nextIndex = (currentIndex + 1) % triggers.length\n nextValue = triggers[nextIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n e.preventDefault()\n const prevIndex = currentIndex === 0 ? triggers.length - 1 : currentIndex - 1\n nextValue = triggers[prevIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"Home\") {\n e.preventDefault()\n nextValue = triggers[0].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"End\") {\n e.preventDefault()\n nextValue = triggers[triggers.length - 1].getAttribute(\"data-tabs-value\")\n }\n\n if (nextValue) {\n setSelectedValue(nextValue)\n setTimeout(() => {\n const nextTrigger = listElement.querySelector(\n `[data-tabs-value=\"${nextValue}\"]`\n ) as HTMLButtonElement | null\n nextTrigger?.focus()\n }, 0)\n }\n },\n [value, disabled, setSelectedValue]\n )\n\n const mergedRef = React.useCallback(\n (el: HTMLButtonElement | null) => {\n buttonRef.current = el\n if (typeof ref === \"function\") ref(el)\n else if (ref) ref.current = el\n },\n [ref]\n )\n\n return (\n <button\n {...asElementProps<\"button\">(focusProps)}\n ref={mergedRef}\n id={`${value}-trigger`}\n role=\"tab\"\n aria-selected={isSelected}\n aria-controls={`${value}-content`}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n data-tabs-value={value}\n className={cn(\"tabs\", \"trigger\", css.trigger, resolved.root, className)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-indicator-ready={isSelected && indicatorReady ? \"true\" : undefined}\n data-indicator-fallback={showIndicatorFallback ? \"true\" : undefined}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {showIndicatorFallback && tabsListContext && (\n <span\n aria-hidden=\"true\"\n className={cn(tabsListContext.indicatorClassName, css[\"indicator-fallback\"])}\n style={fallbackIndicatorStyle}\n />\n )}\n {resolvedIcon?.left && <span className={cn(css.icon, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(css.icon, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </button>\n )\n }\n)\nTabsTrigger.displayName = \"Tab\"\n\nexport interface TabsContentStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsContentProps {\n /** Unique identifier matching the associated TabsTrigger value */\n value: string\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsContentStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsContentBaseStyles = createStylesResolver(['root'] as const);\n\n/** Content panel shown when its corresponding tab is active */\nconst TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(\n ({ value, className, children, styles: stylesProp }, ref) => {\n const { selectedValue, orientation } = useTabsContext()\n const { root } = resolveTabsContentBaseStyles(stylesProp);\n const isVisible = value === selectedValue\n\n return (\n <div\n ref={ref}\n role=\"tabpanel\"\n aria-labelledby={`${value}-trigger`}\n id={`${value}-content`}\n className={cn(\"tabs\", \"content\", css.content, root, className)}\n data-orientation={orientation}\n hidden={!isVisible}\n >\n {isVisible && children}\n </div>\n )\n }\n)\nTabsContent.displayName = \"TabsContent\"\n\nconst Tabs = Object.assign(TabsRoot, {\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n})\n\nexport { Tabs }\nexport type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps }\n",
|
|
4457
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0 0 4px;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--border-color);\n align-items: stretch;\n padding: 0 0 0 4px;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--background);\n box-sizing: border-box;\n border-radius: var(--radius-sm);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-fallback {\n z-index: -1;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2
|
|
4581
|
+
"tsx": "\"use client\"\n\nimport * as React from \"react\"\nimport { useFocusRing } from \"react-aria\"\nimport { useInteractionModality } from \"@react-aria/interactions\"\nimport { cn } from \"./utils\"\nimport { StyleValue } from \"./utils\"\nimport { asElementProps } from \"@/lib/react-aria\"\nimport { StylesProp, createStylesResolver } from \"@/lib/styles\"\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\"\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\"\nimport css from \"./Tabs.module.css\"\n\ntype TabsVariant = \"underline\"\ntype TabsOrientation = \"horizontal\" | \"vertical\"\n\ninterface IndicatorPosition {\n left: number\n top: number\n width: number\n height: number\n}\n\ninterface ListDimensions {\n width: number\n height: number\n}\n\ninterface TabsContextValue {\n selectedValue: string\n setSelectedValue: (value: string) => void\n variant?: TabsVariant\n orientation: TabsOrientation\n isDisabledTab: (value: string) => boolean\n indicatorReady: boolean\n setIndicatorReady: (ready: boolean) => void\n}\n\nconst TabsContext = React.createContext<TabsContextValue | null>(null)\nconst TABS_INDICATOR_INSET = 4\nconst TABS_UNDERLINE_THICKNESS = 2\nconst TABS_UNDERLINE_OFFSET = 2\nconst TABS_UNDERLINE_GUTTER = TABS_UNDERLINE_THICKNESS + TABS_UNDERLINE_OFFSET\nconst TABS_UNDERLINE_FALLBACK_GUTTER = TABS_UNDERLINE_GUTTER + TABS_UNDERLINE_THICKNESS\n\ninterface TabsListContextValue {\n indicatorClassName: string\n}\n\nconst TabsListContext = React.createContext<TabsListContextValue | null>(null)\n\nfunction useTabsContext() {\n const context = React.useContext(TabsContext)\n if (!context) {\n throw new Error(\"Tabs component must be used within Tabs root\")\n }\n return context\n}\n\nfunction useTabsListContext() {\n return React.useContext(TabsListContext)\n}\n\nfunction getTabsIndicatorClassName(indicator: string, variant?: TabsVariant) {\n return cn(\n \"tabs\",\n \"indicator\",\n variant === \"underline\" && \"underline\",\n variant === \"underline\" && \"indicator-underline\",\n css.indicator,\n {\n [css[\"indicator-underline\"]]: variant === \"underline\",\n },\n indicator\n )\n}\n\nexport interface TabsStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsProps {\n /** Optional alternate visual style of the tab list indicator */\n variant?: TabsVariant\n /** Direction of the tab list layout */\n orientation?: TabsOrientation\n /** Initially selected tab value for uncontrolled usage */\n default?: string\n /** Controlled selected tab value */\n value?: string\n /** Called when the selected tab changes */\n onValueChange?: (value: string) => void\n /** Additional CSS class for the tabs root */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsBaseStyles = createStylesResolver(['root'] as const)\n\nconst TabsRoot = React.forwardRef<HTMLDivElement, TabsProps>(\n (\n {\n variant,\n orientation = \"horizontal\",\n default: defaultTab,\n value: controlledValue,\n onValueChange,\n className,\n styles: stylesProp,\n children,\n },\n ref\n ) => {\n const { root } = resolveTabsBaseStyles(stylesProp)\n\n const [uncontrolledValue, setUncontrolledValue] = React.useState(defaultTab || \"\")\n const [disabledTabs, setDisabledTabs] = React.useState<Set<string>>(new Set())\n\n const selectedValue = controlledValue !== undefined ? controlledValue : uncontrolledValue\n const isDisabledTab = React.useCallback(\n (value: string) => disabledTabs.has(value),\n [disabledTabs]\n )\n\n const setSelectedValue = React.useCallback(\n (newValue: string) => {\n if (!isDisabledTab(newValue)) {\n if (controlledValue === undefined) {\n setUncontrolledValue(newValue)\n }\n onValueChange?.(newValue)\n }\n },\n [controlledValue, isDisabledTab, onValueChange]\n )\n\n const registerDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => new Set(prev).add(value))\n }, [])\n\n const unregisterDisabledTab = React.useCallback((value: string) => {\n setDisabledTabs((prev) => {\n const newSet = new Set(prev)\n newSet.delete(value)\n return newSet\n })\n }, [])\n\n const [indicatorReady, setIndicatorReady] = React.useState(false)\n\n return (\n <TabsContext.Provider\n value={{\n selectedValue,\n setSelectedValue,\n variant,\n orientation,\n isDisabledTab,\n indicatorReady,\n setIndicatorReady,\n }}\n >\n <div\n ref={ref}\n className={cn(\"tabs\", css.tabs, root, className)}\n data-orientation={orientation}\n >\n {React.Children.map(children, (child) =>\n React.isValidElement(child) && child.type === TabsTrigger\n ? React.cloneElement(child, {\n _registerDisabled: registerDisabledTab,\n _unregisterDisabled: unregisterDisabledTab,\n } as any)\n : child\n )}\n </div>\n </TabsContext.Provider>\n )\n }\n)\nTabsRoot.displayName = \"Tabs\"\n\nexport interface TabsListStyleSlots {\n root?: StyleValue\n indicator?: StyleValue\n}\n\ninterface TabsListProps {\n /** Additional CSS class names */\n className?: string\n children?: React.ReactNode\n /** Accessible label for the tab list */\n \"aria-label\"?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsListStyleSlots>\n}\n\nconst resolveTabsListBaseStyles = createStylesResolver(['root', 'indicator'] as const);\n\n/** Container for the row of tab trigger buttons */\nconst TabsList = React.forwardRef<HTMLDivElement, TabsListProps>(\n ({ className, children, \"aria-label\": ariaLabel, styles: stylesProp }, ref) => {\n const { selectedValue, variant, orientation, setIndicatorReady } = useTabsContext()\n const { root, indicator } = resolveTabsListBaseStyles(stylesProp)\n const scopeRef = React.useRef<HTMLDivElement>(null)\n const listRef = React.useRef<HTMLDivElement>(null)\n const { scopeProps: focusScopeProps, indicatorProps: focusIndicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef: listRef,\n surfaceSelector: '[data-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n mode: \"ring\",\n dependencies: [selectedValue, orientation, variant],\n })\n const [indicatorPosition, setIndicatorPosition] = React.useState<IndicatorPosition>({\n left: 0,\n top: 0,\n width: 0,\n height: 0,\n })\n const [listDimensions, setListDimensions] = React.useState<ListDimensions>({\n width: 0,\n height: 0,\n })\n\n const indicatorClassName = React.useMemo(\n () => getTabsIndicatorClassName(indicator, variant),\n [indicator, variant]\n )\n const tabsListContext = React.useMemo(\n () => ({ indicatorClassName }),\n [indicatorClassName]\n )\n\n const measureTrigger = React.useCallback((element: HTMLElement | null) => {\n if (!element) return null\n\n const rect = element.getBoundingClientRect()\n const listRect = listRef.current?.getBoundingClientRect()\n\n if (!listRect) return null\n\n const relativeLeft = rect.left - listRect.left\n const relativeTop = rect.top - listRect.top\n\n return {\n left: relativeLeft,\n top: relativeTop,\n width: rect.width,\n height: rect.height,\n }\n }, [])\n\n const measureList = React.useCallback(() => {\n if (!listRef.current) return\n const rect = listRef.current.getBoundingClientRect()\n setListDimensions({\n width: rect.width,\n height: rect.height,\n })\n }, [])\n\n const updateIndicator = React.useCallback(\n (value: string) => {\n if (!listRef.current) return\n\n const trigger = listRef.current.querySelector(\n `[data-tabs-value=\"${value}\"]`\n ) as HTMLElement | null\n\n if (trigger) {\n const position = measureTrigger(trigger)\n if (position) {\n setIndicatorPosition(position)\n }\n }\n },\n [measureTrigger]\n )\n\n React.useLayoutEffect(() => {\n measureList()\n updateIndicator(selectedValue)\n setIndicatorReady(true)\n }, [selectedValue, updateIndicator, measureList, setIndicatorReady])\n\n React.useEffect(() => {\n if (!listRef.current) return\n\n const resizeObserver = new ResizeObserver(() => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n })\n\n resizeObserver.observe(listRef.current)\n return () => resizeObserver.disconnect()\n }, [selectedValue, updateIndicator, measureList])\n\n React.useEffect(() => {\n const handleWindowResize = () => {\n requestAnimationFrame(() => {\n measureList()\n updateIndicator(selectedValue)\n })\n }\n\n window.addEventListener(\"resize\", handleWindowResize)\n return () => window.removeEventListener(\"resize\", handleWindowResize)\n }, [selectedValue, updateIndicator, measureList])\n\n const getIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n const baseStyle: React.CSSProperties = {\n position: \"absolute\",\n transition: \"all 0.2s cubic-bezier(0.4, 0, 0.2, 1)\",\n willChange: \"transform\",\n pointerEvents: \"none\",\n margin: 0,\n opacity: indicatorPosition.width === 0 && indicatorPosition.height === 0 ? 0 : 1,\n }\n\n if (indicatorPosition.width === 0 && indicatorPosition.height === 0) {\n return baseStyle\n }\n\n if (orientation === \"vertical\") {\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left - TABS_UNDERLINE_GUTTER,\n top: indicatorPosition.top,\n width: TABS_UNDERLINE_THICKNESS,\n height: indicatorPosition.height,\n }\n }\n const horizontalPadding = TABS_INDICATOR_INSET\n const adjustedWidth = Math.max(0, listDimensions.width - horizontalPadding * 2)\n return {\n ...baseStyle,\n left: horizontalPadding,\n top: indicatorPosition.top,\n width: adjustedWidth,\n height: indicatorPosition.height,\n }\n }\n\n if (variant === \"underline\") {\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top + indicatorPosition.height + TABS_UNDERLINE_OFFSET,\n width: indicatorPosition.width,\n height: TABS_UNDERLINE_THICKNESS,\n }\n }\n\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: indicatorPosition.top,\n width: indicatorPosition.width,\n height: indicatorPosition.height,\n }\n }, [indicatorPosition, listDimensions, variant, orientation])\n\n const mergedRef = useMergeRefs(listRef, ref)\n\n return (\n <TabsListContext.Provider value={tabsListContext}>\n <div ref={scopeRef} className={cn(\"tabs-scope\", focusScopeProps.className)}>\n <div {...focusIndicatorProps} />\n <div\n ref={mergedRef}\n role=\"tablist\"\n aria-label={ariaLabel}\n aria-orientation={orientation}\n className={cn(\"tabs\", \"list\", css.list, root, className)}\n data-variant={variant}\n data-orientation={orientation}\n style={{ position: \"relative\" }}\n >\n {children}\n {indicatorPosition.width > 0 && (\n <div\n aria-hidden=\"true\"\n className={indicatorClassName}\n style={getIndicatorStyle}\n />\n )}\n </div>\n </div>\n </TabsListContext.Provider>\n )\n }\n)\nTabsList.displayName = \"TabsList\"\n\ninterface TabsTriggerIconStyles {\n left?: StyleValue\n right?: StyleValue\n}\n\ninterface TabsTriggerIconSlots {\n left?: React.ReactNode\n right?: React.ReactNode\n}\n\nexport interface TabsTriggerStyleSlots {\n root?: StyleValue\n icon?: StyleValue | TabsTriggerIconStyles\n}\n\ninterface TabsTriggerProps {\n /** Unique identifier matching the associated TabsContent value */\n value: string\n /** Whether the tab trigger is disabled */\n disabled?: boolean\n /** Icon element(s) rendered inside the trigger. Pass a node for left-only, or { left, right } for both sides. */\n icon?: React.ReactNode | TabsTriggerIconSlots\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsTriggerStyleSlots>\n children?: React.ReactNode\n _registerDisabled?: (value: string) => void\n _unregisterDisabled?: (value: string) => void\n}\n\nconst resolveTabsTriggerBaseStyles = createStylesResolver(['root', 'iconLeft', 'iconRight'] as const);\n\nfunction isTabsTriggerIconSlots(icon: TabsTriggerProps[\"icon\"]): icon is TabsTriggerIconSlots {\n return typeof icon === \"object\" && icon !== null && !React.isValidElement(icon) && ('left' in icon || 'right' in icon)\n}\n\nfunction resolveTabsTriggerIcon(icon: TabsTriggerProps[\"icon\"]): TabsTriggerIconSlots | undefined {\n if (!icon) return undefined\n if (isTabsTriggerIconSlots(icon)) return icon\n return { left: icon }\n}\n\nfunction resolveTabsTriggerStyles(styles: StylesProp<TabsTriggerStyleSlots> | undefined) {\n if (!styles || typeof styles === 'string' || Array.isArray(styles)) return resolveTabsTriggerBaseStyles(styles)\n const { root, icon } = styles\n\n let iconLeft: StyleValue | undefined\n let iconRight: StyleValue | undefined\n\n if (icon) {\n if (typeof icon === 'string' || Array.isArray(icon)) {\n iconLeft = icon\n iconRight = icon\n } else {\n iconLeft = icon.left\n iconRight = icon.right\n }\n }\n\n return resolveTabsTriggerBaseStyles({ root, iconLeft, iconRight })\n}\n\n/** A tab button that activates its associated content panel */\nconst TabsTrigger = React.forwardRef<HTMLButtonElement, TabsTriggerProps>(\n (\n {\n value,\n disabled = false,\n icon,\n className,\n styles: stylesProp,\n children,\n _registerDisabled,\n _unregisterDisabled,\n },\n ref\n ) => {\n const { selectedValue, setSelectedValue, indicatorReady, orientation, variant } = useTabsContext()\n const tabsListContext = useTabsListContext()\n const resolved = resolveTabsTriggerStyles(stylesProp)\n const resolvedIcon = resolveTabsTriggerIcon(icon)\n const buttonRef = React.useRef<HTMLButtonElement>(null)\n const isSelected = value === selectedValue\n const showIndicatorFallback = isSelected && !indicatorReady && !!tabsListContext\n const fallbackIndicatorStyle = React.useMemo<React.CSSProperties>(() => {\n if (variant === \"underline\") {\n if (orientation === \"vertical\") {\n return {\n top: 0,\n bottom: 0,\n left: -TABS_UNDERLINE_FALLBACK_GUTTER,\n width: TABS_UNDERLINE_THICKNESS,\n height: \"100%\",\n margin: 0,\n }\n }\n\n return {\n left: 0,\n right: 0,\n bottom: -TABS_UNDERLINE_FALLBACK_GUTTER,\n width: \"100%\",\n height: TABS_UNDERLINE_THICKNESS,\n margin: 0,\n }\n }\n\n return {\n inset: 0,\n margin: 0,\n }\n }, [orientation, variant])\n\n const { focusProps, isFocused, isFocusVisible } = useFocusRing()\n const interactionModality = useInteractionModality()\n const showFocusVisible = isFocused && interactionModality !== \"pointer\" && isFocusVisible\n\n React.useEffect(() => {\n if (disabled) {\n _registerDisabled?.(value)\n } else {\n _unregisterDisabled?.(value)\n }\n }, [disabled, value, _registerDisabled, _unregisterDisabled])\n\n const handleClick = React.useCallback(() => {\n if (!disabled) {\n setSelectedValue(value)\n }\n }, [disabled, value, setSelectedValue])\n\n const handleKeyDown = React.useCallback(\n (e: React.KeyboardEvent) => {\n if (disabled) return\n\n const listElement = buttonRef.current?.parentElement\n if (!listElement) return\n\n const triggers = Array.from(\n listElement.querySelectorAll('[data-tabs-value]')\n ) as HTMLButtonElement[]\n const currentIndex = triggers.findIndex((el) => el.getAttribute(\"data-tabs-value\") === value)\n\n let nextValue: string | null = null\n\n if (e.key === \"Tab\") {\n const nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1\n const nextTrigger = triggers[nextIndex]\n\n if (nextTrigger) {\n e.preventDefault()\n nextTrigger.focus()\n }\n return\n }\n\n if (e.key === \"ArrowRight\" || e.key === \"ArrowDown\") {\n e.preventDefault()\n const nextIndex = (currentIndex + 1) % triggers.length\n nextValue = triggers[nextIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"ArrowLeft\" || e.key === \"ArrowUp\") {\n e.preventDefault()\n const prevIndex = currentIndex === 0 ? triggers.length - 1 : currentIndex - 1\n nextValue = triggers[prevIndex].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"Home\") {\n e.preventDefault()\n nextValue = triggers[0].getAttribute(\"data-tabs-value\")\n } else if (e.key === \"End\") {\n e.preventDefault()\n nextValue = triggers[triggers.length - 1].getAttribute(\"data-tabs-value\")\n }\n\n if (nextValue) {\n setSelectedValue(nextValue)\n setTimeout(() => {\n const nextTrigger = listElement.querySelector(\n `[data-tabs-value=\"${nextValue}\"]`\n ) as HTMLButtonElement | null\n nextTrigger?.focus()\n }, 0)\n }\n },\n [value, disabled, setSelectedValue]\n )\n\n const mergedRef = useMergeRefs(buttonRef, ref)\n\n return (\n <button\n {...asElementProps<\"button\">(focusProps)}\n ref={mergedRef}\n id={`${value}-trigger`}\n role=\"tab\"\n aria-selected={isSelected}\n aria-controls={`${value}-content`}\n tabIndex={isSelected ? 0 : -1}\n disabled={disabled}\n data-tabs-value={value}\n data-focus-surface=\"true\"\n className={cn(\"tabs\", \"trigger\", css.trigger, resolved.root, className)}\n data-selected={isSelected ? \"true\" : \"false\"}\n data-disabled={disabled ? \"true\" : undefined}\n data-focus-visible={showFocusVisible ? \"true\" : \"false\"}\n data-indicator-ready={isSelected && indicatorReady ? \"true\" : undefined}\n data-indicator-fallback={showIndicatorFallback ? \"true\" : undefined}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n >\n {showIndicatorFallback && tabsListContext && (\n <span\n aria-hidden=\"true\"\n className={cn(tabsListContext.indicatorClassName, css[\"indicator-fallback\"])}\n style={fallbackIndicatorStyle}\n />\n )}\n {resolvedIcon?.left && <span className={cn(css.icon, resolved.iconLeft)}>{resolvedIcon.left}</span>}\n {children}\n {resolvedIcon?.right && <span className={cn(css.icon, resolved.iconRight)}>{resolvedIcon.right}</span>}\n </button>\n )\n }\n)\nTabsTrigger.displayName = \"Tab\"\n\nexport interface TabsContentStyleSlots {\n root?: StyleValue\n}\n\ninterface TabsContentProps {\n /** Unique identifier matching the associated TabsTrigger value */\n value: string\n /** Additional CSS class names */\n className?: string\n /** Custom styles for the component slots */\n styles?: StylesProp<TabsContentStyleSlots>\n children?: React.ReactNode\n}\n\nconst resolveTabsContentBaseStyles = createStylesResolver(['root'] as const);\n\n/** Content panel shown when its corresponding tab is active */\nconst TabsContent = React.forwardRef<HTMLDivElement, TabsContentProps>(\n ({ value, className, children, styles: stylesProp }, ref) => {\n const { selectedValue, orientation } = useTabsContext()\n const { root } = resolveTabsContentBaseStyles(stylesProp);\n const isVisible = value === selectedValue\n\n return (\n <div\n ref={ref}\n role=\"tabpanel\"\n aria-labelledby={`${value}-trigger`}\n id={`${value}-content`}\n className={cn(\"tabs\", \"content\", css.content, root, className)}\n data-orientation={orientation}\n hidden={!isVisible}\n >\n {isVisible && children}\n </div>\n )\n }\n)\nTabsContent.displayName = \"TabsContent\"\n\nconst Tabs = Object.assign(TabsRoot, {\n List: TabsList,\n Trigger: TabsTrigger,\n Content: TabsContent,\n})\n\nexport { Tabs }\nexport type { TabsProps, TabsListProps, TabsTriggerProps, TabsContentProps }\n",
|
|
4582
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .tabs {\n @apply flex w-full flex-col;\n\n &[data-orientation=\"vertical\"] {\n flex-direction: row;\n }\n }\n\n .list {\n @apply relative flex w-full flex-row items-center gap-3 py-1;\n border-radius: var(--radius-sm);\n\n &[data-orientation=\"vertical\"] {\n flex-direction: column;\n width: auto;\n min-width: 120px;\n height: 100%;\n }\n\n &[data-variant=\"underline\"] {\n background-color: transparent;\n border-radius: 0;\n padding: 0 0 4px;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--border-color);\n align-items: stretch;\n padding: 0 0 0 4px;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--background);\n box-sizing: border-box;\n border-radius: var(--radius-sm);\n z-index: 0;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n pointer-events: none;\n }\n\n .indicator-fallback {\n z-index: -1;\n }\n\n .indicator-underline {\n border-radius: 0;\n }\n\n .trigger {\n @apply relative z-[1] flex shrink-0 items-center justify-center gap-2 px-2 py-1.5 cursor-pointer select-none;\n height: 100%;\n background-color: var(--background);\n border: none;\n color: var(--foreground);\n outline: none;\n box-shadow: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]):not([data-selected=\"true\"]) {\n &:hover {\n background-color: var(--background-hover);\n color: var(--foreground-hover);\n }\n\n &:active {\n background-color: var(--background-pressed);\n color: var(--foreground-pressed);\n }\n }\n\n &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n color: var(--foreground-selected);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n .list & {\n background-color: var(--background-selected);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--underline-border);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--underline-border);\n }\n }\n\n &:focus,\n &:focus-visible {\n outline: none !important;\n box-shadow: none !important;\n }\n\n &[data-disabled=\"true\"] {\n --disabled-opacity: 0.5;\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n pointer-events: none;\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: var(--background);\n background-clip: padding-box;\n border-radius: var(--radius-sm);\n border-bottom: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"] &:not([data-disabled]):not([data-selected=\"true\"]):hover {\n background-color: var(--background-hover);\n }\n\n .list[data-variant=\"underline\"] &:not([data-disabled]):not([data-selected=\"true\"]):active {\n background-color: var(--background-pressed);\n }\n\n .list[data-variant=\"underline\"] &[data-selected=\"true\"] {\n background-color: var(--background-selected);\n }\n\n .list[data-variant=\"underline\"] &:focus,\n .list[data-variant=\"underline\"] &:focus-visible {\n outline: none !important;\n box-shadow: none !important;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom: none;\n border-left: 2px solid transparent;\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n border-left-color: var(--underline-border);\n border-bottom: none;\n }\n }\n\n .icon {\n @apply flex h-4 w-4 shrink-0 items-center justify-center;\n }\n\n .content {\n @apply w-full p-0 outline-none;\n flex: 1;\n padding-top: 1rem;\n\n &[data-orientation=\"vertical\"] {\n flex: 1;\n width: 100%;\n }\n\n &:focus-visible {\n outline: 2px solid var(--focus-visible);\n outline-offset: 2px;\n }\n }\n\n @media (max-width: 640px) {\n .list {\n padding: 0.125rem;\n\n &[data-variant=\"underline\"] {\n padding: 0 0 4px;\n }\n }\n\n .trigger {\n @apply px-1 py-1;\n .list[data-variant=\"underline\"] & {\n margin: 0.5rem 0.75rem;\n }\n }\n }\n}\n",
|
|
4458
4583
|
"cssTypes": "declare const styles: {\n tabs: string;\n list: string;\n indicator: string;\n \"indicator-fallback\": string;\n \"indicator-underline\": string;\n trigger: string;\n icon: string;\n content: string;\n};\n\nexport default styles;\n"
|
|
4459
4584
|
},
|
|
4460
4585
|
"textarea": {
|
|
4461
|
-
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState, type ComponentPropsWithoutRef } from \"react\";\n\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useFocusRing } from \"@react-aria/focus\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { Scroll } from \"@/components/Scroll\";\nimport css from \"./Textarea.module.css\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\ntype ResizeAxis = \"both\" | \"x\" | \"y\" | \"none\";\n\ntype TextAreaResizeHandleStyles = {\n both?: StyleValue;\n x?: StyleValue;\n y?: StyleValue;\n};\n\nexport interface TextAreaStyleSlots {\n root?: StyleValue;\n container?: StyleValue;\n surface?: StyleValue;\n scrollWrapper?: StyleValue;\n resizeHandle?: StyleValue | TextAreaResizeHandleStyles;\n characterCount?: StyleValue;\n}\n\nexport type TextAreaStylesProp = StylesProp<TextAreaStyleSlots>;\n\nconst resolveTextAreaBaseStyles = createStylesResolver([\n \"root\",\n \"container\",\n \"surface\",\n \"scrollWrapper\",\n \"resizeHandleBoth\",\n \"resizeHandleX\",\n \"resizeHandleY\",\n \"characterCount\",\n] as const);\n\nfunction resolveTextAreaStyles(styles: TextAreaStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveTextAreaBaseStyles(styles);\n const { root, container, surface, scrollWrapper, resizeHandle, characterCount } = styles;\n\n let resizeHandleBoth: StyleValue | undefined;\n let resizeHandleX: StyleValue | undefined;\n let resizeHandleY: StyleValue | undefined;\n\n if (resizeHandle) {\n if (typeof resizeHandle === \"string\" || Array.isArray(resizeHandle)) {\n resizeHandleBoth = resizeHandle;\n resizeHandleX = resizeHandle;\n resizeHandleY = resizeHandle;\n } else {\n resizeHandleBoth = resizeHandle.both;\n resizeHandleX = resizeHandle.x;\n resizeHandleY = resizeHandle.y;\n }\n }\n\n return resolveTextAreaBaseStyles({\n root,\n container,\n surface,\n scrollWrapper,\n resizeHandleBoth,\n resizeHandleX,\n resizeHandleY,\n characterCount,\n });\n}\n\nexport interface TextAreaProps extends Omit<ComponentPropsWithoutRef<\"textarea\">, \"size\"> {\n /** Size of the textarea */\n size?: Size;\n /** Whether to apply error styling */\n error?: boolean;\n /** Whether the textarea can be manually resized by the user. When enabled, `className` may include Tailwind `resize`, `resize-x`, `resize-y`, or `resize-none` to select the resize axis. */\n resizable?: boolean;\n /** Whether to display a character count below the textarea */\n showCharacterCount?: boolean;\n /** Maximum number of characters allowed */\n maxCharacters?: number;\n /** Maximum height before the custom scrollbar activates */\n maxHeight?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TextAreaStylesProp;\n}\n\nfunction useMergedRef<T>(...refs: (React.Ref<T> | undefined)[]): React.RefCallback<T> {\n return React.useCallback((value: T) => {\n refs.forEach((ref) => {\n if (typeof ref === \"function\") ref(value);\n else if (ref && typeof ref === \"object\") (ref as React.MutableRefObject<T | null>).current = value;\n });\n }, refs);\n}\n\nconst resizeClassMap: Record<string, ResizeAxis> = {\n resize: \"both\",\n \"resize-x\": \"x\",\n \"resize-y\": \"y\",\n \"resize-none\": \"none\",\n};\n\nfunction resolveResizeAxis(className: string | undefined, resizable: boolean): ResizeAxis {\n if (!resizable) return \"none\";\n\n let axis: ResizeAxis | undefined;\n for (const token of className?.split(/\\s+/) ?? []) {\n const nextAxis = resizeClassMap[token];\n if (nextAxis) axis = nextAxis;\n }\n\n return axis ?? \"both\";\n}\n\nfunction stripResizeClasses(className: string | undefined) {\n if (!className) return className;\n\n const filtered = className\n .split(/\\s+/)\n .filter((token) => token && !resizeClassMap[token])\n .join(\" \");\n\n return filtered || undefined;\n}\n\nexport const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(\n (\n {\n className,\n size = \"md\",\n error = false,\n disabled,\n resizable = true,\n showCharacterCount = false,\n maxCharacters,\n maxHeight,\n value: controlledValue,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n style: propStyle,\n styles,\n ...props\n },\n ref\n ) => {\n const [internalValue, setInternalValue] = useState(controlledValue ?? defaultValue ?? \"\");\n const [isFocused, setIsFocused] = React.useState(false);\n const [internalHeight, setInternalHeight] = React.useState<number | null>(null);\n const [internalWidth, setInternalWidth] = React.useState<number | null>(null);\n\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const surfaceRef = React.useRef<HTMLDivElement>(null);\n const scrollWrapperRef = React.useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, textareaRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n const charCount = typeof currentValue === \"string\" ? currentValue.length : 0;\n const isOverLimit = maxCharacters ? charCount > maxCharacters : false;\n\n const handleFocus = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n onFocus?.(e);\n };\n\n const handleBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n onBlur?.(e);\n };\n\n const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {\n const newValue = e.target.value;\n\n if (maxCharacters && newValue.length > maxCharacters) {\n const truncated = newValue.slice(0, maxCharacters);\n if (controlledValue === undefined) {\n setInternalValue(truncated);\n }\n onChange?.({\n ...e,\n target: { ...e.target, value: truncated },\n } as React.ChangeEvent<HTMLTextAreaElement>);\n } else {\n if (controlledValue === undefined) {\n setInternalValue(newValue);\n }\n onChange?.(e);\n }\n };\n\n const resolved = resolveTextAreaStyles(styles);\n const resizeAxis = resolveResizeAxis(className, resizable);\n const canResize = resizeAxis !== \"none\" && !disabled;\n const textareaClassName = stripResizeClasses(className);\n\n const handleResizeMouseDown = React.useCallback((e: React.MouseEvent) => {\n if (!canResize) return;\n\n e.preventDefault();\n const textarea = textareaRef.current;\n const surface = surfaceRef.current;\n const scrollWrapper = scrollWrapperRef.current;\n if (!textarea || !surface) return;\n\n const computed = window.getComputedStyle(textarea);\n const minHeight = Number.parseFloat(computed.minHeight) || 60;\n const minWidth = Number.parseFloat(computed.minWidth) || 160;\n const startX = e.clientX;\n const startY = e.clientY;\n const startWidth = surface.getBoundingClientRect().width;\n const startHeight = maxHeight\n ? (scrollWrapper?.getBoundingClientRect().height ?? surface.getBoundingClientRect().height)\n : surface.getBoundingClientRect().height;\n\n const onMouseMove = (ev: MouseEvent) => {\n if (resizeAxis === \"x\" || resizeAxis === \"both\") {\n const deltaX = ev.clientX - startX;\n setInternalWidth(Math.max(minWidth, startWidth + deltaX));\n }\n\n if (resizeAxis === \"y\" || resizeAxis === \"both\") {\n const deltaY = ev.clientY - startY;\n setInternalHeight(Math.max(minHeight, startHeight + deltaY));\n }\n };\n\n const onMouseUp = () => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"\";\n };\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"none\";\n }, [canResize, maxHeight, resizeAxis]);\n\n const effectiveMaxHeight = internalHeight !== null ? `${internalHeight}px` : maxHeight;\n\n const autoResize = React.useCallback(() => {\n const el = textareaRef.current;\n if (!el || !maxHeight) return;\n el.style.height = \"auto\";\n el.style.height = `${el.scrollHeight}px`;\n }, [maxHeight]);\n\n React.useLayoutEffect(() => {\n autoResize();\n }, [autoResize, currentValue]);\n\n const surfaceStyle: React.CSSProperties = {\n ...(internalWidth !== null ? { width: `${internalWidth}px`, maxWidth: \"100%\" } : {}),\n ...(maxHeight === undefined && internalHeight !== null ? { height: `${internalHeight}px` } : {}),\n };\n\n const textareaStyle: React.CSSProperties = {\n ...propStyle,\n resize: \"none\",\n ...(maxHeight === undefined && internalHeight !== null ? { height: \"100%\" } : {}),\n };\n\n const resizeHandleStyle =\n resizeAxis === \"x\" ? resolved.resizeHandleX : resizeAxis === \"y\" ? resolved.resizeHandleY : resolved.resizeHandleBoth;\n\n const textareaEl = (\n <textarea\n ref={mergedRef}\n disabled={disabled}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n data-resize-axis={resizeAxis}\n data-scroll={maxHeight ? \"true\" : undefined}\n className={cn('textarea', css.textarea, textareaClassName, resolved.root)}\n style={textareaStyle}\n value={currentValue}\n {...mergeProps(focusProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n onChange: handleChange,\n ...props,\n })}\n />\n );\n\n return (\n <div className={cn(\"textarea\", \"container\", css.container, resolved.container)}>\n <div\n ref={surfaceRef}\n className={cn(\"textarea\", \"surface\", css.surface, resolved.surface)}\n data-resize-axis={resizeAxis}\n style={surfaceStyle}\n >\n {maxHeight ? (\n <div\n ref={scrollWrapperRef}\n className={cn(\"textarea\", \"scroll-wrapper\", css[\"scroll-wrapper\"], resolved.scrollWrapper)}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-disabled={disabled || undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-size={size}\n >\n <Scroll maxHeight={effectiveMaxHeight} style={{ height: \"auto\" }}>\n {textareaEl}\n </Scroll>\n </div>\n ) : (\n textareaEl\n )}\n {canResize && (\n <div\n aria-hidden=\"true\"\n data-axis={resizeAxis}\n data-slot=\"resize-handle\"\n className={cn(\"textarea\", \"resize-handle\", css[\"resize-handle\"], resizeHandleStyle)}\n onMouseDown={handleResizeMouseDown}\n />\n )}\n </div>\n {showCharacterCount && (\n <div\n className={cn('textarea', 'character-count', css[\"character-count\"], resolved.characterCount)}\n data-over-limit={isOverLimit || undefined}\n >\n {charCount}{maxCharacters ? ` / ${maxCharacters}` : \"\"} characters\n </div>\n )}\n </div>\n );\n }\n);\n\nTextArea.displayName = \"TextArea\";\n",
|
|
4462
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n
|
|
4463
|
-
"cssTypes": "declare const styles: {\n textarea: string;\n container: string;\n surface: string;\n \"character-count\": string;\n \"scroll-wrapper\": string;\n \"resize-handle\": string;\n};\n\nexport default styles;\n"
|
|
4586
|
+
"tsx": "\"use client\";\n\nimport React, { forwardRef, useState, type ComponentPropsWithoutRef } from \"react\";\n\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { mergeProps } from \"@react-aria/utils\";\n\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { useFocusIndicator } from \"@/hooks/useFocusIndicator\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Scroll } from \"@/components/Scroll\";\nimport css from \"./Textarea.module.css\";\n\ntype TextareaSize = \"sm\" | \"md\" | \"lg\";\ntype ResizeAxis = \"both\" | \"x\" | \"y\" | \"none\";\n\nexport interface TextareaResizeHandleStyles {\n both?: StyleValue;\n x?: StyleValue;\n y?: StyleValue;\n}\n\nexport interface TextareaStyleSlots {\n root?: StyleValue;\n container?: StyleValue;\n surface?: StyleValue;\n scrollWrapper?: StyleValue;\n resizeHandle?: StyleValue | TextareaResizeHandleStyles;\n characterCount?: StyleValue;\n}\n\nexport type TextareaStylesProp = StylesProp<TextareaStyleSlots>;\nexport type TextAreaStyleSlots = TextareaStyleSlots;\nexport type TextAreaStylesProp = TextareaStylesProp;\n\nconst resolveTextareaBaseStyles = createStylesResolver([\n \"root\",\n \"container\",\n \"surface\",\n \"scrollWrapper\",\n \"resizeHandleBoth\",\n \"resizeHandleX\",\n \"resizeHandleY\",\n \"characterCount\",\n] as const);\n\nfunction resolveTextareaStyles(styles: TextareaStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) {\n return resolveTextareaBaseStyles(styles);\n }\n\n const { root, container, surface, scrollWrapper, resizeHandle, characterCount } = styles;\n\n let resizeHandleBoth: StyleValue | undefined;\n let resizeHandleX: StyleValue | undefined;\n let resizeHandleY: StyleValue | undefined;\n\n if (resizeHandle) {\n if (typeof resizeHandle === \"string\" || Array.isArray(resizeHandle)) {\n resizeHandleBoth = resizeHandle;\n resizeHandleX = resizeHandle;\n resizeHandleY = resizeHandle;\n } else {\n resizeHandleBoth = resizeHandle.both;\n resizeHandleX = resizeHandle.x;\n resizeHandleY = resizeHandle.y;\n }\n }\n\n return resolveTextareaBaseStyles({\n root,\n container,\n surface,\n scrollWrapper,\n resizeHandleBoth,\n resizeHandleX,\n resizeHandleY,\n characterCount,\n });\n}\n\nexport interface TextareaProps extends Omit<ComponentPropsWithoutRef<\"textarea\">, \"size\"> {\n /** Size of the textarea. */\n size?: TextareaSize;\n /** Whether to apply error styling. */\n error?: boolean;\n /** Whether the textarea can be manually resized by the user. When enabled, `className` may include Tailwind `resize`, `resize-x`, `resize-y`, or `resize-none` to select the resize axis. */\n resizable?: boolean;\n /** Whether to display a character count below the textarea. */\n showCharacterCount?: boolean;\n /** Maximum number of characters allowed. */\n maxCharacters?: number;\n /** Maximum height before the custom scrollbar activates. */\n maxHeight?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TextareaStylesProp;\n}\n\nexport type TextAreaProps = TextareaProps;\n\nconst resizeClassMap: Record<string, ResizeAxis> = {\n resize: \"both\",\n \"resize-x\": \"x\",\n \"resize-y\": \"y\",\n \"resize-none\": \"none\",\n};\n\nconst sizeMap: Record<TextareaSize, string> = {\n sm: css[\"size-sm\"],\n md: css[\"size-md\"],\n lg: css[\"size-lg\"],\n};\n\nconst resizeHandleClassMap: Record<Exclude<ResizeAxis, \"none\">, string> = {\n both: css[\"resize-handle-both\"],\n x: css[\"resize-handle-x\"],\n y: css[\"resize-handle-y\"],\n};\n\nfunction resolveResizeAxis(className: string | undefined, resizable: boolean): ResizeAxis {\n if (!resizable) return \"none\";\n\n let axis: ResizeAxis | undefined;\n for (const token of className?.split(/\\s+/) ?? []) {\n const nextAxis = resizeClassMap[token];\n if (nextAxis) axis = nextAxis;\n }\n\n return axis ?? \"both\";\n}\n\nfunction stripResizeClasses(className: string | undefined) {\n if (!className) return className;\n\n const filtered = className\n .split(/\\s+/)\n .filter((token) => token && !resizeClassMap[token])\n .join(\" \");\n\n return filtered || undefined;\n}\n\nconst Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n (\n {\n className,\n size = \"md\",\n error = false,\n disabled,\n resizable = true,\n showCharacterCount = false,\n maxCharacters,\n maxHeight,\n value: controlledValue,\n defaultValue,\n onChange,\n onFocus,\n onBlur,\n style: propStyle,\n styles,\n ...props\n },\n ref\n ) => {\n const [internalValue, setInternalValue] = useState(controlledValue ?? defaultValue ?? \"\");\n const [isFocused, setIsFocused] = React.useState(false);\n const [internalHeight, setInternalHeight] = React.useState<number | null>(null);\n const [internalWidth, setInternalWidth] = React.useState<number | null>(null);\n\n const scopeRef = React.useRef<HTMLDivElement>(null);\n const containerRef = React.useRef<HTMLDivElement>(null);\n const surfaceRef = React.useRef<HTMLDivElement>(null);\n const scrollWrapperRef = React.useRef<HTMLDivElement>(null);\n const textareaRef = React.useRef<HTMLTextAreaElement>(null);\n const mergedRef = useMergeRefs(ref, textareaRef);\n\n const { focusProps, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({ isDisabled: disabled });\n const { scopeProps, indicatorProps } = useFocusIndicator({\n scopeRef,\n containerRef,\n surfaceSelector: '[data-textarea-focus-surface=\"true\"]',\n radiusSource: \"surface\",\n dependencies: [maxHeight, size],\n });\n\n const currentValue = controlledValue !== undefined ? controlledValue : internalValue;\n const charCount = typeof currentValue === \"string\" ? currentValue.length : 0;\n const isOverLimit = maxCharacters ? charCount > maxCharacters : false;\n const hasScrollSurface = maxHeight !== undefined;\n const resizeAxis = resolveResizeAxis(className, resizable);\n const canResize = resizeAxis !== \"none\" && !disabled;\n const textareaClassName = stripResizeClasses(className);\n const resolved = resolveTextareaStyles(styles);\n\n const handleFocus = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(true);\n onFocus?.(event);\n };\n\n const handleBlur = (event: React.FocusEvent<HTMLTextAreaElement>) => {\n setIsFocused(false);\n onBlur?.(event);\n };\n\n const handleChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n const nextValue = event.target.value;\n\n if (maxCharacters && nextValue.length > maxCharacters) {\n const truncatedValue = nextValue.slice(0, maxCharacters);\n\n if (controlledValue === undefined) {\n setInternalValue(truncatedValue);\n }\n\n onChange?.({\n ...event,\n target: { ...event.target, value: truncatedValue },\n } as React.ChangeEvent<HTMLTextAreaElement>);\n return;\n }\n\n if (controlledValue === undefined) {\n setInternalValue(nextValue);\n }\n\n onChange?.(event);\n };\n\n const handleResizeMouseDown = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n if (!canResize) return;\n\n event.preventDefault();\n\n const textarea = textareaRef.current;\n const surface = surfaceRef.current;\n const scrollWrapper = scrollWrapperRef.current;\n if (!textarea || !surface) return;\n\n const computed = window.getComputedStyle(textarea);\n const minHeight = Number.parseFloat(computed.minHeight) || 60;\n const minWidth = Number.parseFloat(computed.minWidth) || 160;\n const startX = event.clientX;\n const startY = event.clientY;\n const startWidth = surface.getBoundingClientRect().width;\n const startHeight = hasScrollSurface\n ? (scrollWrapper?.getBoundingClientRect().height ?? surface.getBoundingClientRect().height)\n : surface.getBoundingClientRect().height;\n\n const onMouseMove = (nextEvent: MouseEvent) => {\n if (resizeAxis === \"x\" || resizeAxis === \"both\") {\n const deltaX = nextEvent.clientX - startX;\n setInternalWidth(Math.max(minWidth, startWidth + deltaX));\n }\n\n if (resizeAxis === \"y\" || resizeAxis === \"both\") {\n const deltaY = nextEvent.clientY - startY;\n setInternalHeight(Math.max(minHeight, startHeight + deltaY));\n }\n };\n\n const onMouseUp = () => {\n document.removeEventListener(\"mousemove\", onMouseMove);\n document.removeEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"\";\n };\n\n document.addEventListener(\"mousemove\", onMouseMove);\n document.addEventListener(\"mouseup\", onMouseUp);\n document.body.style.userSelect = \"none\";\n },\n [canResize, hasScrollSurface, resizeAxis]\n );\n\n const effectiveMaxHeight = internalHeight !== null ? `${internalHeight}px` : maxHeight;\n\n const autoResize = React.useCallback(() => {\n const element = textareaRef.current;\n if (!element || !maxHeight) return;\n\n element.style.height = \"auto\";\n element.style.height = `${element.scrollHeight}px`;\n }, [maxHeight]);\n\n React.useLayoutEffect(() => {\n autoResize();\n }, [autoResize, currentValue]);\n\n const surfaceStyle: React.CSSProperties = {\n ...(internalWidth !== null ? { width: `${internalWidth}px`, maxWidth: \"100%\" } : {}),\n ...(maxHeight === undefined && internalHeight !== null ? { height: `${internalHeight}px` } : {}),\n };\n\n const textareaStyle: React.CSSProperties = {\n ...propStyle,\n resize: \"none\",\n ...(maxHeight === undefined && internalHeight !== null ? { height: \"100%\" } : {}),\n };\n\n const resolvedResizeHandle =\n resizeAxis === \"none\"\n ? undefined\n : resizeAxis === \"x\"\n ? resolved.resizeHandleX\n : resizeAxis === \"y\"\n ? resolved.resizeHandleY\n : resolved.resizeHandleBoth;\n\n const textareaElement = (\n <textarea\n ref={mergedRef}\n disabled={disabled}\n data-size={size}\n data-scroll={hasScrollSurface ? \"true\" : undefined}\n data-resize-axis={resizeAxis}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n data-textarea-focus-surface={hasScrollSurface ? undefined : \"true\"}\n className={cn(\n \"textarea\",\n css.textarea,\n sizeMap[size],\n textareaClassName,\n resolved.root\n )}\n style={textareaStyle}\n value={currentValue}\n {...mergeProps(focusProps, hoverProps, {\n onFocus: handleFocus,\n onBlur: handleBlur,\n onChange: handleChange,\n ...props,\n })}\n />\n );\n\n return (\n <div ref={scopeRef} className={cn(\"textarea-scope\", scopeProps.className)}>\n <div {...indicatorProps} data-focus-indicator=\"local\" />\n <div\n ref={containerRef}\n className={cn(\"textarea\", \"container\", css.container, resolved.container)}\n >\n <div\n ref={surfaceRef}\n className={cn(\"textarea\", \"surface\", css.surface, resolved.surface)}\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n data-resize-axis={resizeAxis}\n style={surfaceStyle}\n >\n {hasScrollSurface ? (\n <div\n ref={scrollWrapperRef}\n data-textarea-focus-surface=\"true\"\n data-disabled={disabled ? \"true\" : undefined}\n data-error={error || isOverLimit ? \"true\" : undefined}\n data-focused={isFocused ? \"true\" : undefined}\n data-focus-visible={isFocusVisible ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : undefined}\n className={cn(\n \"textarea\",\n \"scroll-wrapper\",\n css[\"scroll-wrapper\"],\n resolved.scrollWrapper\n )}\n >\n <Scroll maxHeight={effectiveMaxHeight} style={{ height: \"auto\" }}>\n {textareaElement}\n </Scroll>\n </div>\n ) : (\n textareaElement\n )}\n {canResize && (\n <div\n aria-hidden=\"true\"\n data-axis={resizeAxis}\n data-slot=\"resize-handle\"\n className={cn(\n \"textarea\",\n \"resize-handle\",\n css[\"resize-handle\"],\n resizeHandleClassMap[resizeAxis],\n resolvedResizeHandle\n )}\n onMouseDown={handleResizeMouseDown}\n />\n )}\n </div>\n {showCharacterCount && (\n <div\n className={cn(\n \"textarea\",\n \"character-count\",\n css[\"character-count\"],\n resolved.characterCount\n )}\n data-over-limit={isOverLimit ? \"true\" : undefined}\n >\n {charCount}\n {maxCharacters ? ` / ${maxCharacters}` : \"\"} characters\n </div>\n )}\n </div>\n </div>\n );\n }\n);\n\nTextarea.displayName = \"Textarea\";\n\nconst TextArea = Textarea;\n\nexport { Textarea, TextArea };\n",
|
|
4587
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n\n @apply block w-full px-3 py-2;\n box-sizing: border-box;\n resize: none;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n background-color: var(--background);\n color: var(--foreground);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n outline: none;\n transition:\n background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n border-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),\n color 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n\n &::placeholder {\n color: var(--placeholder);\n }\n\n &:hover:not([data-disabled]),\n &[data-hovered=\"true\"]:not([data-disabled]) {\n background-color: var(--background-hover);\n }\n\n &[data-disabled=\"true\"] {\n background-color: var(--background-disabled);\n color: var(--foreground-disabled);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n\n &[data-resize-axis=\"x\"],\n &[data-resize-axis=\"both\"] {\n padding-inline-end: calc(var(--padding-inline) + 1rem);\n }\n\n &[data-resize-axis=\"y\"],\n &[data-resize-axis=\"both\"] {\n padding-block-end: calc(var(--padding-block) + 1rem);\n }\n\n &[data-scroll=\"true\"] {\n border: none;\n border-radius: 0;\n background: transparent;\n box-shadow: none;\n overflow: hidden;\n\n &[data-disabled=\"true\"] {\n background-color: transparent;\n opacity: 1;\n }\n\n &[data-error=\"true\"] {\n border-color: transparent;\n }\n }\n }\n\n .size-sm {\n min-height: 5rem;\n --padding-inline: 0.5rem;\n --padding-block: 0.25rem;\n font-size: var(--text-sm);\n @apply px-2 py-1;\n }\n\n .size-md {\n min-height: 6rem;\n --padding-inline: 0.75rem;\n --padding-block: 0.5rem;\n font-size: var(--text-sm);\n @apply px-3 py-2;\n }\n\n .size-lg {\n min-height: 8rem;\n --padding-inline: 1rem;\n --padding-block: 0.75rem;\n font-size: var(--text-md);\n @apply px-4 py-3;\n }\n\n .container {\n @apply w-full;\n }\n\n .surface {\n @apply relative w-full;\n }\n\n .scroll-wrapper {\n @apply w-full overflow-hidden;\n border: var(--border-width-base, 1px) solid var(--background-border);\n border-radius: var(--radius-sm, 0.375rem);\n background-color: var(--background);\n\n &:hover:not([data-disabled=\"true\"]),\n &[data-hovered=\"true\"]:not([data-disabled=\"true\"]) {\n background-color: var(--background-hover);\n }\n\n &[data-disabled=\"true\"] {\n background-color: var(--background-disabled);\n opacity: var(--disabled-opacity);\n }\n\n &[data-error=\"true\"] {\n border-color: var(--background-error-border);\n }\n }\n\n .resize-handle {\n position: absolute;\n z-index: 1;\n touch-action: none;\n user-select: none;\n\n &::before,\n &::after {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--handle-background);\n transition: background-color 150ms;\n }\n\n &:hover::before,\n &:hover::after {\n background-color: var(--handle-hover-background);\n }\n }\n\n .resize-handle-both {\n right: 3px;\n bottom: 3px;\n width: 1.5rem;\n height: 1.5rem;\n cursor: nwse-resize;\n\n &::before {\n right: 0.15rem;\n bottom: 0.35rem;\n width: 0.5rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n\n &::after {\n right: 0.2rem;\n bottom: 0.6rem;\n width: 1rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n }\n\n .resize-handle-x {\n top: 50%;\n right: 0;\n width: 0.75rem;\n height: 2rem;\n cursor: ew-resize;\n transform: translateY(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 0.125rem;\n height: 1.5rem;\n transform: translate(-50%, -50%);\n }\n\n &::after {\n display: none;\n }\n }\n\n .resize-handle-y {\n left: 50%;\n bottom: 0;\n width: 2rem;\n height: 0.75rem;\n cursor: ns-resize;\n transform: translateX(-50%);\n\n &::before {\n top: 50%;\n left: 50%;\n width: 1.5rem;\n height: 0.125rem;\n transform: translate(-50%, -50%);\n }\n\n &::after {\n display: none;\n }\n }\n\n .character-count {\n @apply mt-1;\n color: var(--count-foreground);\n font-size: var(--text-sm);\n transition: color 0.15s var(--ease-snappy-pop);\n }\n\n .character-count[data-over-limit=\"true\"] {\n color: var(--count-over-limit-foreground);\n }\n}\n",
|
|
4588
|
+
"cssTypes": "declare const styles: {\n textarea: string;\n \"size-sm\": string;\n \"size-md\": string;\n \"size-lg\": string;\n container: string;\n surface: string;\n \"character-count\": string;\n \"scroll-wrapper\": string;\n \"resize-handle\": string;\n \"resize-handle-both\": string;\n \"resize-handle-x\": string;\n \"resize-handle-y\": string;\n};\n\nexport default styles;\n"
|
|
4464
4589
|
},
|
|
4465
4590
|
"toast": {
|
|
4466
4591
|
"tsx": "\"use client\";\n\nimport React, { forwardRef, useImperativeHandle, useRef, useEffect, useCallback } from 'react';\nimport gsap from 'gsap';\nimport { useGSAP } from \"@gsap/react\";\nimport { mergeProps } from \"@react-aria/utils\";\nimport { useHover } from \"@react-aria/interactions\";\nimport { useFocusRing } from \"@react-aria/focus\";\nimport { cn, type StyleValue } from \"./utils\";\nimport { type StylesProp, createStylesResolver } from '@/lib/styles';\nimport css from './Toast.module.css';\nimport { ToastProps as ToastData, dispatch } from \"./Toast.Store\";\n\nimport { Info, CircleCheck, TriangleAlert, CircleAlert, type LucideIcon } from \"lucide-react\";\n\nimport { X } from 'lucide-react';\n\nconst DRAG_DISMISS_THRESHOLD = 100;\nconst DRAG_LEFT_RESISTANCE = 20;\n\nexport interface ToastStyleSlots {\n root?: StyleValue;\n iconWrap?: StyleValue;\n icon?: StyleValue;\n content?: StyleValue;\n title?: StyleValue;\n description?: StyleValue;\n close?: StyleValue;\n closeIcon?: StyleValue;\n}\n\nexport type ToastStylesProp = StylesProp<ToastStyleSlots>;\n\nconst resolveToastBaseStyles = createStylesResolver([\n 'root',\n 'iconWrap',\n 'icon',\n 'content',\n 'title',\n 'description',\n 'close',\n 'closeIcon'\n] as const);\n\nfunction resolveToastStyles(styles: ToastStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveToastBaseStyles(styles);\n\n const { root, iconWrap, icon, content, title, description, close, closeIcon } = styles;\n\n return resolveToastBaseStyles({ root, iconWrap, icon, content, title, description, close, closeIcon });\n}\n\nconst toastIcons: Record<string, LucideIcon | null> = {\n danger: TriangleAlert,\n success: CircleCheck,\n info: Info,\n warning: CircleAlert,\n default: null,\n};\n\ninterface ToastComponentProps {\n /** Toast data object containing content and display options */\n toast: ToastData;\n /** Whether the auto-dismiss timer pauses on mouse hover */\n pauseOnHover?: boolean;\n onDragStart?: () => void;\n onDragEnd?: () => void;\n onDismissStart?: () => void;\n onDismissEnd?: () => void;\n}\n\nexport const Toast = forwardRef<HTMLDivElement, ToastComponentProps>(function Toast(\n { toast, pauseOnHover = false, onDragStart, onDragEnd, onDismissStart, onDismissEnd },\n ref\n) {\n const innerRef = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => innerRef.current!);\n\n const {\n id,\n title,\n description,\n jsx,\n variant = 'default',\n duration = 5000,\n onDismiss,\n position = 'bottom-right',\n styles,\n } = toast;\n\n const resolved = resolveToastStyles(styles);\n const isTop = position.startsWith('top');\n const { focusProps, isFocused, isFocusVisible } = useFocusRing();\n const { hoverProps, isHovered } = useHover({});\n const closeInteractionProps = mergeProps(focusProps, hoverProps) as React.ButtonHTMLAttributes<HTMLButtonElement>;\n\n // Time tracking refs\n const elapsedRef = useRef(0);\n const lastTimeRef = useRef<number>(Date.now());\n const animationFrameRef = useRef<number | null>(null);\n\n // Use a ref for paused state to avoid restarting the effect on every hover change\n const isPausedRef = useRef(pauseOnHover);\n useEffect(() => {\n isPausedRef.current = pauseOnHover;\n }, [pauseOnHover]);\n\n // Drag state refs\n const dragStartXRef = useRef(0);\n const currentDeltaRef = useRef(0);\n const dragPausedRef = useRef(false);\n\n const handleDismiss = useCallback(() => {\n // Change absolute numbers to relative strings\n const yOffset = isTop ? \"-=20\" : \"+=20\";\n\n if (innerRef.current) {\n innerRef.current.dataset.dismissing = \"true\";\n onDismissStart?.();\n dispatch({ type: 'CLOSE_TOAST', toastId: id });\n gsap.killTweensOf(innerRef.current);\n gsap.to(innerRef.current, {\n opacity: 0,\n y: yOffset, // Animates relative to its current layout position\n scale: 0.9,\n duration: 0.35,\n ease: \"expo.out\",\n onComplete: () => {\n onDismissEnd?.();\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n },\n });\n } else {\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n }\n }, [id, isTop, onDismiss]);\n\n useGSAP(() => {\n if (!innerRef.current) return;\n\n const spawnDir = toast.spawnDirection || 'bottom';\n const fromY = spawnDir === 'top' ? (isTop ? 25 : -25) : (isTop ? -25 : 25);\n\n gsap.from(innerRef.current, {\n opacity: 1,\n y: fromY,\n duration: 0.45,\n ease: \"expo.out\",\n });\n }, { scope: innerRef });\n\n const handlePointerDown = useCallback((e: React.PointerEvent) => {\n if (innerRef.current?.dataset.dismissing) return;\n dragStartXRef.current = e.clientX;\n currentDeltaRef.current = 0;\n dragPausedRef.current = true;\n onDragStart?.();\n gsap.killTweensOf(innerRef.current);\n\n const onMove = (ev: PointerEvent) => {\n if (!innerRef.current) return;\n const delta = ev.clientX - dragStartXRef.current;\n currentDeltaRef.current = delta;\n\n if (delta >= 0) {\n const progress = Math.min(delta / DRAG_DISMISS_THRESHOLD, 1);\n gsap.set(innerRef.current, { x: delta, opacity: 1 - progress * 0.5 });\n } else {\n const x = -DRAG_LEFT_RESISTANCE * (1 - Math.exp(delta / DRAG_LEFT_RESISTANCE));\n gsap.set(innerRef.current, { x, opacity: 1 });\n }\n };\n\n const onUp = () => {\n dragPausedRef.current = false;\n onDragEnd?.();\n document.removeEventListener('pointermove', onMove);\n document.removeEventListener('pointerup', onUp);\n document.removeEventListener('pointercancel', onUp);\n\n const delta = currentDeltaRef.current;\n currentDeltaRef.current = 0;\n\n if (delta >= DRAG_DISMISS_THRESHOLD) {\n if (innerRef.current) {\n innerRef.current.dataset.dismissing = \"true\";\n onDismissStart?.();\n // Dispatch CLOSE_TOAST immediately to signal stack adjustment\n dispatch({ type: 'CLOSE_TOAST', toastId: id });\n gsap.killTweensOf(innerRef.current);\n gsap.to(innerRef.current, {\n x: \"+=200\",\n opacity: 0,\n duration: 0.25,\n ease: \"power2.in\",\n onComplete: () => {\n onDismissEnd?.();\n onDismiss?.();\n // Dispatch DISMISS_TOAST after animation completes\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n },\n });\n } else {\n // If innerRef.current is null, just dismiss immediately\n onDismiss?.();\n dispatch({ type: 'DISMISS_TOAST', toastId: id });\n }\n } else if (innerRef.current) {\n gsap.to(innerRef.current, {\n x: 0,\n opacity: 1,\n duration: 0.55,\n ease: \"elastic.out(1, 0.55)\",\n });\n }\n };\n\n document.addEventListener('pointermove', onMove);\n document.addEventListener('pointerup', onUp);\n document.addEventListener('pointercancel', onUp);\n }, [id, isTop, onDismiss, onDragStart, onDragEnd, onDismissStart, onDismissEnd]);\n\n // Animation Frame Timer Logic\n useEffect(() => {\n if (duration === Infinity || duration <= 0) return;\n lastTimeRef.current = Date.now();\n\n const loop = () => {\n const now = Date.now();\n const delta = now - lastTimeRef.current;\n lastTimeRef.current = now;\n\n if (!isPausedRef.current && !dragPausedRef.current) {\n elapsedRef.current += delta;\n\n if (elapsedRef.current >= duration) {\n handleDismiss();\n return;\n }\n }\n\n animationFrameRef.current = requestAnimationFrame(loop);\n };\n\n animationFrameRef.current = requestAnimationFrame(loop);\n\n return () => {\n if (animationFrameRef.current) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n };\n }, [duration, handleDismiss]);\n\n const Icon = toastIcons[variant as keyof typeof toastIcons];\n\n return (\n <div\n ref={innerRef}\n className={cn('toast', css.root, variant, resolved.root)}\n role=\"alert\"\n onPointerDown={handlePointerDown}\n >\n {Icon && (\n <div className={cn(\"toast icon-wrap\", css[\"icon-wrap\"], resolved.iconWrap)}>\n <Icon className={cn(\"toast icon\", css.icon, resolved.icon)} />\n </div>\n )}\n <div className={cn('toast content', css.content, resolved.content)}>\n {jsx || (\n <>\n {title && <h4 className={cn('toast title', css.title, resolved.title)}>{title}</h4>}\n {description && <p className={cn('toast description', css.description, resolved.description)}>{description}</p>}\n </>\n )}\n {toast.action}\n </div>\n <button\n {...closeInteractionProps}\n onClick={handleDismiss}\n className={cn('toast close', css.close, resolved.close)}\n aria-label=\"Close\"\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n <X className={cn(\"toast close-icon\", css[\"close-icon\"], resolved.closeIcon)} />\n </button>\n </div>\n );\n});\n",
|
|
@@ -4468,7 +4593,7 @@ export const generatedSourceCode = {
|
|
|
4468
4593
|
"cssTypes": "declare const styles: {\n root: string;\n \"icon-wrap\": string;\n icon: string;\n content: string;\n title: string;\n description: string;\n close: string;\n \"close-icon\": string;\n};\n\nexport default styles;\n"
|
|
4469
4594
|
},
|
|
4470
4595
|
"tooltip": {
|
|
4471
|
-
"tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback, useLayoutEffect } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltip, mergeProps } from \"react-aria\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { useTooltipTriggerState } from \"react-stately\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { Frame } from \"../Frame\";\nimport { Badge } from \"../Badge\";\nimport css from \"./Tooltip.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { type StyleValue } from \"./utils\";\n\nconst ARROW_PATH = \"M 0 0 C 3 0 4 -9 6 -9 C 8 -9 9 0 12 0\";\nconst ARROW_WIDTH = 12;\nconst TOOLTIP_GAP = 4;\nconst ARROW_POSITIONING_SIZE = 6;\nconst DEFAULT_SHOW_DELAY_MS = 600;\nconst SWAP_WINDOW_MS = 150;\nconst EXIT_ANIMATION_MS = 160;\n\nlet lastCloseTime = 0;\nlet lastOpenTime = 0;\nlet pendingExit: (() => void) | null = null;\n\nfunction useTimeout() {\n const idRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const start = useCallback((fn: () => void, ms: number) => {\n clearTimeout(idRef.current);\n idRef.current = setTimeout(fn, ms);\n }, []);\n\n const clear = useCallback(() => {\n clearTimeout(idRef.current);\n }, []);\n\n useEffect(() => () => clearTimeout(idRef.current), []);\n\n return [start, clear] as const;\n}\n\ntype TooltipPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: TooltipPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\nexport interface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hint?: StyleValue;\n}\n\nexport type TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipBaseStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hint',\n] as const);\n\nfunction resolveTooltipStyles(styles: TooltipStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveTooltipBaseStyles(styles);\n const { root, trigger, content, frame, hint } = styles;\n return resolveTooltipBaseStyles({ root, trigger, content, frame, hint });\n}\n\nexport interface TooltipProps {\n children: React.ReactNode;\n /** Content to display inside the tooltip */\n content: React.ReactNode;\n /** Preferred side of the trigger where the tooltip appears */\n position?: TooltipPosition;\n /** Additional CSS class for the trigger wrapper */\n className?: string;\n /** Milliseconds before the tooltip appears after hover */\n delay?: number;\n /** Whether the tooltip is disabled */\n isDisabled?: boolean;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the tooltip opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the tooltip */\n hint?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TooltipStylesProp;\n}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n children,\n content,\n position = \"top\",\n className,\n delay = DEFAULT_SHOW_DELAY_MS,\n isDisabled = false,\n isOpen: controlledIsOpen,\n onOpenChange,\n showArrow = false,\n hint,\n styles,\n },\n _ref\n ) => {\n const triggerWrapperRef = useRef<HTMLSpanElement>(null);\n const triggerRef = useRef<HTMLElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [shouldRender, setShouldRender] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [isInstant, setIsInstant] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isFocusVisible, setIsFocusVisible] = useState(false);\n const wasOpenRef = useRef(false);\n const [startSwapTimer, clearSwapTimer] = useTimeout();\n const [startUnmountTimer, clearUnmountTimer] = useTimeout();\n\n const resolved = resolveTooltipStyles(styles);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n\n const state = useTooltipTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: useCallback((open: boolean) => {\n if (open) lastOpenTime = Date.now();\n else lastCloseTime = Date.now();\n onOpenChangeRef.current?.(open);\n }, []),\n delay,\n isDisabled,\n });\n\n const { tooltipProps: ariaTooltipProps } = useTooltip({}, state);\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: position,\n // Tooltips are commonly used in fixed headers; fixed positioning avoids scroll drift.\n strategy: \"fixed\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(TOOLTIP_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n useEffect(() => {\n if (state.isOpen) {\n wasOpenRef.current = true;\n const elapsed = Date.now() - lastCloseTime;\n const isSwap = lastCloseTime > 0 && elapsed < SWAP_WINDOW_MS;\n\n if (pendingExit) {\n pendingExit();\n pendingExit = null;\n }\n\n setIsInstant(isSwap);\n setShouldRender(true);\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n\n if (lastOpenTime > 0 && lastOpenTime >= lastCloseTime) {\n setIsVisible(false);\n setShouldRender(false);\n return;\n }\n\n // Non-batched: delay exit to allow cross-frame swap detection.\n // If another tooltip opens within the window, pendingExit cancels.\n startSwapTimer(() => {\n setIsVisible(false);\n startUnmountTimer(() => {\n setShouldRender(false);\n pendingExit = null;\n }, EXIT_ANIMATION_MS);\n }, SWAP_WINDOW_MS);\n\n pendingExit = () => {\n clearSwapTimer();\n clearUnmountTimer();\n setIsVisible(false);\n setShouldRender(false);\n };\n }\n }, [state.isOpen]);\n\n useEffect(() => {\n if (shouldRender && state.isOpen && isPositioned) {\n if (isInstant) {\n setIsVisible(true);\n const frame = requestAnimationFrame(() => setIsInstant(false));\n return () => cancelAnimationFrame(frame);\n } else {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n }\n }\n }, [shouldRender, state.isOpen, isPositioned, isInstant]);\n\n const mergedTriggerRef = useCallback((el: HTMLElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLElement | null>).current = el;\n refs.setReference(el);\n }, [refs]);\n\n const mergedFloatingRef = useCallback((el: HTMLDivElement | null) => {\n (tooltipRef as React.MutableRefObject<HTMLDivElement | null>).current = el;\n refs.setFloating(el);\n if (typeof _ref === \"function\") _ref(el);\n else if (_ref && typeof _ref === \"object\") _ref.current = el;\n }, [_ref, refs]);\n\n useLayoutEffect(() => {\n const wrapper = triggerWrapperRef.current;\n const reference = wrapper?.firstElementChild instanceof HTMLElement\n ? wrapper.firstElementChild\n : wrapper;\n\n (triggerRef as React.MutableRefObject<HTMLElement | null>).current = reference;\n refs.setReference(reference);\n }, [children, refs]);\n\n useEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger || isDisabled) {\n return;\n }\n\n const handleMouseEnter = () => {\n setIsHovered(true);\n state.open();\n };\n const handleMouseLeave = () => {\n setIsHovered(false);\n state.close();\n };\n const handleFocusIn = () => {\n let focusVisible = false;\n try {\n focusVisible = trigger.matches(\":focus-visible\");\n } catch {\n focusVisible = true;\n }\n setIsFocused(true);\n setIsFocusVisible(focusVisible);\n state.open(true);\n };\n const handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && trigger.contains(event.relatedTarget)) {\n return;\n }\n\n setIsFocused(false);\n setIsFocusVisible(false);\n state.close(true);\n };\n\n trigger.addEventListener(\"mouseenter\", handleMouseEnter);\n trigger.addEventListener(\"mouseleave\", handleMouseLeave);\n trigger.addEventListener(\"focusin\", handleFocusIn);\n trigger.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n trigger.removeEventListener(\"mouseenter\", handleMouseEnter);\n trigger.removeEventListener(\"mouseleave\", handleMouseLeave);\n trigger.removeEventListener(\"focusin\", handleFocusIn);\n trigger.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, [children, isDisabled, state]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n const child = React.Children.only(children);\n\n const triggerElement = React.isValidElement(child) && child.type !== React.Fragment\n ? (\n <span\n ref={triggerWrapperRef}\n className={cn(css.trigger, className, resolved.trigger)}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {child}\n </span>\n )\n : (\n <span\n ref={mergedTriggerRef}\n className={cn(css.trigger, className, resolved.trigger)}\n style={{ display: \"inline-block\" }}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(ariaTooltipProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn(\"tooltip\", \"content\", css.content, resolved.content)}\n data-open={(isVisible && isTriggerVisible) ? \"true\" : \"false\"}\n data-instant={(isInstant || !isTriggerVisible) ? \"true\" : undefined}\n style={{\n transform: (isVisible && isTriggerVisible) ? \"scale(1)\" : getInitialTransform(placement),\n }}\n >\n <Frame\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n style={{ \"--frame-radius\": \"8px\" } as React.CSSProperties}\n >\n <div className={cn(\"tooltip\", \"frame\", css.frame, resolved.frame)} data-hint={hint ? \"true\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(css.hint, resolved.hint)}>{hint}</Badge>}\n </div>\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n\nexport { Tooltip };\n",
|
|
4596
|
+
"tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback, useLayoutEffect } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltip, mergeProps } from \"react-aria\";\nimport { useFloating } from \"../../hooks/useFloat/react/useFloating\";\nimport { flip } from \"../../hooks/useFloat/core/middleware/flip\";\nimport { offset } from \"../../hooks/useFloat/core/middleware/offset\";\nimport { shift } from \"../../hooks/useFloat/core/middleware/shift\";\nimport { autoUpdate } from \"../../hooks/useFloat/dom/autoUpdate\";\nimport { cn } from \"./utils\";\nimport { useTooltipTriggerState } from \"react-stately\";\nimport { asElementProps } from \"@/lib/react-aria\";\nimport { useMergeRefs } from \"@/hooks/useMergeRefs\";\nimport { Frame } from \"../Frame\";\nimport { Badge } from \"../Badge\";\nimport css from \"./Tooltip.module.css\";\nimport { type StylesProp, createStylesResolver } from \"@/lib/styles\";\nimport { type StyleValue } from \"./utils\";\n\nconst ARROW_PATH = \"M 0 0 C 3 0 4 -9 6 -9 C 8 -9 9 0 12 0\";\nconst ARROW_WIDTH = 12;\nconst TOOLTIP_GAP = 4;\nconst ARROW_POSITIONING_SIZE = 6;\nconst DEFAULT_SHOW_DELAY_MS = 600;\nconst SWAP_WINDOW_MS = 150;\nconst EXIT_ANIMATION_MS = 160;\n\nlet lastCloseTime = 0;\nlet lastOpenTime = 0;\nlet pendingExit: (() => void) | null = null;\n\nfunction useTimeout() {\n const idRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n const start = useCallback((fn: () => void, ms: number) => {\n clearTimeout(idRef.current);\n idRef.current = setTimeout(fn, ms);\n }, []);\n\n const clear = useCallback(() => {\n clearTimeout(idRef.current);\n }, []);\n\n useEffect(() => () => clearTimeout(idRef.current), []);\n\n return [start, clear] as const;\n}\n\ntype TooltipPosition = \"top\" | \"right\" | \"bottom\" | \"left\";\n\nconst getFrameSide = (position: TooltipPosition): \"top\" | \"right\" | \"bottom\" | \"left\" => {\n switch (position) {\n case \"top\":\n return \"bottom\";\n case \"bottom\":\n return \"top\";\n case \"left\":\n return \"right\";\n case \"right\":\n return \"left\";\n }\n};\n\nconst getInitialTransform = (placement: string): string => {\n switch (placement) {\n case \"top\":\n return \"translateY(3px) scale(0.95)\";\n case \"bottom\":\n return \"translateY(-3px) scale(0.95)\";\n case \"left\":\n return \"translateX(3px) scale(0.95)\";\n case \"right\":\n return \"translateX(-3px) scale(0.95)\";\n default:\n return \"scale(0.95)\";\n }\n};\n\nexport interface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hint?: StyleValue;\n}\n\nexport type TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipBaseStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hint',\n] as const);\n\nfunction resolveTooltipStyles(styles: TooltipStylesProp | undefined) {\n if (!styles || typeof styles === \"string\" || Array.isArray(styles)) return resolveTooltipBaseStyles(styles);\n const { root, trigger, content, frame, hint } = styles;\n return resolveTooltipBaseStyles({ root, trigger, content, frame, hint });\n}\n\nexport interface TooltipProps {\n children: React.ReactNode;\n /** Content to display inside the tooltip */\n content: React.ReactNode;\n /** Preferred side of the trigger where the tooltip appears */\n position?: TooltipPosition;\n /** Additional CSS class for the trigger wrapper */\n className?: string;\n /** Milliseconds before the tooltip appears after hover */\n delay?: number;\n /** Whether the tooltip is disabled */\n isDisabled?: boolean;\n /** Controlled open state */\n isOpen?: boolean;\n /** Called when the tooltip opens or closes */\n onOpenChange?: (isOpen: boolean) => void;\n /** Whether to render a directional arrow pointing at the trigger */\n showArrow?: boolean;\n /** Keyboard shortcut or hint text rendered as a Badge at the end of the tooltip */\n hint?: string;\n /** Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those. */\n styles?: TooltipStylesProp;\n}\n\nconst Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n children,\n content,\n position = \"top\",\n className,\n delay = DEFAULT_SHOW_DELAY_MS,\n isDisabled = false,\n isOpen: controlledIsOpen,\n onOpenChange,\n showArrow = false,\n hint,\n styles,\n },\n _ref\n ) => {\n const triggerWrapperRef = useRef<HTMLSpanElement>(null);\n const triggerRef = useRef<HTMLElement>(null);\n const tooltipRef = useRef<HTMLDivElement>(null);\n const [shouldRender, setShouldRender] = useState(false);\n const [isVisible, setIsVisible] = useState(false);\n const [isInstant, setIsInstant] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isFocusVisible, setIsFocusVisible] = useState(false);\n const wasOpenRef = useRef(false);\n const [startSwapTimer, clearSwapTimer] = useTimeout();\n const [startUnmountTimer, clearUnmountTimer] = useTimeout();\n\n const resolved = resolveTooltipStyles(styles);\n\n const onOpenChangeRef = useRef(onOpenChange);\n onOpenChangeRef.current = onOpenChange;\n\n const state = useTooltipTriggerState({\n isOpen: controlledIsOpen,\n onOpenChange: useCallback((open: boolean) => {\n if (open) lastOpenTime = Date.now();\n else lastCloseTime = Date.now();\n onOpenChangeRef.current?.(open);\n }, []),\n delay,\n isDisabled,\n });\n\n const { tooltipProps: ariaTooltipProps } = useTooltip({}, state);\n\n const { refs, floatingStyles, placement } = useFloating({\n placement: position,\n // Tooltips are commonly used in fixed headers; fixed positioning avoids scroll drift.\n strategy: \"fixed\",\n whileElementsMounted: autoUpdate,\n middleware: [\n offset(TOOLTIP_GAP + ARROW_POSITIONING_SIZE),\n flip(),\n shift({ padding: 8 }),\n ],\n });\n\n const isPositioned = floatingStyles.transform !== undefined;\n\n useEffect(() => {\n if (state.isOpen) {\n wasOpenRef.current = true;\n const elapsed = Date.now() - lastCloseTime;\n const isSwap = lastCloseTime > 0 && elapsed < SWAP_WINDOW_MS;\n\n if (pendingExit) {\n pendingExit();\n pendingExit = null;\n }\n\n setIsInstant(isSwap);\n setShouldRender(true);\n } else if (wasOpenRef.current) {\n wasOpenRef.current = false;\n\n if (lastOpenTime > 0 && lastOpenTime >= lastCloseTime) {\n setIsVisible(false);\n setShouldRender(false);\n return;\n }\n\n // Non-batched: delay exit to allow cross-frame swap detection.\n // If another tooltip opens within the window, pendingExit cancels.\n startSwapTimer(() => {\n setIsVisible(false);\n startUnmountTimer(() => {\n setShouldRender(false);\n pendingExit = null;\n }, EXIT_ANIMATION_MS);\n }, SWAP_WINDOW_MS);\n\n pendingExit = () => {\n clearSwapTimer();\n clearUnmountTimer();\n setIsVisible(false);\n setShouldRender(false);\n };\n }\n }, [state.isOpen]);\n\n useEffect(() => {\n if (shouldRender && state.isOpen && isPositioned) {\n if (isInstant) {\n setIsVisible(true);\n const frame = requestAnimationFrame(() => setIsInstant(false));\n return () => cancelAnimationFrame(frame);\n } else {\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n setIsVisible(true);\n });\n });\n }\n }\n }, [shouldRender, state.isOpen, isPositioned, isInstant]);\n\n const mergedTriggerRef = useMergeRefs(triggerRef, refs.setReference);\n\n const mergedFloatingRef = useMergeRefs(tooltipRef, refs.setFloating, _ref);\n\n useLayoutEffect(() => {\n const wrapper = triggerWrapperRef.current;\n const reference = wrapper?.firstElementChild instanceof HTMLElement\n ? wrapper.firstElementChild\n : wrapper;\n\n (triggerRef as React.MutableRefObject<HTMLElement | null>).current = reference;\n refs.setReference(reference);\n }, [children, refs]);\n\n useEffect(() => {\n const trigger = triggerRef.current;\n if (!trigger || isDisabled) {\n return;\n }\n\n const handleMouseEnter = () => {\n setIsHovered(true);\n state.open();\n };\n const handleMouseLeave = () => {\n setIsHovered(false);\n state.close();\n };\n const handleFocusIn = () => {\n let focusVisible = false;\n try {\n focusVisible = trigger.matches(\":focus-visible\");\n } catch {\n focusVisible = true;\n }\n setIsFocused(true);\n setIsFocusVisible(focusVisible);\n state.open(true);\n };\n const handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && trigger.contains(event.relatedTarget)) {\n return;\n }\n\n setIsFocused(false);\n setIsFocusVisible(false);\n state.close(true);\n };\n\n trigger.addEventListener(\"mouseenter\", handleMouseEnter);\n trigger.addEventListener(\"mouseleave\", handleMouseLeave);\n trigger.addEventListener(\"focusin\", handleFocusIn);\n trigger.addEventListener(\"focusout\", handleFocusOut);\n\n return () => {\n trigger.removeEventListener(\"mouseenter\", handleMouseEnter);\n trigger.removeEventListener(\"mouseleave\", handleMouseLeave);\n trigger.removeEventListener(\"focusin\", handleFocusIn);\n trigger.removeEventListener(\"focusout\", handleFocusOut);\n };\n }, [children, isDisabled, state]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n const child = React.Children.only(children);\n\n const triggerElement = React.isValidElement(child) && child.type !== React.Fragment\n ? (\n <span\n ref={triggerWrapperRef}\n className={cn(css.trigger, className, resolved.trigger)}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {child}\n </span>\n )\n : (\n <span\n ref={mergedTriggerRef}\n className={cn(css.trigger, className, resolved.trigger)}\n style={{ display: \"inline-block\" }}\n data-disabled={isDisabled ? \"true\" : undefined}\n data-hovered={isHovered ? \"true\" : \"false\"}\n data-focused={isFocused ? \"true\" : \"false\"}\n data-focus-visible={isFocusVisible ? \"true\" : \"false\"}\n >\n {children}\n </span>\n );\n\n return (\n <>\n {triggerElement}\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(ariaTooltipProps))}\n className={cn(css.root, resolved.root)}\n style={{\n ...floatingStyles,\n }}\n >\n <div\n className={cn(\"tooltip\", \"content\", css.content, resolved.content)}\n data-open={(isVisible && isTriggerVisible) ? \"true\" : \"false\"}\n data-instant={(isInstant || !isTriggerVisible) ? \"true\" : undefined}\n style={{\n transform: (isVisible && isTriggerVisible) ? \"scale(1)\" : getInitialTransform(placement),\n }}\n >\n <Frame\n side={showArrow ? getFrameSide(position) : position}\n shapeMode={showArrow ? \"extend\" : undefined}\n path={showArrow ? ARROW_PATH : undefined}\n pathWidth={showArrow ? ARROW_WIDTH : undefined}\n style={{ \"--frame-radius\": \"8px\" } as React.CSSProperties}\n >\n <div className={cn(\"tooltip\", \"frame\", css.frame, resolved.frame)} data-hint={hint ? \"true\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(css.hint, resolved.hint)}>{hint}</Badge>}\n </div>\n </Frame>\n </div>\n </div>,\n document.body\n )}\n </>\n );\n }\n);\n\nTooltip.displayName = \"Tooltip\";\n\nexport { Tooltip };\n",
|
|
4472
4597
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n display: contents;\n }\n\n .root {\n @apply absolute;\n pointer-events: none;\n z-index: 50;\n }\n\n .content {\n --frame-fill: var(--background);\n --frame-stroke-color: var(--background-border);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-open=\"true\"] {\n opacity: 1;\n pointer-events: auto;\n }\n\n .content[data-instant=\"true\"] {\n transition: none;\n }\n\n .frame {\n @apply flex items-center gap-1.5 px-2 py-1 whitespace-nowrap;\n color: var(--foreground);\n }\n\n .frame[data-hint=\"true\"] {\n @apply pr-1;\n }\n\n .hint {\n @apply flex-shrink-0;\n }\n}\n",
|
|
4473
4598
|
"cssTypes": "export interface Styles {\n trigger: string;\n root: string;\n content: string;\n frame: string;\n hint: string;\n}\n\ndeclare const styles: Styles;\nexport default styles;\n"
|
|
4474
4599
|
}
|
|
@@ -4705,6 +4830,6 @@ export const generatedCorePeerDependencies = [
|
|
|
4705
4830
|
"react-dom"
|
|
4706
4831
|
];
|
|
4707
4832
|
export const packageMetadata = {
|
|
4708
|
-
"version": "0.3.
|
|
4833
|
+
"version": "0.3.44"
|
|
4709
4834
|
};
|
|
4710
4835
|
//# sourceMappingURL=generated-data.js.map
|