@yewdiel21/motion-kit 0.9.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/auto/index.d.ts +72 -3
- package/dist/auto/index.js +139 -3
- package/dist/auto/index.js.map +1 -1
- package/dist/{chunk-BFC27F2G.js → chunk-6P4P7NU6.js} +2 -218
- package/dist/chunk-6P4P7NU6.js.map +1 -0
- package/dist/chunk-CU6SXW3E.js +20334 -0
- package/dist/chunk-CU6SXW3E.js.map +1 -0
- package/dist/{chunk-XDWBVFHQ.js → chunk-EY4BJKC4.js} +1203 -29
- package/dist/chunk-EY4BJKC4.js.map +1 -0
- package/dist/chunk-QP4QJOEN.js +72 -0
- package/dist/chunk-QP4QJOEN.js.map +1 -0
- package/dist/css/components/activity-feed.css +240 -0
- package/dist/css/components/attachment.css +266 -0
- package/dist/css/components/breadcrumb.css +110 -0
- package/dist/css/components/calendar.css +159 -0
- package/dist/css/components/chart-tooltip.css +168 -0
- package/dist/css/components/chart.css +296 -0
- package/dist/css/components/code-block.css +264 -0
- package/dist/css/components/color-area.css +82 -0
- package/dist/css/components/color-field.css +67 -0
- package/dist/css/components/color-picker.css +131 -0
- package/dist/css/components/color-slider.css +113 -0
- package/dist/css/components/command-search.css +252 -0
- package/dist/css/components/context-menu.css +23 -0
- package/dist/css/components/credit-card.css +288 -0
- package/dist/css/components/data-table.css +591 -0
- package/dist/css/components/date-range-picker.css +168 -0
- package/dist/css/components/date-time-picker.css +178 -0
- package/dist/css/components/drawer.css +58 -0
- package/dist/css/components/editable-table.css +201 -0
- package/dist/css/components/empty-state.css +61 -0
- package/dist/css/components/file-explorer.css +633 -0
- package/dist/css/components/filter-builder.css +232 -0
- package/dist/css/components/hold-confirm.css +87 -0
- package/dist/css/components/hover-card.css +20 -0
- package/dist/css/components/image-preview.css +195 -0
- package/dist/css/components/input-otp.css +10 -0
- package/dist/css/components/json-viewer.css +272 -0
- package/dist/css/components/like-button.css +78 -0
- package/dist/css/components/link-preview.css +275 -0
- package/dist/css/components/loading-button.css +77 -0
- package/dist/css/components/multi-select.css +267 -0
- package/dist/css/components/notification-bell.css +127 -0
- package/dist/css/components/pagination.css +11 -0
- package/dist/css/components/password-strength.css +183 -0
- package/dist/css/components/popover.css +25 -0
- package/dist/css/components/progress.css +140 -0
- package/dist/css/components/quantity.css +16 -0
- package/dist/css/components/questionnaire.css +217 -0
- package/dist/css/components/range-calendar.css +186 -0
- package/dist/css/components/range-slider.css +192 -0
- package/dist/css/components/resizable-panel.css +243 -0
- package/dist/css/components/rich-text-editor.css +284 -0
- package/dist/css/components/scratch-card.css +75 -0
- package/dist/css/components/skeleton.css +108 -0
- package/dist/css/components/slot-picker.css +126 -0
- package/dist/css/components/split-view.css +19 -0
- package/dist/css/components/star-rating.css +80 -0
- package/dist/css/components/stat-card.css +164 -0
- package/dist/css/components/studio-dock.css +35 -0
- package/dist/css/components/swipe-actions.css +105 -0
- package/dist/css/components/tag-input.css +244 -0
- package/dist/css/components/time-field.css +88 -0
- package/dist/css/components/timeline.css +219 -0
- package/dist/css/components/tooltip.css +79 -0
- package/dist/css/components/trailer.css +37 -0
- package/dist/css/components/tree-view.css +210 -0
- package/dist/css/components/virtual-list.css +195 -0
- package/dist/css/controls.css +65 -0
- package/dist/css/tokens.css +185 -181
- package/dist/elements/index.d.ts +742 -5
- package/dist/elements/index.js +1527 -8
- package/dist/elements/index.js.map +1 -1
- package/dist/{engine-GDGwQnJE.d.ts → engine-Cn6IhE3z.d.ts} +1 -1
- package/dist/engine-QECQnGz1.d.ts +33 -0
- package/dist/index.css +10113 -181
- package/dist/index.d.ts +89 -5
- package/dist/index.js +285 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.d.ts +678 -23
- package/dist/react/index.js +2389 -7
- package/dist/react/index.js.map +1 -1
- package/dist/{reduced-B98f8LPs.d.ts → reduced-BBUusld6.d.ts} +206 -2
- package/dist/reveal-DEvp6U6T.d.ts +2381 -0
- package/dist/{store-pHiUvxP8.d.ts → store-DjkTBX7f.d.ts} +2 -2
- package/package.json +1 -1
- package/registry.json +583 -104
- package/skills/motion-kit/SKILL.md +209 -189
- package/src/auto/auto-init.ts +136 -0
- package/src/components/activity-feed/README.md +56 -0
- package/src/components/activity-feed/activity-feed.css +239 -0
- package/src/components/activity-feed/element.ts +36 -0
- package/src/components/activity-feed/engine.ts +351 -0
- package/src/components/activity-feed/react.tsx +32 -0
- package/src/components/attachment/README.md +27 -0
- package/src/components/attachment/attachment.css +265 -0
- package/src/components/attachment/element.ts +50 -0
- package/src/components/attachment/engine.ts +469 -0
- package/src/components/attachment/react.tsx +65 -0
- package/src/components/breadcrumb/README.md +38 -0
- package/src/components/breadcrumb/breadcrumb.css +109 -0
- package/src/components/breadcrumb/element.ts +27 -0
- package/src/components/breadcrumb/engine.ts +249 -0
- package/src/components/breadcrumb/react.tsx +30 -0
- package/src/components/calendar/README.md +82 -0
- package/src/components/calendar/calendar.css +158 -0
- package/src/components/calendar/element.ts +27 -0
- package/src/components/calendar/engine.ts +412 -0
- package/src/components/calendar/react.tsx +69 -0
- package/src/components/chart/README.md +24 -0
- package/src/components/chart/chart.css +295 -0
- package/src/components/chart/element.ts +41 -0
- package/src/components/chart/engine.ts +811 -0
- package/src/components/chart/react.tsx +50 -0
- package/src/components/chart-tooltip/README.md +53 -0
- package/src/components/chart-tooltip/chart-tooltip.css +167 -0
- package/src/components/chart-tooltip/element.ts +37 -0
- package/src/components/chart-tooltip/engine.ts +442 -0
- package/src/components/chart-tooltip/react.tsx +47 -0
- package/src/components/code-block/README.md +19 -0
- package/src/components/code-block/code-block.css +263 -0
- package/src/components/code-block/element.ts +34 -0
- package/src/components/code-block/engine.ts +254 -0
- package/src/components/code-block/highlight.ts +244 -0
- package/src/components/code-block/react.tsx +94 -0
- package/src/components/color-area/README.md +50 -0
- package/src/components/color-area/color-area.css +81 -0
- package/src/components/color-area/element.ts +26 -0
- package/src/components/color-area/engine.ts +246 -0
- package/src/components/color-area/react.tsx +42 -0
- package/src/components/color-field/README.md +43 -0
- package/src/components/color-field/color-field.css +66 -0
- package/src/components/color-field/element.ts +26 -0
- package/src/components/color-field/engine.ts +174 -0
- package/src/components/color-field/react.tsx +42 -0
- package/src/components/color-picker/README.md +45 -0
- package/src/components/color-picker/color-picker.css +130 -0
- package/src/components/color-picker/element.ts +45 -0
- package/src/components/color-picker/engine.ts +314 -0
- package/src/components/color-picker/react.tsx +47 -0
- package/src/components/color-slider/README.md +55 -0
- package/src/components/color-slider/color-slider.css +112 -0
- package/src/components/color-slider/element.ts +26 -0
- package/src/components/color-slider/engine.ts +234 -0
- package/src/components/color-slider/react.tsx +45 -0
- package/src/components/command-search/README.md +15 -0
- package/src/components/command-search/command-search.css +251 -0
- package/src/components/command-search/element.ts +36 -0
- package/src/components/command-search/engine.ts +356 -0
- package/src/components/command-search/match.ts +53 -0
- package/src/components/command-search/react.tsx +66 -0
- package/src/components/context-menu/README.md +31 -0
- package/src/components/context-menu/context-menu.css +22 -0
- package/src/components/context-menu/element.ts +40 -0
- package/src/components/context-menu/engine.ts +153 -0
- package/src/components/context-menu/react.tsx +57 -0
- package/src/components/credit-card/README.md +20 -0
- package/src/components/credit-card/card.ts +77 -0
- package/src/components/credit-card/credit-card.css +287 -0
- package/src/components/credit-card/element.ts +33 -0
- package/src/components/credit-card/engine.ts +410 -0
- package/src/components/credit-card/react.tsx +36 -0
- package/src/components/data-table/README.md +83 -0
- package/src/components/data-table/data-table.css +590 -0
- package/src/components/data-table/element.ts +54 -0
- package/src/components/data-table/engine.ts +1059 -0
- package/src/components/data-table/react.tsx +94 -0
- package/src/components/date-range-picker/README.md +51 -0
- package/src/components/date-range-picker/date-range-picker.css +167 -0
- package/src/components/date-range-picker/element.ts +52 -0
- package/src/components/date-range-picker/engine.ts +352 -0
- package/src/components/date-range-picker/react.tsx +57 -0
- package/src/components/date-time-picker/README.md +48 -0
- package/src/components/date-time-picker/date-time-picker.css +177 -0
- package/src/components/date-time-picker/element.ts +47 -0
- package/src/components/date-time-picker/engine.ts +350 -0
- package/src/components/date-time-picker/react.tsx +54 -0
- package/src/components/dialog/README.md +103 -102
- package/src/components/dialog/element.ts +123 -118
- package/src/components/dialog/engine.ts +449 -432
- package/src/components/drawer/README.md +28 -0
- package/src/components/drawer/drawer.css +57 -0
- package/src/components/drawer/element.ts +21 -0
- package/src/components/drawer/engine.ts +56 -0
- package/src/components/drawer/react.tsx +85 -0
- package/src/components/editable-table/README.md +67 -0
- package/src/components/editable-table/editable-table.css +200 -0
- package/src/components/editable-table/element.ts +45 -0
- package/src/components/editable-table/engine.ts +775 -0
- package/src/components/editable-table/react.tsx +79 -0
- package/src/components/empty-state/README.md +41 -0
- package/src/components/empty-state/element.ts +23 -0
- package/src/components/empty-state/empty-state.css +60 -0
- package/src/components/empty-state/engine.ts +232 -0
- package/src/components/empty-state/react.tsx +34 -0
- package/src/components/file-explorer/README.md +67 -0
- package/src/components/file-explorer/element.ts +39 -0
- package/src/components/file-explorer/engine.ts +1307 -0
- package/src/components/file-explorer/file-explorer.css +632 -0
- package/src/components/file-explorer/react.tsx +77 -0
- package/src/components/filter-builder/README.md +47 -0
- package/src/components/filter-builder/element.ts +34 -0
- package/src/components/filter-builder/engine.ts +365 -0
- package/src/components/filter-builder/filter-builder.css +231 -0
- package/src/components/filter-builder/react.tsx +36 -0
- package/src/components/hold-confirm/README.md +13 -0
- package/src/components/hold-confirm/element.ts +24 -0
- package/src/components/hold-confirm/engine.ts +216 -0
- package/src/components/hold-confirm/hold-confirm.css +86 -0
- package/src/components/hold-confirm/react.tsx +28 -0
- package/src/components/hover-card/README.md +20 -0
- package/src/components/hover-card/element.ts +8 -0
- package/src/components/hover-card/engine.ts +131 -0
- package/src/components/hover-card/hover-card.css +19 -0
- package/src/components/hover-card/react.tsx +19 -0
- package/src/components/image-preview/README.md +16 -0
- package/src/components/image-preview/element.ts +26 -0
- package/src/components/image-preview/engine.ts +325 -0
- package/src/components/image-preview/image-preview.css +194 -0
- package/src/components/image-preview/react.tsx +43 -0
- package/src/components/input-otp/README.md +18 -0
- package/src/components/input-otp/element.ts +14 -0
- package/src/components/input-otp/engine.ts +106 -0
- package/src/components/input-otp/input-otp.css +9 -0
- package/src/components/input-otp/react.tsx +21 -0
- package/src/components/json-viewer/README.md +56 -0
- package/src/components/json-viewer/element.ts +37 -0
- package/src/components/json-viewer/engine.ts +440 -0
- package/src/components/json-viewer/json-viewer.css +271 -0
- package/src/components/json-viewer/react.tsx +56 -0
- package/src/components/like-button/README.md +13 -0
- package/src/components/like-button/element.ts +47 -0
- package/src/components/like-button/engine.ts +145 -0
- package/src/components/like-button/like-button.css +77 -0
- package/src/components/like-button/react.tsx +39 -0
- package/src/components/link-preview/README.md +19 -0
- package/src/components/link-preview/element.ts +29 -0
- package/src/components/link-preview/engine.ts +192 -0
- package/src/components/link-preview/link-preview.css +274 -0
- package/src/components/link-preview/react.tsx +101 -0
- package/src/components/loading-button/README.md +45 -0
- package/src/components/loading-button/element.ts +34 -0
- package/src/components/loading-button/engine.ts +201 -0
- package/src/components/loading-button/loading-button.css +76 -0
- package/src/components/loading-button/react.tsx +43 -0
- package/src/components/multi-select/README.md +48 -0
- package/src/components/multi-select/element.ts +28 -0
- package/src/components/multi-select/engine.ts +294 -0
- package/src/components/multi-select/multi-select.css +266 -0
- package/src/components/multi-select/react.tsx +39 -0
- package/src/components/notification-bell/README.md +14 -0
- package/src/components/notification-bell/element.ts +48 -0
- package/src/components/notification-bell/engine.ts +202 -0
- package/src/components/notification-bell/notification-bell.css +126 -0
- package/src/components/notification-bell/react.tsx +57 -0
- package/src/components/pagination/README.md +17 -0
- package/src/components/pagination/element.ts +14 -0
- package/src/components/pagination/engine.ts +102 -0
- package/src/components/pagination/pagination.css +10 -0
- package/src/components/pagination/react.tsx +22 -0
- package/src/components/password-strength/README.md +17 -0
- package/src/components/password-strength/element.ts +22 -0
- package/src/components/password-strength/engine.ts +212 -0
- package/src/components/password-strength/password-strength.css +182 -0
- package/src/components/password-strength/react.tsx +36 -0
- package/src/components/password-strength/score.ts +62 -0
- package/src/components/popover/README.md +44 -0
- package/src/components/popover/element.ts +31 -0
- package/src/components/popover/engine.ts +114 -0
- package/src/components/popover/popover.css +24 -0
- package/src/components/popover/react.tsx +50 -0
- package/src/components/progress/README.md +17 -0
- package/src/components/progress/element.ts +30 -0
- package/src/components/progress/engine.ts +173 -0
- package/src/components/progress/progress.css +139 -0
- package/src/components/progress/react.tsx +35 -0
- package/src/components/quantity/README.md +20 -0
- package/src/components/quantity/element.ts +14 -0
- package/src/components/quantity/engine.ts +85 -0
- package/src/components/quantity/quantity.css +15 -0
- package/src/components/quantity/react.tsx +21 -0
- package/src/components/questionnaire/README.md +32 -0
- package/src/components/questionnaire/element.ts +118 -0
- package/src/components/questionnaire/engine.ts +255 -0
- package/src/components/questionnaire/questionnaire.css +216 -0
- package/src/components/questionnaire/react.tsx +140 -0
- package/src/components/range-calendar/README.md +58 -0
- package/src/components/range-calendar/element.ts +42 -0
- package/src/components/range-calendar/engine.ts +390 -0
- package/src/components/range-calendar/range-calendar.css +185 -0
- package/src/components/range-calendar/react.tsx +56 -0
- package/src/components/range-slider/README.md +23 -0
- package/src/components/range-slider/element.ts +53 -0
- package/src/components/range-slider/engine.ts +316 -0
- package/src/components/range-slider/range-slider.css +191 -0
- package/src/components/range-slider/react.tsx +66 -0
- package/src/components/resizable-panel/README.md +61 -0
- package/src/components/resizable-panel/element.ts +36 -0
- package/src/components/resizable-panel/engine.ts +551 -0
- package/src/components/resizable-panel/react.tsx +74 -0
- package/src/components/resizable-panel/resizable-panel.css +242 -0
- package/src/components/rich-text-editor/README.md +57 -0
- package/src/components/rich-text-editor/element.ts +34 -0
- package/src/components/rich-text-editor/engine.ts +673 -0
- package/src/components/rich-text-editor/react.tsx +60 -0
- package/src/components/rich-text-editor/rich-text-editor.css +283 -0
- package/src/components/scratch-card/README.md +15 -0
- package/src/components/scratch-card/element.ts +30 -0
- package/src/components/scratch-card/engine.ts +257 -0
- package/src/components/scratch-card/react.tsx +40 -0
- package/src/components/scratch-card/scratch-card.css +74 -0
- package/src/components/skeleton/README.md +15 -0
- package/src/components/skeleton/element.ts +32 -0
- package/src/components/skeleton/engine.ts +104 -0
- package/src/components/skeleton/react.tsx +41 -0
- package/src/components/skeleton/skeleton.css +107 -0
- package/src/components/slot-picker/README.md +68 -0
- package/src/components/slot-picker/element.ts +27 -0
- package/src/components/slot-picker/engine.ts +412 -0
- package/src/components/slot-picker/react.tsx +76 -0
- package/src/components/slot-picker/slot-picker.css +125 -0
- package/src/components/split-view/README.md +50 -0
- package/src/components/split-view/element.ts +34 -0
- package/src/components/split-view/engine.ts +114 -0
- package/src/components/split-view/react.tsx +63 -0
- package/src/components/split-view/split-view.css +18 -0
- package/src/components/star-rating/README.md +14 -0
- package/src/components/star-rating/element.ts +26 -0
- package/src/components/star-rating/engine.ts +217 -0
- package/src/components/star-rating/react.tsx +33 -0
- package/src/components/star-rating/star-rating.css +79 -0
- package/src/components/stat-card/README.md +42 -0
- package/src/components/stat-card/element.ts +40 -0
- package/src/components/stat-card/engine.ts +283 -0
- package/src/components/stat-card/react.tsx +36 -0
- package/src/components/stat-card/stat-card.css +163 -0
- package/src/components/studio-dock/README.md +44 -0
- package/src/components/studio-dock/element.ts +11 -0
- package/src/components/studio-dock/engine.ts +212 -0
- package/src/components/studio-dock/react.tsx +23 -0
- package/src/components/studio-dock/studio-dock.css +34 -0
- package/src/components/swipe-actions/README.md +19 -0
- package/src/components/swipe-actions/element.ts +26 -0
- package/src/components/swipe-actions/engine.ts +260 -0
- package/src/components/swipe-actions/react.tsx +80 -0
- package/src/components/swipe-actions/swipe-actions.css +104 -0
- package/src/components/tag-input/README.md +42 -0
- package/src/components/tag-input/element.ts +39 -0
- package/src/components/tag-input/engine.ts +313 -0
- package/src/components/tag-input/react.tsx +54 -0
- package/src/components/tag-input/tag-input.css +243 -0
- package/src/components/time-field/README.md +54 -0
- package/src/components/time-field/element.ts +38 -0
- package/src/components/time-field/engine.ts +110 -0
- package/src/components/time-field/react.tsx +45 -0
- package/src/components/time-field/time-field.css +87 -0
- package/src/components/timeline/README.md +57 -0
- package/src/components/timeline/element.ts +31 -0
- package/src/components/timeline/engine.ts +300 -0
- package/src/components/timeline/react.tsx +59 -0
- package/src/components/timeline/timeline.css +218 -0
- package/src/components/tooltip/README.md +46 -0
- package/src/components/tooltip/element.ts +22 -0
- package/src/components/tooltip/engine.ts +288 -0
- package/src/components/tooltip/react.tsx +30 -0
- package/src/components/tooltip/tooltip.css +78 -0
- package/src/components/trailer/README.md +28 -0
- package/src/components/trailer/element.ts +13 -0
- package/src/components/trailer/engine.ts +189 -0
- package/src/components/trailer/react.tsx +16 -0
- package/src/components/trailer/trailer.css +36 -0
- package/src/components/tree-view/README.md +61 -0
- package/src/components/tree-view/element.ts +32 -0
- package/src/components/tree-view/engine.ts +569 -0
- package/src/components/tree-view/react.tsx +78 -0
- package/src/components/tree-view/tree-view.css +209 -0
- package/src/components/virtual-list/README.md +81 -0
- package/src/components/virtual-list/element.ts +42 -0
- package/src/components/virtual-list/engine.ts +678 -0
- package/src/components/virtual-list/react.tsx +101 -0
- package/src/components/virtual-list/virtual-list.css +194 -0
- package/src/core/chips.ts +196 -0
- package/src/core/color.ts +304 -0
- package/src/core/date-range.ts +67 -0
- package/src/core/dates.ts +108 -0
- package/src/core/files.ts +107 -0
- package/src/core/filter.ts +229 -0
- package/src/core/gsap.ts +80 -65
- package/src/core/listbox.ts +332 -0
- package/src/core/mark.ts +29 -0
- package/src/core/position.ts +89 -0
- package/src/core/relative-time.ts +77 -0
- package/src/core/segment-field.ts +373 -0
- package/src/core/segments.ts +153 -0
- package/src/core/tree.ts +116 -0
- package/src/core/virtual.ts +79 -0
- package/src/css/controls/README.md +14 -0
- package/src/css/controls/status.css +63 -0
- package/src/css/tokens.css +186 -182
- package/src/elements/index.ts +171 -0
- package/src/index.ts +97 -0
- package/src/motion/micro.ts +248 -0
- package/src/motion/sheet.ts +88 -65
- package/src/motion/surface.ts +9 -0
- package/src/react/index.ts +78 -0
- package/src/react/use-action-button.ts +6 -0
- package/src/react/use-editorial-reveal.ts +7 -0
- package/src/react/use-film-hero.ts +6 -0
- package/src/react/use-font-cycle.ts +7 -0
- package/src/react/use-image-zoom.ts +5 -0
- package/src/react/use-joined-cta.ts +5 -0
- package/src/react/use-local-clock.ts +5 -0
- package/src/react/use-scroll-dock.ts +5 -0
- package/src/react/use-sticker.ts +5 -0
- package/src/react/use-studio-dock.ts +8 -0
- package/src/react/use-thumbnail-nav.ts +7 -0
- package/src/react/use-trailer.ts +6 -0
- package/src/react/use-weight-hover.ts +5 -0
- package/src/recipes/action-button/README.md +31 -0
- package/src/recipes/action-button/action-button.css +113 -0
- package/src/recipes/action-button/create-action-button.ts +56 -0
- package/src/recipes/editorial-reveal/README.md +35 -0
- package/src/recipes/editorial-reveal/create-editorial-reveal.ts +91 -0
- package/src/recipes/editorial-reveal/editorial-reveal.css +17 -0
- package/src/recipes/film-hero/README.md +23 -0
- package/src/recipes/film-hero/create-film-hero.ts +65 -0
- package/src/recipes/film-hero/film-hero.css +29 -0
- package/src/recipes/font-cycle/README.md +11 -0
- package/src/recipes/font-cycle/create-font-cycle.ts +126 -0
- package/src/recipes/font-cycle/font-cycle.css +36 -0
- package/src/recipes/image-zoom/README.md +16 -0
- package/src/recipes/image-zoom/create-image-zoom.ts +27 -0
- package/src/recipes/image-zoom/image-zoom.css +7 -0
- package/src/recipes/joined-cta/README.md +13 -0
- package/src/recipes/joined-cta/create-joined-cta.ts +22 -0
- package/src/recipes/joined-cta/joined-cta.css +17 -0
- package/src/recipes/local-clock/README.md +31 -0
- package/src/recipes/local-clock/create-local-clock.ts +61 -0
- package/src/recipes/scroll-dock/README.md +46 -0
- package/src/recipes/scroll-dock/create-scroll-dock.ts +72 -0
- package/src/recipes/scroll-dock/scroll-dock.css +70 -0
- package/src/recipes/sticker/README.md +59 -0
- package/src/recipes/sticker/create-sticker.ts +490 -0
- package/src/recipes/sticker/sticker.css +112 -0
- package/src/recipes/thumbnail-nav/README.md +16 -0
- package/src/recipes/thumbnail-nav/create-thumbnail-nav.ts +165 -0
- package/src/recipes/thumbnail-nav/thumbnail-nav.css +108 -0
- package/src/recipes/weight-hover/README.md +37 -0
- package/src/recipes/weight-hover/create-weight-hover.ts +136 -0
- package/src/recipes/weight-hover/weight-hover.css +7 -0
- package/dist/chunk-BFC27F2G.js.map +0 -1
- package/dist/chunk-J6OCFNGU.js +0 -177
- package/dist/chunk-J6OCFNGU.js.map +0 -1
- package/dist/chunk-JQMJQSXD.js +0 -2374
- package/dist/chunk-JQMJQSXD.js.map +0 -1
- package/dist/chunk-XDWBVFHQ.js.map +0 -1
- package/dist/reveal-C0hCDcB_.d.ts +0 -415
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type HTMLAttributes } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import type { FilterGroup } from '../../core/filter';
|
|
5
|
+
import { createFilterBuilder, type FilterBuilderInstance, type FilterBuilderOptions } from './engine';
|
|
6
|
+
|
|
7
|
+
export interface FilterBuilderProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'>, FilterBuilderOptions {
|
|
8
|
+
/** Controlled filter; without it the builder keeps its own (defaultValue). */
|
|
9
|
+
value?: FilterGroup;
|
|
10
|
+
defaultValue?: FilterGroup;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** <FilterBuilder fields={fields} value={filter} onChange={setFilter} />. */
|
|
14
|
+
export function FilterBuilder({ fields, value, defaultValue, maxDepth, label, summary, onChange, className, ...props }: FilterBuilderProps) {
|
|
15
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
16
|
+
const instance = useRef<FilterBuilderInstance | null>(null);
|
|
17
|
+
const change = useLatest(onChange);
|
|
18
|
+
const start = useRef(value ?? defaultValue);
|
|
19
|
+
const fieldsKey = JSON.stringify(fields);
|
|
20
|
+
useIsomorphicLayoutEffect(() => {
|
|
21
|
+
const created = createFilterBuilder(ref.current, { fields, value: value ?? start.current, maxDepth, label, summary, onChange: (next) => change.current?.(next) });
|
|
22
|
+
instance.current = created;
|
|
23
|
+
return () => {
|
|
24
|
+
start.current = created.value;
|
|
25
|
+
created.destroy();
|
|
26
|
+
instance.current = null;
|
|
27
|
+
};
|
|
28
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
29
|
+
}, [fieldsKey, maxDepth, label, summary]);
|
|
30
|
+
const valueKey = JSON.stringify(value ?? null);
|
|
31
|
+
useIsomorphicLayoutEffect(() => {
|
|
32
|
+
if (value && instance.current && JSON.stringify(instance.current.value) !== valueKey) instance.current.setValue(value);
|
|
33
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
34
|
+
}, [valueKey]);
|
|
35
|
+
return <div {...props} ref={ref} className={['mk-filter-builder', className].filter(Boolean).join(' ')} />;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# hold-confirm
|
|
2
|
+
|
|
3
|
+
Confirmación por pulsación mantenida sobre el mismo botón: mientras se mantiene (puntero, Espacio o Enter) un relleno lo cruza durante `duration`, el botón se comprime un poco y la etiqueta cambia con blur. Soltar antes devuelve el relleno con resorte. Al completar: rebote de pulsación (curva click de Melonmind), un ✓ que se dibuja, partículas, `onConfirm` / `mk:confirm` y vuelta al inicio tras `resetAfter`.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<mk-hold-confirm class="mk-hold-confirm"><button>Eliminar cuenta</button></mk-hold-confirm>
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
`defineElements()`, `createHoldConfirm(root, options)` (root es el botón o lo contiene) o `<HoldConfirm onConfirm={remove}>Eliminar cuenta</HoldConfirm>`.
|
|
10
|
+
|
|
11
|
+
Opciones: `duration=1200` ms, `resetAfter=1800` ms (0 lo deja confirmado), `holdingLabel='Mantén pulsado…'`, `doneLabel='Confirmado'`, `hint='Mantén pulsado para confirmar'` (descripción accesible), `disabled`, `onConfirm()`. API: `reset()`, `confirmed`, `destroy()`. Un clic simple no envía formularios ni actúa. Un estado educado anuncia la confirmación.
|
|
12
|
+
|
|
13
|
+
Variables: `--mk-hold-bg`, `--mk-hold-fg`, `--mk-hold-fill`, `--mk-hold-on-fill` (tonos de error por defecto; `.mk-hold-confirm--neutral` usa superficie y texto). Movimiento reducido: el relleno salta y no hay partículas; la confirmación sigue llegando a su tiempo.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createHoldConfirm, type HoldConfirmInstance } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-hold-confirm class="mk-hold-confirm" duration="1200"><button>Eliminar cuenta</button></mk-hold-confirm>
|
|
6
|
+
* Hold the button (pointer, Space or Enter) to confirm. Emits mk:confirm.
|
|
7
|
+
*/
|
|
8
|
+
export class MkHoldConfirmElement extends MkElement<HoldConfirmInstance> {
|
|
9
|
+
static options: Record<string, OptionType> = { duration: 'number', resetAfter: 'number', holdingLabel: 'string', doneLabel: 'string', hint: 'string', disabled: 'boolean' };
|
|
10
|
+
|
|
11
|
+
protected create(options: Record<string, unknown>) {
|
|
12
|
+
return createHoldConfirm(this, options);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
reset() {
|
|
16
|
+
this.instance?.reset();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get confirmed() {
|
|
20
|
+
return this.instance?.confirmed ?? false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const defineHoldConfirm = () => defineElement('mk-hold-confirm', MkHoldConfirmElement);
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { gsap, EASE, registerMotion } from '../../core/gsap';
|
|
2
|
+
import { reduced } from '../../core/reduced';
|
|
3
|
+
import { createScope } from '../../core/scope';
|
|
4
|
+
import { inert, type Instance } from '../../core/types';
|
|
5
|
+
import { burst, press } from '../../motion/micro';
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Hold to confirm: the same button is held (pointer, Space or Enter) while a
|
|
9
|
+
* fill runs across it for `duration`; the button presses in a little and its
|
|
10
|
+
* label changes with a blur. Letting go early springs the fill back. Holding
|
|
11
|
+
* to the end confirms: a press bounce, a check that draws itself, a ring of
|
|
12
|
+
* particles, onConfirm / mk:confirm, and the button returns after `resetAfter`.
|
|
13
|
+
* The hint ("Mantén pulsado para confirmar") is the button's description.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export interface HoldConfirmOptions {
|
|
17
|
+
/** Hold time in ms (default 1200). */
|
|
18
|
+
duration?: number;
|
|
19
|
+
/** Back to the start after confirming, in ms (default 1800; 0 keeps it confirmed). */
|
|
20
|
+
resetAfter?: number;
|
|
21
|
+
holdingLabel?: string;
|
|
22
|
+
doneLabel?: string;
|
|
23
|
+
hint?: string;
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
onConfirm?: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface HoldConfirmInstance extends Instance {
|
|
29
|
+
reset(): void;
|
|
30
|
+
readonly confirmed: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const CHECK = '<svg aria-hidden="true" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12.5l4.5 4.5L19 7.5" pathLength="1"/></svg>';
|
|
34
|
+
let sequence = 0;
|
|
35
|
+
|
|
36
|
+
export function createHoldConfirm(root: HTMLElement | null, options: HoldConfirmOptions = {}): HoldConfirmInstance {
|
|
37
|
+
const button = root ? ((root.matches('button') ? root : root.querySelector('button')) as HTMLButtonElement | null) : null;
|
|
38
|
+
if (!root || !button) return inert({ reset() {}, confirmed: false });
|
|
39
|
+
registerMotion();
|
|
40
|
+
const scope = createScope();
|
|
41
|
+
const duration = Number.isFinite(options.duration) && options.duration! > 0 ? options.duration! : 1200;
|
|
42
|
+
const resetAfter = Number.isFinite(options.resetAfter) ? Math.max(0, options.resetAfter!) : 1800;
|
|
43
|
+
const made: Element[] = [];
|
|
44
|
+
const add = <T extends Element>(parent: Element, element: T, first = false) => {
|
|
45
|
+
if (first) parent.prepend(element);
|
|
46
|
+
else parent.append(element);
|
|
47
|
+
made.push(element);
|
|
48
|
+
return element;
|
|
49
|
+
};
|
|
50
|
+
scope.add(() => made.forEach((element) => element.remove()));
|
|
51
|
+
|
|
52
|
+
// The label: the authored text, kept in a span that swaps with a blur.
|
|
53
|
+
const authored = [...button.childNodes];
|
|
54
|
+
const label = document.createElement('span');
|
|
55
|
+
label.dataset.mkHoldLabel = '';
|
|
56
|
+
label.append(...authored);
|
|
57
|
+
button.append(label);
|
|
58
|
+
scope.add(() => {
|
|
59
|
+
label.remove();
|
|
60
|
+
button.append(...authored);
|
|
61
|
+
});
|
|
62
|
+
const fill = add(button, document.createElement('span'), true);
|
|
63
|
+
fill.dataset.mkHoldFill = '';
|
|
64
|
+
fill.setAttribute('aria-hidden', 'true');
|
|
65
|
+
const check = add(button, document.createElement('span'));
|
|
66
|
+
check.dataset.mkHoldCheck = '';
|
|
67
|
+
check.innerHTML = CHECK;
|
|
68
|
+
const hint = add(root === button ? button : root, document.createElement('span'));
|
|
69
|
+
hint.className = 'mk-sr-only';
|
|
70
|
+
hint.id = `mk-hold-${++sequence}`;
|
|
71
|
+
hint.textContent = options.hint ?? 'Mantén pulsado para confirmar';
|
|
72
|
+
const live = add(root === button ? button : root, document.createElement('span'));
|
|
73
|
+
live.className = 'mk-sr-only';
|
|
74
|
+
live.setAttribute('role', 'status');
|
|
75
|
+
|
|
76
|
+
scope.attr(button, 'type', button.getAttribute('type') ?? 'button');
|
|
77
|
+
scope.attr(button, 'aria-describedby', [button.getAttribute('aria-describedby'), hint.id].filter(Boolean).join(' '));
|
|
78
|
+
scope.attr(button, 'data-mk-hold', '');
|
|
79
|
+
scope.attr(button, 'style', button.getAttribute('style'));
|
|
80
|
+
if (options.disabled) scope.attr(button, 'disabled', '');
|
|
81
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
82
|
+
|
|
83
|
+
let holding = false;
|
|
84
|
+
let confirmed = false;
|
|
85
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
86
|
+
let back: ReturnType<typeof setTimeout> | undefined;
|
|
87
|
+
const effects = new Set<gsap.core.Animation>();
|
|
88
|
+
const keep = (animation: gsap.core.Animation | null) => {
|
|
89
|
+
if (!animation) return;
|
|
90
|
+
effects.add(animation);
|
|
91
|
+
// Finished ones leave as new ones arrive: the animation's own onComplete stays untouched.
|
|
92
|
+
for (const effect of effects) if (effect.totalProgress() === 1) effects.delete(effect);
|
|
93
|
+
};
|
|
94
|
+
scope.add(() => {
|
|
95
|
+
clearTimeout(timer);
|
|
96
|
+
clearTimeout(back);
|
|
97
|
+
for (const effect of effects) effect.kill();
|
|
98
|
+
effects.clear();
|
|
99
|
+
gsap.killTweensOf([fill, button, label, check]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/** Show `text`, or the authored content again with null. */
|
|
103
|
+
let showing: string | null = null;
|
|
104
|
+
const write = (text: string | null) => {
|
|
105
|
+
showing = text;
|
|
106
|
+
if (text === null) label.replaceChildren(...authored);
|
|
107
|
+
else label.textContent = text;
|
|
108
|
+
};
|
|
109
|
+
const swap = (text: string | null) => {
|
|
110
|
+
if (showing === text) return;
|
|
111
|
+
if (reduced()) {
|
|
112
|
+
write(text);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
gsap.killTweensOf(label);
|
|
116
|
+
keep(
|
|
117
|
+
gsap
|
|
118
|
+
.timeline()
|
|
119
|
+
.to(label, { opacity: 0, filter: 'blur(6px)', y: -4, duration: 0.12, ease: 'power2.in' })
|
|
120
|
+
.call(() => write(text))
|
|
121
|
+
.fromTo(label, { opacity: 0, filter: 'blur(6px)', y: 4 }, { opacity: 1, filter: 'blur(0px)', y: 0, duration: 0.25, ease: EASE.pop, clearProps: 'all' }),
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const setProgress = (value: number, seconds: number, ease: string) => {
|
|
126
|
+
gsap.killTweensOf(fill, 'scaleX');
|
|
127
|
+
if (reduced() || seconds === 0) gsap.set(fill, { scaleX: value });
|
|
128
|
+
else keep(gsap.to(fill, { scaleX: value, duration: seconds, ease }));
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
const start = () => {
|
|
132
|
+
if (holding || confirmed || button.disabled || scope.disposed) return;
|
|
133
|
+
holding = true;
|
|
134
|
+
scope.attr(button, 'data-mk-holding', '');
|
|
135
|
+
swap(options.holdingLabel ?? 'Mantén pulsado…');
|
|
136
|
+
setProgress(1, duration / 1000, 'power1.in');
|
|
137
|
+
if (!reduced()) keep(gsap.to(button, { scale: 0.96, duration: duration / 1000, ease: 'power1.out' }));
|
|
138
|
+
clearTimeout(timer);
|
|
139
|
+
timer = setTimeout(confirm, duration);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const cancel = () => {
|
|
143
|
+
if (!holding) return;
|
|
144
|
+
holding = false;
|
|
145
|
+
clearTimeout(timer);
|
|
146
|
+
scope.attr(button, 'data-mk-holding', null);
|
|
147
|
+
swap(null);
|
|
148
|
+
setProgress(0, 0.5, EASE.swatch);
|
|
149
|
+
if (!reduced()) keep(gsap.to(button, { scale: 1, duration: 0.5, ease: EASE.click, clearProps: 'scale,transform' }));
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const reset = () => {
|
|
153
|
+
if (scope.disposed) return;
|
|
154
|
+
clearTimeout(back);
|
|
155
|
+
confirmed = false;
|
|
156
|
+
holding = false;
|
|
157
|
+
scope.attr(button, 'data-mk-confirmed', null);
|
|
158
|
+
scope.attr(button, 'data-mk-holding', null);
|
|
159
|
+
swap(null);
|
|
160
|
+
setProgress(0, 0.4, 'power2.out');
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
function confirm() {
|
|
164
|
+
if (!holding || scope.disposed) return;
|
|
165
|
+
holding = false;
|
|
166
|
+
confirmed = true;
|
|
167
|
+
scope.attr(button!, 'data-mk-holding', null);
|
|
168
|
+
scope.attr(button!, 'data-mk-confirmed', '');
|
|
169
|
+
setProgress(1, 0, 'none');
|
|
170
|
+
gsap.killTweensOf(button, 'scale');
|
|
171
|
+
gsap.set(button, { clearProps: 'scale,transform' });
|
|
172
|
+
keep(press(button));
|
|
173
|
+
swap(options.doneLabel ?? 'Confirmado');
|
|
174
|
+
const path = check.querySelector('path');
|
|
175
|
+
if (path && !reduced()) keep(gsap.fromTo(path, { strokeDasharray: 1, strokeDashoffset: 1 }, { strokeDashoffset: 0, duration: 0.45, delay: 0.1, ease: 'power2.out' }));
|
|
176
|
+
keep(burst(button, { count: 8, distance: 34, colors: ['currentColor'] }));
|
|
177
|
+
live.textContent = options.doneLabel ?? 'Confirmado';
|
|
178
|
+
options.onConfirm?.();
|
|
179
|
+
root!.dispatchEvent(new CustomEvent('mk:confirm', { bubbles: true }));
|
|
180
|
+
if (resetAfter) back = setTimeout(reset, resetAfter);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
scope.on(button, 'pointerdown', (event) => {
|
|
184
|
+
const pointer = event as PointerEvent;
|
|
185
|
+
if (pointer.button !== 0) return;
|
|
186
|
+
pointer.preventDefault();
|
|
187
|
+
button.focus({ preventScroll: true });
|
|
188
|
+
start();
|
|
189
|
+
});
|
|
190
|
+
for (const type of ['pointerup', 'pointerleave', 'pointercancel']) scope.on(button, type, cancel);
|
|
191
|
+
scope.on(button, 'keydown', (event) => {
|
|
192
|
+
const key = event as KeyboardEvent;
|
|
193
|
+
if (key.key !== ' ' && key.key !== 'Enter') return;
|
|
194
|
+
key.preventDefault();
|
|
195
|
+
if (!key.repeat) start();
|
|
196
|
+
});
|
|
197
|
+
scope.on(button, 'keyup', (event) => {
|
|
198
|
+
const key = (event as KeyboardEvent).key;
|
|
199
|
+
if (key === ' ' || key === 'Enter') cancel();
|
|
200
|
+
});
|
|
201
|
+
scope.on(button, 'blur', cancel);
|
|
202
|
+
// A plain click (no hold) must not submit a form or act.
|
|
203
|
+
scope.on(button, 'click', (event) => event.preventDefault());
|
|
204
|
+
scope.on(button, 'contextmenu', (event) => {
|
|
205
|
+
if (holding) event.preventDefault();
|
|
206
|
+
});
|
|
207
|
+
gsap.set(fill, { scaleX: 0 });
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
reset,
|
|
211
|
+
get confirmed() {
|
|
212
|
+
return confirmed;
|
|
213
|
+
},
|
|
214
|
+
destroy: () => scope.dispose(),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* hold-confirm — Melonmind button (64 px pill, surface tones); while held a darker fill runs from the left,
|
|
2
|
+
the button presses in, the label changes; confirmed, a check draws itself. */
|
|
3
|
+
.mk-hold-confirm,
|
|
4
|
+
mk-hold-confirm {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
[data-mk-hold] {
|
|
9
|
+
--mk-hold-bg: var(--mk-error-soft);
|
|
10
|
+
--mk-hold-fg: var(--mk-on-error-soft);
|
|
11
|
+
--mk-hold-fill: var(--mk-error);
|
|
12
|
+
--mk-hold-on-fill: var(--mk-on-error);
|
|
13
|
+
position: relative;
|
|
14
|
+
isolation: isolate;
|
|
15
|
+
display: inline-flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
gap: 0.5rem;
|
|
19
|
+
min-height: 3rem;
|
|
20
|
+
min-width: 12rem;
|
|
21
|
+
padding: 0 1.75rem;
|
|
22
|
+
overflow: hidden;
|
|
23
|
+
border: 0;
|
|
24
|
+
border-radius: 64px;
|
|
25
|
+
background: var(--mk-hold-bg);
|
|
26
|
+
color: var(--mk-hold-fg);
|
|
27
|
+
font: inherit;
|
|
28
|
+
font-weight: 500;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
user-select: none;
|
|
31
|
+
-webkit-user-select: none;
|
|
32
|
+
touch-action: manipulation;
|
|
33
|
+
-webkit-touch-callout: none;
|
|
34
|
+
transition: color 0.2s ease;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
[data-mk-hold]:focus-visible {
|
|
38
|
+
outline: 2px solid var(--mk-accent);
|
|
39
|
+
outline-offset: 3px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
[data-mk-hold]:disabled {
|
|
43
|
+
opacity: 0.5;
|
|
44
|
+
cursor: not-allowed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
[data-mk-hold] [data-mk-hold-fill] {
|
|
48
|
+
position: absolute;
|
|
49
|
+
inset: 0;
|
|
50
|
+
z-index: -1;
|
|
51
|
+
border-radius: inherit;
|
|
52
|
+
background: var(--mk-hold-fill);
|
|
53
|
+
transform-origin: left center;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[data-mk-hold]:is([data-mk-holding], [data-mk-confirmed]) {
|
|
57
|
+
color: var(--mk-hold-on-fill);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
[data-mk-hold] [data-mk-hold-label] {
|
|
61
|
+
display: inline-block;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
[data-mk-hold] [data-mk-hold-check] {
|
|
65
|
+
display: none;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
[data-mk-hold][data-mk-confirmed] [data-mk-hold-check] {
|
|
69
|
+
display: inline-grid;
|
|
70
|
+
order: -1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* The neutral variant (Melonmind tonal). */
|
|
74
|
+
.mk-hold-confirm--neutral [data-mk-hold],
|
|
75
|
+
[data-mk-hold].mk-hold-confirm--neutral {
|
|
76
|
+
--mk-hold-bg: var(--mk-surface-3);
|
|
77
|
+
--mk-hold-fg: var(--mk-text);
|
|
78
|
+
--mk-hold-fill: var(--mk-text);
|
|
79
|
+
--mk-hold-on-fill: var(--mk-bg);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
@media (prefers-reduced-motion: reduce) {
|
|
83
|
+
[data-mk-hold] {
|
|
84
|
+
transition: none;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type ButtonHTMLAttributes, type ReactNode } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { createHoldConfirm, type HoldConfirmOptions } from './engine';
|
|
5
|
+
|
|
6
|
+
export interface HoldConfirmProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'onClick'>, HoldConfirmOptions {
|
|
7
|
+
children?: ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* <HoldConfirm onConfirm={removeAccount}>Eliminar cuenta</HoldConfirm>: hold the button to confirm.
|
|
12
|
+
* The engine moves the label into its own span, so pass plain text or inline content.
|
|
13
|
+
*/
|
|
14
|
+
export function HoldConfirm({ duration, resetAfter, holdingLabel, doneLabel, hint, disabled, onConfirm, className, children, ...props }: HoldConfirmProps) {
|
|
15
|
+
const ref = useRef<HTMLSpanElement>(null);
|
|
16
|
+
const confirm = useLatest(onConfirm);
|
|
17
|
+
useIsomorphicLayoutEffect(() => {
|
|
18
|
+
const instance = createHoldConfirm(ref.current, { duration, resetAfter, holdingLabel, doneLabel, hint, disabled, onConfirm: () => confirm.current?.() });
|
|
19
|
+
return () => instance.destroy();
|
|
20
|
+
}, [duration, resetAfter, holdingLabel, doneLabel, hint, disabled]);
|
|
21
|
+
return (
|
|
22
|
+
<span ref={ref} className={['mk-hold-confirm', className].filter(Boolean).join(' ')}>
|
|
23
|
+
<button type="button" {...props}>
|
|
24
|
+
<span data-mk-hold-text="">{children}</span>
|
|
25
|
+
</button>
|
|
26
|
+
</span>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# hover-card
|
|
2
|
+
|
|
3
|
+
Vista previa no modal, accesible al señalar, enfocar o tocar. Los enlaces/botones del panel pueden recibir foco.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<mk-hover-card class="mk-hover-card" placement="bottom">
|
|
7
|
+
<button type="button" data-mk-hover-trigger>Ver estudio</button>
|
|
8
|
+
<div data-mk-hover-panel hidden role="region" aria-label="Vista previa">Contenido</div>
|
|
9
|
+
</mk-hover-card>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`defineElements()`, `createHoverCard(root, options)`, o `<HoverCard trigger="Ver estudio" placement="bottom">Contenido</HoverCard>`.
|
|
13
|
+
|
|
14
|
+
Opciones: `openDelay=120` ms, `closeDelay=140` ms, `placement='top'|'bottom'` (lado preferido), `onOpenChange(boolean)`. API `open()`, `close()`, `reposition()`, `isOpen`, `destroy()`. Evento `mk:open-change` con `{open}`.
|
|
15
|
+
|
|
16
|
+
Escape cierra y retorna al disparador; salir con foco y puntero fuera o tocar fuera cierra. El puntero puede pasar del disparador al panel sin cerrarlo y el panel admite foco en su contenido. No atrapa el foco.
|
|
17
|
+
|
|
18
|
+
El panel se posiciona respecto al root (sin portal: mantener un ancestro que permita overflow) y no se sale del viewport: si su lado no tiene sitio pasa al otro (`data-mk-side`) y se desplaza en horizontal lejos de los bordes (`--mk-hover-shift`); vuelve a colocarse con scroll y resize mientras está abierto. El panel es cristal inverso (`--mk-glass-inverse`, radio de 24px) y entra con un pop de .3 s desde el blur al señalar o tocar; abierto desde teclado (`data-mk-input="keyboard"`) aparece sin entrada, igual que con movimiento reducido. Limpia timers, listeners y frames y restaura todos los atributos.
|
|
19
|
+
|
|
20
|
+
[link-preview](../link-preview/README.md) lo compone para previsualizar enlaces.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createHoverCard, type HoverCardInstance } from './engine';
|
|
3
|
+
/** Light DOM: supply the same child markup shown in the README. */
|
|
4
|
+
export class MkHoverCardElement extends MkElement<HoverCardInstance> {
|
|
5
|
+
static options: Record<string, OptionType> = { openDelay: 'number', closeDelay: 'number', placement: 'string' };
|
|
6
|
+
protected create(options: Record<string, unknown>) { return createHoverCard(this, options); }
|
|
7
|
+
}
|
|
8
|
+
export const defineHoverCard = () => defineElement('mk-hover-card', MkHoverCardElement);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { requestFrame } from '../../core/frame';
|
|
2
|
+
import { createScope } from '../../core/scope';
|
|
3
|
+
import { inert, type Instance } from '../../core/types';
|
|
4
|
+
|
|
5
|
+
export interface HoverCardOptions {
|
|
6
|
+
openDelay?: number;
|
|
7
|
+
closeDelay?: number;
|
|
8
|
+
/** Preferred side; it flips when the viewport has no room there. */
|
|
9
|
+
placement?: 'top' | 'bottom';
|
|
10
|
+
onOpenChange?: (open: boolean) => void;
|
|
11
|
+
}
|
|
12
|
+
export interface HoverCardInstance extends Instance {
|
|
13
|
+
open(): void;
|
|
14
|
+
close(): void;
|
|
15
|
+
/** Place the open panel again (after its content changed size). */
|
|
16
|
+
reposition(): void;
|
|
17
|
+
readonly isOpen: boolean;
|
|
18
|
+
}
|
|
19
|
+
let sequence = 0;
|
|
20
|
+
/** Distance kept from the viewport edges. */
|
|
21
|
+
const EDGE = 12;
|
|
22
|
+
const GAP = 12;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Nonmodal preview: pointer, keyboard focus and tap all reveal the same panel.
|
|
26
|
+
* The panel stays inside the viewport: it flips to the other side when its own
|
|
27
|
+
* has no room and shifts sideways (--mk-hover-shift) away from the edges, again
|
|
28
|
+
* on scroll and resize. Opened from the keyboard it appears without the entrance.
|
|
29
|
+
*/
|
|
30
|
+
export function createHoverCard(root: HTMLElement | null, options: HoverCardOptions = {}): HoverCardInstance {
|
|
31
|
+
const trigger = root?.querySelector<HTMLElement>('[data-mk-hover-trigger]');
|
|
32
|
+
const panel = root?.querySelector<HTMLElement>('[data-mk-hover-panel]');
|
|
33
|
+
if (!root || !trigger || !panel) return inert({ open() {}, close() {}, reposition() {}, isOpen: false });
|
|
34
|
+
const scope = createScope();
|
|
35
|
+
const openDelay = Number.isFinite(options.openDelay) ? Math.max(0, options.openDelay!) : 120;
|
|
36
|
+
const closeDelay = Number.isFinite(options.closeDelay) ? Math.max(0, options.closeDelay!) : 140;
|
|
37
|
+
const preferred = options.placement ?? 'top';
|
|
38
|
+
let opened = false;
|
|
39
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
40
|
+
let pointerInside = false;
|
|
41
|
+
/** The last input was a pointer (focus that follows a click is not keyboard). */
|
|
42
|
+
let pointerInput = false;
|
|
43
|
+
let id = panel.id;
|
|
44
|
+
if (!id) { do { id = `mk-hover-${++sequence}`; } while (document.getElementById(id)); }
|
|
45
|
+
scope.attr(panel, 'id', id);
|
|
46
|
+
scope.attr(trigger, 'aria-controls', id);
|
|
47
|
+
scope.attr(root, 'data-mk-placement', preferred);
|
|
48
|
+
scope.attr(root, 'data-mk-side', preferred);
|
|
49
|
+
scope.attr(root, 'style', root.getAttribute('style'));
|
|
50
|
+
scope.attr(root, 'data-mk-ready', '');
|
|
51
|
+
|
|
52
|
+
const place = () => {
|
|
53
|
+
if (!opened || scope.disposed) return;
|
|
54
|
+
root.style.removeProperty('--mk-hover-shift');
|
|
55
|
+
const anchor = root.getBoundingClientRect();
|
|
56
|
+
const box = panel.getBoundingClientRect();
|
|
57
|
+
const view = { width: innerWidth, height: innerHeight };
|
|
58
|
+
const need = box.height + GAP + EDGE;
|
|
59
|
+
const above = anchor.top;
|
|
60
|
+
const below = view.height - anchor.bottom;
|
|
61
|
+
let side = preferred;
|
|
62
|
+
if (preferred === 'top' && above < need && below > above) side = 'bottom';
|
|
63
|
+
if (preferred === 'bottom' && below < need && above > below) side = 'top';
|
|
64
|
+
scope.attr(root, 'data-mk-side', side);
|
|
65
|
+
// The panel starts at the root's left edge; move it back inside the viewport.
|
|
66
|
+
const left = anchor.left;
|
|
67
|
+
let shift = 0;
|
|
68
|
+
if (left + box.width > view.width - EDGE) shift = view.width - EDGE - box.width - left;
|
|
69
|
+
if (left + shift < EDGE) shift = EDGE - left;
|
|
70
|
+
if (shift) root.style.setProperty('--mk-hover-shift', `${Math.round(shift)}px`);
|
|
71
|
+
};
|
|
72
|
+
let pending: (() => void) | null = null;
|
|
73
|
+
const schedulePlace = () => {
|
|
74
|
+
if (!opened || pending) return;
|
|
75
|
+
pending = requestFrame(() => {
|
|
76
|
+
pending = null;
|
|
77
|
+
place();
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
scope.add(() => pending?.());
|
|
81
|
+
|
|
82
|
+
const cancel = () => { clearTimeout(timer); timer = undefined; };
|
|
83
|
+
const set = (value: boolean) => {
|
|
84
|
+
cancel();
|
|
85
|
+
if (scope.disposed) return;
|
|
86
|
+
const changed = value !== opened;
|
|
87
|
+
opened = value;
|
|
88
|
+
scope.attr(trigger, 'aria-expanded', String(value));
|
|
89
|
+
scope.attr(panel, 'hidden', value ? null : '');
|
|
90
|
+
scope.attr(root, 'data-mk-open', value ? '' : null);
|
|
91
|
+
if (value && changed) {
|
|
92
|
+
scope.attr(root, 'data-mk-input', pointerInput ? 'pointer' : 'keyboard');
|
|
93
|
+
place();
|
|
94
|
+
}
|
|
95
|
+
if (changed) {
|
|
96
|
+
options.onOpenChange?.(value);
|
|
97
|
+
root.dispatchEvent(new CustomEvent('mk:open-change', { bubbles: true, detail: { open: value } }));
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
const schedule = (value: boolean, delay: number) => { cancel(); timer = setTimeout(() => set(value), delay); };
|
|
101
|
+
scope.on(root, 'pointerenter', () => { pointerInside = true; pointerInput = true; schedule(true, openDelay); });
|
|
102
|
+
scope.on(root, 'pointerleave', () => {
|
|
103
|
+
pointerInside = false;
|
|
104
|
+
if (!root.contains(document.activeElement)) schedule(false, closeDelay);
|
|
105
|
+
});
|
|
106
|
+
scope.on(root, 'pointerdown', () => { pointerInput = true; });
|
|
107
|
+
scope.on(document, 'keydown', () => { pointerInput = false; }, true);
|
|
108
|
+
scope.on(root, 'focusin', () => set(true));
|
|
109
|
+
scope.on(root, 'focusout', (event) => {
|
|
110
|
+
if (!pointerInside && !root.contains((event as FocusEvent).relatedTarget as Node | null)) schedule(false, closeDelay);
|
|
111
|
+
});
|
|
112
|
+
scope.on(trigger, 'click', () => { pointerInput = true; set(true); });
|
|
113
|
+
scope.on(document, 'pointerdown', (event) => { if (!root.contains(event.target as Node)) set(false); });
|
|
114
|
+
scope.on(root, 'keydown', (event) => {
|
|
115
|
+
if (event.key !== 'Escape' || !opened) return;
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
trigger.focus();
|
|
118
|
+
set(false);
|
|
119
|
+
});
|
|
120
|
+
scope.on(window, 'resize', schedulePlace);
|
|
121
|
+
scope.on(window, 'scroll', schedulePlace, { capture: true, passive: true });
|
|
122
|
+
scope.add(cancel);
|
|
123
|
+
set(false);
|
|
124
|
+
return {
|
|
125
|
+
open: () => set(true),
|
|
126
|
+
close: () => set(false),
|
|
127
|
+
reposition: place,
|
|
128
|
+
get isOpen() { return opened; },
|
|
129
|
+
destroy: () => scope.dispose(),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
.mk-hover-card, mk-hover-card { position: relative; display: inline-flex; }
|
|
2
|
+
.mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] {
|
|
3
|
+
transform-origin: 50% 100%;
|
|
4
|
+
position: absolute; z-index: 10; bottom: calc(100% + .75rem); left: var(--mk-hover-shift, 0);
|
|
5
|
+
width: min(19rem, calc(100vw - 1.5rem)); padding: 1.25rem; border-radius: var(--mk-radius-lg);
|
|
6
|
+
color: var(--mk-on-glass-inverse); background: var(--mk-glass-inverse); border: 0; border-radius: var(--mk-radius-lg, 24px);
|
|
7
|
+
box-shadow: var(--mk-glass-bevel); backdrop-filter: blur(16px);
|
|
8
|
+
/* Melonmind animation-general-in-2: blur 32 → 0, scale .7 → 1, 300 ms. */
|
|
9
|
+
animation: mk-hover-card-in .3s cubic-bezier(.1,.8,0,1.1) both;
|
|
10
|
+
}
|
|
11
|
+
/* Bridge over the gap so the pointer can travel into the panel. */
|
|
12
|
+
.mk-hover-card [data-mk-hover-panel]::after, mk-hover-card [data-mk-hover-panel]::after { content: ''; position: absolute; inset: -1rem 0; z-index: -1; }
|
|
13
|
+
:is(.mk-hover-card, mk-hover-card)[data-mk-side=bottom] [data-mk-hover-panel] { top: calc(100% + .75rem); bottom: auto; transform-origin: 50% 0; animation-name: mk-hover-card-down; }
|
|
14
|
+
/* Opened from the keyboard: no entrance. */
|
|
15
|
+
:is(.mk-hover-card, mk-hover-card)[data-mk-input=keyboard] [data-mk-hover-panel] { animation: none; }
|
|
16
|
+
.mk-hover-card [hidden], mk-hover-card [hidden] { display: none !important; }
|
|
17
|
+
@keyframes mk-hover-card-in { from { opacity: 0; filter: blur(32px); transform: translateY(8px) scale(.7); } to { opacity: 1; filter: none; transform: none; } }
|
|
18
|
+
@keyframes mk-hover-card-down { from { opacity: 0; filter: blur(32px); transform: translateY(-8px) scale(.7); } to { opacity: 1; filter: none; transform: none; } }
|
|
19
|
+
@media (prefers-reduced-motion: reduce) { .mk-hover-card [data-mk-hover-panel], mk-hover-card [data-mk-hover-panel] { animation: none; } }
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useRef, type HTMLAttributes, type ReactNode } from 'react';
|
|
3
|
+
import { useIsomorphicLayoutEffect, useLatest } from '../../react/use-recipe';
|
|
4
|
+
import { createHoverCard, type HoverCardOptions } from './engine';
|
|
5
|
+
export interface HoverCardProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange'>, HoverCardOptions {
|
|
6
|
+
trigger: ReactNode; children: ReactNode; triggerLabel?: string; panelLabel?: string;
|
|
7
|
+
}
|
|
8
|
+
export function HoverCard({ trigger, children, triggerLabel, panelLabel = 'Vista previa', openDelay, closeDelay, placement, onOpenChange, className, ...props }: HoverCardProps) {
|
|
9
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
10
|
+
const change = useLatest(onOpenChange);
|
|
11
|
+
useIsomorphicLayoutEffect(() => {
|
|
12
|
+
const instance = createHoverCard(ref.current, { openDelay, closeDelay, placement, onOpenChange: (n) => change.current?.(n) });
|
|
13
|
+
return () => instance.destroy();
|
|
14
|
+
}, [openDelay, closeDelay, placement]);
|
|
15
|
+
return <div {...props} ref={ref} className={['mk-hover-card', className].filter(Boolean).join(' ')}>
|
|
16
|
+
<button type="button" className="mk-button mk-button--tonal" data-mk-hover-trigger aria-label={triggerLabel} aria-expanded="false">{trigger}</button>
|
|
17
|
+
<div data-mk-hover-panel role="region" aria-label={panelLabel} hidden>{children}</div>
|
|
18
|
+
</div>;
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# image-preview
|
|
2
|
+
|
|
3
|
+
Visor de imágenes tipo diálogo: las miniaturas `[data-mk-image]` abren un `<dialog>` modal donde la imagen crece desde su miniatura (0.7 s, curva firma; el radio de la miniatura pasa a 24 px) sobre un fondo oscuro desenfocado. Anterior/siguiente (botones, flechas o deslizar) entran con desplazamiento y blur, y el contador rueda. Doble clic o doble toque hace zoom ×2 donde apuntas y arrastrar lo mueve. Arrastrar hacia abajo, Escape o cerrar devuelven la imagen a su miniatura y el foco a ella.
|
|
4
|
+
|
|
5
|
+
```html
|
|
6
|
+
<mk-image-preview class="mk-image-preview">
|
|
7
|
+
<button data-mk-image data-mk-src="/big/1.jpg" data-mk-caption="Atardecer"><img src="/thumb/1.jpg" alt="Atardecer en la sierra"></button>
|
|
8
|
+
<button data-mk-image data-mk-src="/big/2.jpg"><img src="/thumb/2.jpg" alt="Lago"></button>
|
|
9
|
+
</mk-image-preview>
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`defineElements()`, `createImagePreview(root, options)` o `<ImagePreview images={[{ src, thumb, alt, caption }]} />`. La imagen grande sale de `data-mk-src`, el `href` o el `src` de la miniatura; el pie, de `data-mk-caption`.
|
|
13
|
+
|
|
14
|
+
Opciones: `labels {close, prev, next, dialog}`, `loop=true`, `onChange(index)`, `onOpenChange(open)`. API: `open(index)`, `close()`, `next()`, `prev()`, `isOpen`, `index`, `destroy()`. Eventos `mk:open`, `mk:change` `{index, previous}` y `mk:close`. Miniaturas que no son botones reciben `tabindex` y `role="button"` (Enter o Espacio abren).
|
|
15
|
+
|
|
16
|
+
Movimiento reducido: abre, cambia y cierra sin crecimiento ni deslizamientos.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { MkElement, defineElement, type OptionType } from '../../elements/base';
|
|
2
|
+
import { createImagePreview, type ImagePreviewInstance } from './engine';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* <mk-image-preview class="mk-image-preview">
|
|
6
|
+
* <button data-mk-image data-mk-src="/big/1.jpg" data-mk-caption="Atardecer"><img src="/thumb/1.jpg" alt="Atardecer en la sierra"></button>
|
|
7
|
+
* </mk-image-preview>
|
|
8
|
+
* Emits mk:open, mk:change ({index, previous}) and mk:close.
|
|
9
|
+
*/
|
|
10
|
+
export class MkImagePreviewElement extends MkElement<ImagePreviewInstance> {
|
|
11
|
+
static options: Record<string, OptionType> = { loop: 'boolean', labels: 'json' };
|
|
12
|
+
|
|
13
|
+
protected create(options: Record<string, unknown>) {
|
|
14
|
+
return createImagePreview(this, options);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
open(index?: number) {
|
|
18
|
+
this.instance?.open(index);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
close() {
|
|
22
|
+
this.instance?.close();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const defineImagePreview = () => defineElement('mk-image-preview', MkImagePreviewElement);
|