ui-lab-registry 0.3.4 → 0.3.41
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/generated-data.d.ts.map +1 -1
- package/dist/generated-data.js +33 -17
- package/dist/generated-data.js.map +1 -1
- package/dist/generated-styles.js +70 -70
- package/dist/generated-styles.js.map +1 -1
- package/dist/generated-styles.json +70 -70
- package/package.json +2 -2
- package/src/generated-data.ts +33 -17
- package/src/generated-styles.ts +70 -70
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-data.d.ts","sourceRoot":"","sources":["../src/generated-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAElH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"generated-data.d.ts","sourceRoot":"","sources":["../src/generated-data.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAElH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CA69HrD,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAsC3D,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAoMnE,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwChD,CAAC;AAEF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAoLxE,CAAC;AAEF,eAAO,MAAM,4BAA4B,UAIxC,CAAC;AAEF,eAAO,MAAM,6BAA6B,UAGzC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,eAE7B,CAAC"}
|
package/dist/generated-data.js
CHANGED
|
@@ -25,8 +25,19 @@ export const generatedAPI = {
|
|
|
25
25
|
"type": "AnchorStylesProp",
|
|
26
26
|
"required": false,
|
|
27
27
|
"description": "Classes applied to the root or named slots. Accepts a string, cn()-compatible array, slot object, or array of any of those."
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"name": "preview",
|
|
31
|
+
"type": "ReactNode",
|
|
32
|
+
"required": false,
|
|
33
|
+
"description": "Preview content to show in a tooltip on hover. Use this in server components instead of <Anchor.Preview>."
|
|
28
34
|
}
|
|
29
35
|
],
|
|
36
|
+
"subComponents": {
|
|
37
|
+
"AnchorPreview": {
|
|
38
|
+
"props": []
|
|
39
|
+
}
|
|
40
|
+
},
|
|
30
41
|
"examples": [
|
|
31
42
|
{
|
|
32
43
|
"title": "Basic Anchor",
|
|
@@ -2068,7 +2079,14 @@ export const generatedAPI = {
|
|
|
2068
2079
|
]
|
|
2069
2080
|
},
|
|
2070
2081
|
"mask": {
|
|
2071
|
-
"props": [
|
|
2082
|
+
"props": [
|
|
2083
|
+
{
|
|
2084
|
+
"name": "asChild",
|
|
2085
|
+
"type": "boolean",
|
|
2086
|
+
"required": false,
|
|
2087
|
+
"defaultValue": "false"
|
|
2088
|
+
}
|
|
2089
|
+
],
|
|
2072
2090
|
"examples": [
|
|
2073
2091
|
{
|
|
2074
2092
|
"title": "Mask - Read More Effect",
|
|
@@ -2986,14 +3004,12 @@ export const generatedAPI = {
|
|
|
2986
3004
|
{
|
|
2987
3005
|
"name": "maxHeight",
|
|
2988
3006
|
"type": "string",
|
|
2989
|
-
"required": false
|
|
2990
|
-
"defaultValue": "100%"
|
|
3007
|
+
"required": false
|
|
2991
3008
|
},
|
|
2992
3009
|
{
|
|
2993
3010
|
"name": "maxWidth",
|
|
2994
3011
|
"type": "string",
|
|
2995
|
-
"required": false
|
|
2996
|
-
"defaultValue": "100%"
|
|
3012
|
+
"required": false
|
|
2997
3013
|
},
|
|
2998
3014
|
{
|
|
2999
3015
|
"name": "direction",
|
|
@@ -4045,7 +4061,7 @@ export const generatedAPI = {
|
|
|
4045
4061
|
}
|
|
4046
4062
|
};
|
|
4047
4063
|
export const generatedStyles = {
|
|
4048
|
-
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n
|
|
4064
|
+
"anchor": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\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);\n }\n\n &:focus-visible {\n outline: 2px solid 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);\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",
|
|
4049
4065
|
"badge": "@reference \"tailwindcss\";\n\n@layer components {\n .badge {\n @apply inline-flex items-center justify-center gap-2 px-3 py-0.5;\n height: fit-content;\n width: fit-content;\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--background-border);\n border-radius: var(--radius-sm);\n }\n\n .badge.sm {\n @apply px-1.5 py-px;\n gap: 0.25rem;\n font-size: var(--text-sm);\n }\n\n .badge.dismissible {\n @apply pr-0.5;\n }\n\n .badge.md {\n @apply px-3.5 py-1;\n font-size: var(--text-sm);\n }\n\n .badge.lg {\n @apply px-4 py-2.5;\n font-size: var(--text-sm);\n }\n\n .pill { border-radius: 9999px; }\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);\n background: transparent;\n border: none;\n color: var(--dismiss-color);\n transition: opacity 150ms var(--ease-snappy-pop), transform 150ms var(--ease-snappy-pop);\n outline: none;\n }\n\n .dismiss-button[data-hovered=\"true\"] {\n background: var(--dismiss-hover-background);\n }\n\n .dismiss-button[data-pressed=\"true\"] {\n background: var(--dismiss-pressed-background);\n transform: scale(0.95);\n }\n\n .dismiss-button[data-focus-visible=\"true\"] {\n outline: 2px solid currentColor;\n outline-offset: 1px;\n }\n}\n",
|
|
4050
4066
|
"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);\n line-height: var(--leading-normal);\n background-color: var(--background);\n color: var(--foreground);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n transition: background-color 0.15s ease-out, border-color 0.15s ease-out;\n }\n\n .content {\n @apply flex flex-col gap-2;\n }\n\n .icon-container {\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);\n transition: background-color 0.15s ease-out;\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);\n font-size: inherit;\n line-height: var(--leading-tight);\n @apply my-0;\n }\n\n .body {\n font-weight: var(--font-weight-medium);\n font-size: inherit;\n line-height: var(--leading-normal);\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",
|
|
4051
4067
|
"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 /* height: 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 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(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--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",
|
|
@@ -4078,15 +4094,15 @@ export const generatedStyles = {
|
|
|
4078
4094
|
"select": "@reference \"tailwindcss\";\n\n@layer components {\n .select {\n --disabled-opacity: 0.5;\n --padding-x: calc(var(--spacing) * 2.00);\n --padding-y: calc(var(--spacing) * 1.75);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n font-size: var(--font-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(--border-color);\n border-radius: var(--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]:not([data-disabled]) {\n background-color: var(--pressed-background);\n }\n\n &[aria-expanded=\"true\"] {\n background-color: var(--hover-background);\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(:disabled):hover .icon-section,\n &:not(:disabled):hover .value-section:not(:empty) {\n background-color: var(--hover-background);\n }\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--focus-ring-background), 0 0 0 4px var(--ring-color);\n @apply outline-none;\n }\n\n :global(.group) &:focus-visible {\n @apply outline-none;\n }\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(--padding-y) var(--padding-x);\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n font-size: var(--font-size);\n\n &:only-child {\n border-radius: var(--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(--padding-y) var(--padding-x);\n border-radius: 0 var(--inner-radius) var(--inner-radius) 0;\n }\n\n .icon {\n @apply flex items-center justify-center w-4 h-4 opacity-70;\n }\n\n .trigger[aria-expanded=\"true\"] .icon,\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 p-0;\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 --padding-x: calc(var(--spacing) * 1.5);\n --padding-y: var(--spacing);\n --radius: var(--radius-sm, 0.375rem);\n --inner-radius: calc(var(--radius) - var(--border-width-base, 1px));\n overflow: hidden;\n background-color: var(--content-background);\n border: var(--border-width-base, 1px) solid var(--content-border);\n border-radius: var(--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 --item-padding-x: var(--padding-x);\n --item-padding-y: calc(var(--padding-y) * 1.15);\n\n @apply flex items-center gap-2 outline-none cursor-default select-none;\n padding: var(--item-padding-y) var(--item-padding-x);\n\n border-radius: var(--inner-radius);\n font-size: var(--font-size);\n font-weight: var(--font-weight-medium);\n color: var(--item-foreground);\n\n &[data-selected=\"true\"] {\n color: var(--item-foreground);\n }\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n cursor: not-allowed;\n pointer-events: none;\n }\n &[data-highlighted=\"true\"] {\n background-color: var(--item-background-hover);\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(--font-size);\n font-weight: var(--font-weight-medium);\n color: var(--item-description-color);\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(--item-icon-color); }\n .item-indicator { color: var(--item-indicator-color); 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(--content-border); /* Reuses content border var */\n }\n\n .placeholder {\n color: var(--placeholder-color);\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(--inner-radius);\n transition: box-shadow 150ms var(--ease-snappy-pop), border-color 150ms var(--ease-snappy-pop);\n\n --input-active-border-color: transparent;\n --input-active-box-shadow: none;\n\n &:focus-within {\n @apply outline-none;\n box-shadow: 0 0 0 1px var(--search-focus-ring);\n z-index: 1;\n }\n }\n\n .search-value-section {\n @apply p-0;\n border-radius: var(--inner-radius) 0 0 var(--inner-radius);\n }\n\n .input {\n padding: var(--padding-y) calc(var(--padding-x) * 1.50);\n padding-right: calc(var(--padding-x) * 2 + 1rem);\n @apply border-none rounded-none shadow-none bg-transparent;\n\n &[data-active], &[data-focus-visible] {\n @apply border-none shadow-none;\n }\n }\n\n .search-content-input {\n padding-inline: calc(var(--padding-x) * 1.50);\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(--padding-x);\n }\n\n\n .search-wrapper {\n @apply overflow-hidden;\n border-bottom: var(--border-width-base, 1px) solid var(--content-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(--content-border);\n }\n\n .sub-trigger {\n --subtrigger-padding-x: var(--padding-x);\n --subtrigger-padding-y: var(--padding-y);\n\n @apply flex items-center gap-2 cursor-default select-none outline-none;\n padding: var(--subtrigger-padding-y) var(--subtrigger-padding-x);\n border-radius: var(--inner-radius);\n font-size: var(--font-size);\n font-weight: var(--font-weight-medium);\n color: var(--subtrigger-foreground);\n\n &[data-highlighted=\"true\"],\n &[data-state=\"open\"]:not([data-highlighted=\"true\"]) {\n background-color: var(--subtrigger-background-hover);\n }\n\n &[data-disabled] {\n opacity: var(--disabled-opacity, 0.5);\n pointer-events: none;\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",
|
|
4079
4095
|
"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 .slider[data-size=\"sm\"] { @apply h-6; }\n .slider[data-size=\"md\"] { @apply h-8; }\n .slider[data-size=\"lg\"] { @apply h-10; }\n\n .slider[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n .track {\n --track-height-sm: 0.25rem;\n --track-height-md: 0.375rem;\n --track-height-lg: 0.5rem;\n\n @apply relative flex grow items-center;\n flex-grow: 1;\n overflow: visible;\n border-radius: var(--radius-xs);\n background-color: var(--slider-track-background);\n }\n\n .slider[data-size=\"sm\"] .track { height: var(--track-height-sm); }\n .slider[data-size=\"md\"] .track { height: var(--track-height-md); }\n .slider[data-size=\"lg\"] .track { height: var(--track-height-lg); }\n\n .range {\n @apply absolute h-full;\n background-color: var(--slider-range-background-default);\n transition: background-color 200ms var(--ease-snappy-pop);\n border-radius: var(--radius-xs);\n }\n\n .slider[data-disabled] .range { background-color: var(--slider-range-background-disabled); }\n\n .thumb {\n --thumb-size-sm: 0.75rem;\n --thumb-size-md: 1rem;\n --thumb-size-lg: 1.25rem;\n\n @apply absolute block;\n background-color: var(--slider-thumb-background-default);\n border-radius: 9999px;\n outline: none;\n top: 50%;\n transform: translate(-50%, -50%);\n }\n\n .slider[data-size=\"sm\"] .thumb {\n width: var(--thumb-size-sm);\n height: var(--thumb-size-sm);\n }\n\n .slider[data-size=\"md\"] .thumb {\n width: var(--thumb-size-md);\n height: var(--thumb-size-md);\n }\n\n .slider[data-size=\"lg\"] .thumb {\n width: var(--thumb-size-lg);\n height: var(--thumb-size-lg);\n }\n\n .slider[data-disabled] .thumb {\n background-color: var(--slider-thumb-background-disabled);\n cursor: not-allowed;\n }\n\n .thumb[data-focus-visible] {\n background-color: var(--slider-thumb-background-focus);\n box-shadow: 0 0 0 3px var(--slider-thumb-ring);\n }\n\n .thumb[data-dragging] {\n cursor: grabbing;\n transform: translate(-50%, -50%) scale(1.1);\n }\n}\n",
|
|
4080
4096
|
"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(--track-background-unchecked);\n border: var(--border-width-base) solid var(--border);\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(--thumb-background-unchecked);\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(--track-background-checked);\n border-color: var(--border-checked);\n }\n\n .switch[data-selected] .switch-thumb {\n background-color: var(--thumb-background-checked);\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-hover);\n }\n }\n\n .switch[data-selected]:not([data-disabled]):active .switch-track {\n border-color: var(--border-active);\n }\n\n .switch[data-disabled] {\n opacity: var(--disabled-opacity);\n cursor: not-allowed;\n }\n\n\n\n .switch[data-focus-visible] {\n box-shadow: var(--focus-ring);\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",
|
|
4081
|
-
"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;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\n border-radius: var(--radius-xs);\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 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n
|
|
4097
|
+
"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;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\n border-radius: var(--radius-xs);\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 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n height: 100%;\n background-color: transparent;\n border: none;\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\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: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\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(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-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(--content-outline-color);\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;\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",
|
|
4082
4098
|
"textarea": "@reference \"tailwindcss\";\n\n@layer components {\n .textarea {\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n --disabled-opacity: 0.6;\n\n @apply block w-full px-3 py-2;\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-snug);\n color: var(--foreground);\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n box-sizing: border-box;\n resize: none;\n outline: none;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n &::placeholder {\n color: var(--placeholder);\n }\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n color: var(--disabled-foreground);\n cursor: not-allowed;\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n\n &[data-resize-axis=\"x\"],\n &[data-resize-axis=\"both\"] {\n padding-inline-end: calc(var(--textarea-padding-x) + 1rem);\n }\n\n &[data-resize-axis=\"y\"],\n &[data-resize-axis=\"both\"] {\n padding-block-end: calc(var(--textarea-padding-y) + 1rem);\n }\n\n &[data-scroll=\"true\"] {\n overflow: hidden;\n resize: none;\n background: transparent;\n border: none;\n border-radius: 0;\n box-shadow: none;\n\n &[data-active],\n &[data-focus-visible] {\n border-color: transparent;\n box-shadow: none;\n }\n\n &[data-disabled] {\n background-color: transparent;\n opacity: 1;\n }\n\n &[data-error] {\n border-color: transparent;\n\n &[data-active] {\n border-color: transparent;\n box-shadow: none;\n }\n }\n }\n }\n\n .surface {\n @apply relative w-full;\n }\n\n .resize-handle {\n position: absolute;\n z-index: 1;\n touch-action: none;\n user-select: none;\n\n &::before {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &::after {\n content: \"\";\n position: absolute;\n border-radius: var(--radius-full);\n background-color: var(--resize-handle-color);\n transition: background-color 150ms;\n }\n\n &:hover::before,\n &:hover::after {\n background-color: var(--resize-handle-color-hover);\n }\n\n &[data-axis=\"both\"] {\n right: 3px;\n bottom: 3px;\n width: 1.5rem;\n height: 1.5rem;\n cursor: nwse-resize;\n\n &::before {\n bottom: 0.35rem;\n right: 0.15rem;\n width: 0.5rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n\n &::after {\n bottom: 0.6rem;\n right: 0.2rem;\n width: 1.0rem;\n height: 0.125rem;\n transform: rotate(-45deg);\n transform-origin: center;\n }\n }\n\n &[data-axis=\"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\n &[data-axis=\"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 }\n\n .scroll-wrapper {\n --disabled-opacity: 0.6;\n\n @apply w-full overflow-hidden;\n background-color: var(--background);\n border: var(--border-width-base) solid var(--border);\n border-radius: var(--radius-sm);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-focus-visible] {\n outline: none;\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n\n &[data-disabled] {\n background-color: var(--disabled-background);\n opacity: var(--disabled-opacity);\n }\n\n &[data-error] {\n border-color: var(--border);\n\n &[data-active] {\n border-color: var(--ring-color);\n box-shadow: 0 0 0 1px mix(var(--ring-color) 20%, transparent);\n }\n }\n }\n\n .textarea[data-size=\"sm\"] {\n min-height: 5rem;\n --textarea-padding-x: 0.5rem;\n --textarea-padding-y: 0.25rem;\n font-size: var(--text-sm);\n @apply px-2 py-1;\n }\n\n .textarea[data-size=\"md\"] {\n min-height: 6rem;\n --textarea-padding-x: 0.75rem;\n --textarea-padding-y: 0.5rem;\n font-size: var(--text-sm);\n @apply px-3 py-2;\n }\n\n .textarea[data-size=\"lg\"] {\n min-height: 8rem;\n --textarea-padding-x: 1rem;\n --textarea-padding-y: 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 .character-count {\n font-size: var(--text-sm);\n color: var(--character-count-color);\n @apply mt-1;\n transition: color 0.15s var(--ease-snappy-pop);\n }\n\n .character-count[data-over-limit] {\n color: var(--character-count-over-limit-color);\n }\n}\n",
|
|
4083
4099
|
"toast": "@reference \"tailwindcss\";\n\n@layer components {\n .toast {\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) solid var(--border);\n border-radius: var(--radius-sm);\n box-shadow: var(--shadow);\n font-family: inherit;\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n touch-action: pan-y;\n }\n\n .icon {\n @apply mr-4 mt-2 h-5 w-5 shrink-0;\n color: var(--icon-color);\n }\n\n .content {\n @apply min-w-0 flex-1;\n }\n\n .title {\n @apply m-0;\n font-weight: var(--font-weight-semibold);\n font-size: var(--text-sm);\n line-height: var(--leading-tight);\n color: var(--title-color);\n }\n\n .description {\n @apply mt-1 mb-0;\n font-weight: var(--font-weight-medium);\n font-size: var(--text-sm);\n line-height: var(--leading-normal);\n color: var(--description-color);\n }\n\n .close {\n @apply flex shrink-0 items-center justify-center p-2 cursor-pointer;\n background: transparent;\n border: none;\n border-radius: var(--radius-sm);\n color: var(--close-color);\n opacity: 0.6;\n transition: opacity 0.15s ease-out;\n\n &:focus-visible {\n outline: 2px solid currentColor;\n outline-offset: 2px;\n }\n\n @media (hover: hover) {\n &:hover {\n opacity: 1;\n background: var(--close-hover-background);\n }\n }\n }\n}\n",
|
|
4084
|
-
"tooltip": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n
|
|
4100
|
+
"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(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\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] {\n @apply pr-1;\n }\n}\n"
|
|
4085
4101
|
};
|
|
4086
4102
|
export const generatedSourceCode = {
|
|
4087
4103
|
"anchor": {
|
|
4088
|
-
"tsx": "
|
|
4089
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .anchor {\n
|
|
4104
|
+
"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\", \"trigger\", css.trigger, className, resolved.root)}\n {...props}\n >\n {filteredChildren}\n </a>\n ) : (\n <span\n ref={ref as React.Ref<HTMLSpanElement>}\n className={cn(\"anchor\", \"trigger\", css.trigger, 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",
|
|
4105
|
+
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .preview, .anchor {\n display: inline\n }\n\n .trigger {\n @apply inline-block relative cursor-pointer;\n color: var(--foreground);\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);\n }\n\n &:focus-visible {\n outline: 2px solid 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);\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",
|
|
4090
4106
|
"cssTypes": "export const anchor: string;\nexport const preview: string;\nexport const trigger: string;\nexport const underline: string;"
|
|
4091
4107
|
},
|
|
4092
4108
|
"badge": {
|
|
@@ -4100,7 +4116,7 @@ export const generatedSourceCode = {
|
|
|
4100
4116
|
"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 icon: string;\n icon: string;\n title: string;\n body: string;\n};\n\nexport default styles;\n"
|
|
4101
4117
|
},
|
|
4102
4118
|
"button": {
|
|
4103
|
-
"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 =
|
|
4119
|
+
"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 = useMergedRef(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 if (isAnchor) {\n return (\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 );\n }\n\n return (\n <button\n {...mergeProps(buttonProps, focusProps, hoverProps, props)}\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 );\n }\n);\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\nButton.displayName = \"Button\";\n\nexport { Button };\n",
|
|
4104
4120
|
"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 /* height: 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 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(--hover-background);\n border-color: var(--hover-border);\n }\n\n &:active:not(:disabled) {\n filter: brightness(0.8);\n }\n\n &:focus-visible {\n box-shadow: 0 0 0 1.5px var(--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",
|
|
4105
4121
|
"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"
|
|
4106
4122
|
},
|
|
@@ -4190,7 +4206,7 @@ export const generatedSourceCode = {
|
|
|
4190
4206
|
"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 desc: string;\n readonly footer: string;\n};\n\nexport default styles;\n"
|
|
4191
4207
|
},
|
|
4192
4208
|
"mask": {
|
|
4193
|
-
"tsx": "
|
|
4209
|
+
"tsx": "import * as React from \"react\";\nimport { cn } from \"./utils\";\nimport styles from \"./Mask.module.css\";\n\nexport interface MaskProps extends React.HTMLAttributes<HTMLDivElement> {\n asChild?: boolean;\n children: React.ReactNode;\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 MaskRoot = React.forwardRef<HTMLDivElement, MaskProps>(\n ({ asChild = false, className, children, style, ...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\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: mergeRefs(ref, child.props.ref),\n className: cn(\"mask\", styles.mask, 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, 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 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\"], 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 mergeRefs<T>(\n ...refs: Array<React.Ref<T> | undefined>\n): React.RefCallback<T> {\n return (value) => {\n refs.forEach((ref) => {\n if (!ref) return;\n\n if (typeof ref === \"function\") {\n ref(value);\n return;\n }\n\n (ref as React.MutableRefObject<T | null>).current = value;\n });\n };\n}\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",
|
|
4194
4210
|
"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",
|
|
4195
4211
|
"cssTypes": "declare const styles: {\n mask: string;\n \"mask-gradient\": string;\n};\n\nexport default styles;\n"
|
|
4196
4212
|
},
|
|
@@ -4235,7 +4251,7 @@ export const generatedSourceCode = {
|
|
|
4235
4251
|
"cssTypes": "declare const styles: {\n \"radio-group\": string;\n \"radio-item\": string;\n \"radio-input\": string;\n radio: string;\n \"radio-dot\": string;\n sm: string;\n md: string;\n lg: string;\n \"radio-label\": string;\n \"radio-label-disabled\": string;\n \"radio-description\": string;\n \"radio-description-error\": string;\n};\n\nexport default styles;\n"
|
|
4236
4252
|
},
|
|
4237
4253
|
"scroll": {
|
|
4238
|
-
"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 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\ninterface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n horizontal?: StyleValue;\n vertical?: StyleValue;\n}\n\ntype 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 \"horizontal\",\n \"vertical\",\n] as const);\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 = \"100%\",\n maxWidth = \"100%\",\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 thumbRef = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRef(ref, containerRef);\n\n const resolved = resolveScrollBaseStyles(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 containerSize = container[clientSizeKey];\n const contentSize = content[scrollSizeKey] || containerSize;\n const currentScroll = content[scrollPosKey];\n const trackSize = isHoriz ? containerSize : containerSize - numPaddingY * 2;\n\n const needs = contentSize > containerSize;\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 maxScroll = contentSize - containerSize;\n const scrollProgress = needs ? 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 && 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 const gradient = `linear-gradient(to bottom, transparent 0%, black ${topP * fadeSize}%, black ${100 - botP * fadeSize}%, transparent 100%)`;\n content.style.maskImage = gradient;\n content.style.webkitMaskImage = gradient;\n } else if (!isHoriz) {\n content.style.maskImage = \"\";\n content.style.webkitMaskImage = \"\";\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 containerSize = containerRef.current[clientSizeKey];\n const maxScroll = contentRef.current[scrollSizeKey] - containerSize;\n const scrollRatio = maxScroll / (containerSize - 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 containerSize = containerRef.current[clientSizeKey];\n const contentSize = contentRef.current[scrollSizeKey];\n const maxScroll = contentSize - containerSize;\n const trackSize = isHoriz ? containerSize : containerSize - 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 = clampedThumbStart / maxThumbPos;\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 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 [isHoriz ? \"maxWidth\" : \"maxHeight\"]: isHoriz ? maxWidth : maxHeight,\n ...propsStyle,\n }}\n {...restProps}\n >\n {children}\n </div>\n );\n }\n\n const showOpacity = !hide || (needsScrollbar && (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 isHoriz ? resolved.horizontal : resolved.vertical\n )}\n style={{\n [isHoriz ? \"width\" : \"height\"]: \"100%\",\n [isHoriz ? \"maxWidth\" : \"maxHeight\"]: isHoriz ? maxWidth : maxHeight,\n ...(!isHoriz && strPaddingY ? { \"--scroll-padding-y\": strPaddingY } : {}),\n ...propsStyle,\n } as React.CSSProperties}\n onMouseMove={handleContainerMouseMove}\n onMouseLeave={handleContainerMouseLeave}\n data-dragging={String(isDragging)}\n data-inline={String(inline)}\n {...restProps}\n >\n <div\n ref={handleContentRef}\n className={cn(css.content, resolved.content)}\n onScroll={handleScroll}\n onWheel={isHoriz ? handleWheel : undefined}\n style={{ [isHoriz ? \"maxWidth\" : \"maxHeight\"]: \"inherit\" }}\n {...(storageKey\n ? {\n [SCROLL_RESTORE_STORAGE_KEY_ATTR]: storageKey,\n [SCROLL_RESTORE_AXIS_ATTR]: direction,\n }\n : {})}\n >\n {children}\n </div>\n\n <div\n className={cn(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 || !hide) && (\n <div\n ref={thumbRef}\n className={cn(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",
|
|
4254
|
+
"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\ninterface ScrollStyleSlots {\n root?: StyleValue;\n content?: StyleValue;\n track?: StyleValue;\n thumb?: StyleValue;\n horizontal?: StyleValue;\n vertical?: StyleValue;\n}\n\ntype 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 \"horizontal\",\n \"vertical\",\n] as const);\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 = resolveScrollBaseStyles(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 containerSize = container[clientSizeKey];\n const contentSize = content[scrollSizeKey] || containerSize;\n const currentScroll = content[scrollPosKey];\n const trackSize = isHoriz ? containerSize : containerSize - numPaddingY * 2;\n\n const needs = contentSize > containerSize;\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 maxScroll = contentSize - containerSize;\n const scrollProgress = needs ? 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 containerSize = containerRef.current[clientSizeKey];\n const maxScroll = contentRef.current[scrollSizeKey] - containerSize;\n const scrollRatio = maxScroll / (containerSize - 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 containerSize = containerRef.current[clientSizeKey];\n const contentSize = contentRef.current[scrollSizeKey];\n const maxScroll = contentSize - containerSize;\n const trackSize = isHoriz ? containerSize : containerSize - 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 = clampedThumbStart / maxThumbPos;\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 isHoriz ? resolved.horizontal : resolved.vertical\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-dragging={String(isDragging)}\n data-inline={String(inline)}\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(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(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",
|
|
4239
4255
|
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .root {\n @apply relative;\n }\n\n .vertical {\n --scrollbar-width: 12px;\n min-height: 0;\n }\n\n .horizontal { --scrollbar-height: 12px; }\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 }\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 background-color: var(--track-background);\n box-sizing: border-box;\n }\n\n .horizontal .track {\n bottom: 2px;\n left: 0;\n height: 12px;\n width: 100%;\n background-color: var(--track-background);\n }\n\n .thumb {\n position: absolute;\n border-radius: calc(var(--radius-xs) * 0.80);\n background-color: var(--thumb-background);\n transition-property: background-color, width, height;\n transition-duration: 150ms;\n }\n\n .thumb:hover { background-color: var(--thumb-hover-background); }\n\n .root[data-dragging=\"true\"] .thumb {\n background-color: var(--thumb-dragging-background);\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-dragging=\"true\"] .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-dragging=\"true\"] .thumb {\n height: 8px;\n margin-top: 4px;\n }\n}\n",
|
|
4240
4256
|
"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"
|
|
4241
4257
|
},
|
|
@@ -4261,7 +4277,7 @@ export const generatedSourceCode = {
|
|
|
4261
4277
|
},
|
|
4262
4278
|
"tabs": {
|
|
4263
4279
|
"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\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\ninterface 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 defaultValue?: 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 defaultValue,\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(defaultValue || \"\")\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\ninterface 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: 0,\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_THICKNESS,\n width: indicatorPosition.width,\n height: TABS_UNDERLINE_THICKNESS,\n }\n }\n\n const verticalPadding = TABS_INDICATOR_INSET\n const adjustedHeight = Math.max(0, listDimensions.height - verticalPadding * 2)\n return {\n ...baseStyle,\n left: indicatorPosition.left,\n top: verticalPadding,\n width: indicatorPosition.width,\n height: adjustedHeight,\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 TabsTriggerStyleSlots {\n root?: StyleValue\n icon?: StyleValue\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 displayed before the tab label */\n icon?: React.ReactNode\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', 'icon'] as const);\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 { root, icon: iconStyles } = resolveTabsTriggerBaseStyles(stylesProp)\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_THICKNESS,\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_THICKNESS,\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, 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 {icon && <span className={cn(css[\"trigger-icon\"], iconStyles)}>{icon}</span>}\n {children}\n </button>\n )\n }\n)\nTabsTrigger.displayName = \"Tab\"\n\ninterface 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",
|
|
4264
|
-
"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;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\n border-radius: var(--radius-xs);\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 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n background-color: transparent;\n border: none;\n
|
|
4280
|
+
"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;\n }\n\n &[data-variant=\"underline\"][data-orientation=\"vertical\"] {\n border-bottom: none;\n border-left: var(--border-width-base) solid var(--list-border-color);\n align-items: stretch;\n }\n }\n\n .indicator {\n @apply absolute;\n background-color: var(--indicator-background);\n box-sizing: border-box;\n border-radius: var(--radius-xs);\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 rounded-sm px-2 py-1.5 cursor-pointer select-none;\n height: 100%;\n background-color: transparent;\n border: none;\n color: var(--trigger-color);\n outline: none;\n transition: color 0.15s ease, background-color 0.15s ease;\n\n\n &:not([data-disabled]) {\n &:hover {\n color: var(--trigger-hover-color);\n }\n\n &:active {\n color: var(--trigger-active-color);\n }\n }\n\n &[data-selected=\"true\"] {\n color: var(--trigger-selected-color);\n }\n\n &[data-selected=\"true\"]:not([data-indicator-ready=\"true\"]):not([data-indicator-fallback=\"true\"]) {\n .list & {\n background-color: var(--trigger-selected-background);\n }\n\n .list[data-variant=\"underline\"] & {\n background-color: transparent;\n border-bottom-color: var(--trigger-underline-color);\n }\n\n .list[data-variant=\"underline\"][data-orientation=\"vertical\"] & {\n border-bottom-color: transparent;\n border-left-color: var(--trigger-underline-color);\n }\n }\n\n &[data-focus-visible] {\n background: var(--trigger-focus-background);\n outline: none;\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: transparent;\n border-radius: 0;\n border-bottom: 2px solid transparent;\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(--trigger-underline-color);\n border-bottom: none;\n }\n }\n\n .trigger-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(--content-outline-color);\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;\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",
|
|
4265
4281
|
"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 \"trigger-icon\": string;\n content: string;\n};\n\nexport default styles;\n"
|
|
4266
4282
|
},
|
|
4267
4283
|
"textarea": {
|
|
@@ -4275,8 +4291,8 @@ export const generatedSourceCode = {
|
|
|
4275
4291
|
"cssTypes": "declare const styles: {\n toast: string;\n icon: string;\n content: string;\n title: string;\n description: string;\n close: string;\n default: string;\n danger: string;\n success: string;\n info: string;\n warning: string;\n};\n\nexport default styles;\n"
|
|
4276
4292
|
},
|
|
4277
4293
|
"tooltip": {
|
|
4278
|
-
"tsx": "\"use client\";\n\nimport React, { useRef, useState, useEffect, useCallback } from \"react\";\n\nimport { createPortal } from \"react-dom\";\nimport { useTooltipTrigger, 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\ninterface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hintBadge?: StyleValue;\n}\n\ntype TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hintBadge',\n] as const);\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 triggerRef = useRef<HTMLDivElement>(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 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 { triggerProps, tooltipProps } = useTooltipTrigger(\n { isDisabled },\n state,\n triggerRef\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: HTMLDivElement | null) => {\n (triggerRef as React.MutableRefObject<HTMLDivElement | 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 }, [refs]);\n\n const trigger = triggerRef.current;\n const isTriggerVisible = !!(trigger && (trigger.offsetWidth > 0 || trigger.offsetHeight > 0));\n\n return (\n <>\n <div\n ref={mergedTriggerRef}\n {...asElementProps<\"div\">(mergeProps(triggerProps))}\n className={cn(css.trigger, className, resolved.trigger)}\n >\n {children}\n </div>\n\n {shouldRender &&\n createPortal(\n <div\n ref={mergedFloatingRef}\n {...asElementProps<\"div\">(mergeProps(tooltipProps, 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-visible={(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 cornerRadius={8}\n padding=\"none\"\n >\n <div className={cn('tooltip', 'frame', css.frame, resolved.frame)} data-hint={hint ? \"\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(resolved.hintBadge)}>{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",
|
|
4279
|
-
"css": "@reference \"tailwindcss\";\n\n@layer components {\n .trigger {\n
|
|
4294
|
+
"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\ninterface TooltipStyleSlots {\n root?: StyleValue;\n trigger?: StyleValue;\n content?: StyleValue;\n frame?: StyleValue;\n hintBadge?: StyleValue;\n}\n\ntype TooltipStylesProp = StylesProp<TooltipStyleSlots>;\n\nconst resolveTooltipStyles = createStylesResolver([\n 'root',\n 'trigger',\n 'content',\n 'frame',\n 'hintBadge',\n] as const);\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 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 }, [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 = () => state.open();\n const handleMouseLeave = () => state.close();\n const handleFocusIn = () => state.open(true);\n const handleFocusOut = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && trigger.contains(event.relatedTarget)) {\n return;\n }\n\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 >\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 >\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-visible={(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 cornerRadius={8}\n padding=\"none\"\n >\n <div className={cn('tooltip', 'frame', css.frame, resolved.frame)} data-hint={hint ? \"\" : undefined}>\n {content}\n {hint && <Badge variant=\"secondary\" size=\"sm\" className={cn(resolved.hintBadge)}>{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",
|
|
4295
|
+
"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(--tooltip-fill);\n --frame-stroke-color: var(--tooltip-border-color);\n opacity: 0;\n transition: opacity 0.15s ease-out, transform 0.15s ease-out;\n }\n\n .content[data-visible=\"true\"] {\n opacity: 1;\n pointer-events: auto;\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] {\n @apply pr-1;\n }\n}\n",
|
|
4280
4296
|
"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"
|
|
4281
4297
|
}
|
|
4282
4298
|
};
|
|
@@ -4512,6 +4528,6 @@ export const generatedCorePeerDependencies = [
|
|
|
4512
4528
|
"react-dom"
|
|
4513
4529
|
];
|
|
4514
4530
|
export const packageMetadata = {
|
|
4515
|
-
"version": "0.3.
|
|
4531
|
+
"version": "0.3.41"
|
|
4516
4532
|
};
|
|
4517
4533
|
//# sourceMappingURL=generated-data.js.map
|