@svelte-atoms/core 1.0.0-alpha.25 → 1.0.0-alpha.27
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 +645 -645
- package/dist/components/accordion/accordion-root.svelte +61 -79
- package/dist/components/accordion/accordion-root.svelte.d.ts +2 -15
- package/dist/components/accordion/index.d.ts +2 -1
- package/dist/components/accordion/index.js +2 -1
- package/dist/components/accordion/item/accordion-item-body.svelte +42 -52
- package/dist/components/accordion/item/accordion-item-body.svelte.d.ts +2 -8
- package/dist/components/accordion/item/accordion-item-header.svelte +50 -56
- package/dist/components/accordion/item/accordion-item-header.svelte.d.ts +3 -20
- package/dist/components/accordion/item/accordion-item-indicator.svelte +50 -59
- package/dist/components/accordion/item/accordion-item-indicator.svelte.d.ts +2 -8
- package/dist/components/accordion/item/accordion-item-root.svelte +65 -79
- package/dist/components/accordion/item/accordion-item-root.svelte.d.ts +2 -12
- package/dist/components/accordion/item/index.d.ts +1 -0
- package/dist/components/accordion/item/types.d.ts +52 -0
- package/dist/components/accordion/item/types.js +1 -0
- package/dist/components/accordion/types.d.ts +21 -0
- package/dist/components/accordion/types.js +1 -0
- package/dist/components/alert/alert-actions.svelte +42 -52
- package/dist/components/alert/alert-actions.svelte.d.ts +3 -30
- package/dist/components/alert/alert-close-button.svelte +72 -79
- package/dist/components/alert/alert-close-button.svelte.d.ts +8 -35
- package/dist/components/alert/alert-content.svelte +42 -52
- package/dist/components/alert/alert-content.svelte.d.ts +3 -30
- package/dist/components/alert/alert-description.svelte +41 -51
- package/dist/components/alert/alert-description.svelte.d.ts +7 -10
- package/dist/components/alert/alert-icon.svelte +46 -56
- package/dist/components/alert/alert-icon.svelte.d.ts +2 -8
- package/dist/components/alert/alert-root.svelte +102 -118
- package/dist/components/alert/alert-root.svelte.d.ts +2 -13
- package/dist/components/alert/alert-title.svelte +41 -51
- package/dist/components/alert/alert-title.svelte.d.ts +2 -8
- package/dist/components/alert/index.d.ts +1 -0
- package/dist/components/alert/index.js +1 -0
- package/dist/components/alert/types.d.ts +85 -0
- package/dist/components/alert/types.js +1 -0
- package/dist/components/atom/html-atom.svelte +201 -217
- package/dist/components/atom/html-atom.svelte.d.ts +2 -22
- package/dist/components/atom/types.d.ts +7 -2
- package/dist/components/avatar/types.d.ts +7 -2
- package/dist/components/badge/badge.svelte +1 -1
- package/dist/components/badge/types.d.ts +7 -2
- package/dist/components/breadcrumb/breadcrumb-item.svelte +1 -1
- package/dist/components/breadcrumb/breadcrumb-root.svelte +1 -1
- package/dist/components/breadcrumb/breadcrumb-separator.svelte +1 -1
- package/dist/components/button/button.stories.svelte +57 -57
- package/dist/components/button/button.svelte +1 -1
- package/dist/components/button/button.svelte.d.ts +4 -1
- package/dist/components/button/index.d.ts +1 -0
- package/dist/components/button/index.js +1 -0
- package/dist/components/button/types.d.ts +8 -3
- package/dist/components/card/card-body.svelte +39 -45
- package/dist/components/card/card-body.svelte.d.ts +7 -4
- package/dist/components/card/card-description.svelte +41 -48
- package/dist/components/card/card-description.svelte.d.ts +7 -7
- package/dist/components/card/card-footer.svelte +41 -48
- package/dist/components/card/card-footer.svelte.d.ts +7 -4
- package/dist/components/card/card-header.svelte +41 -48
- package/dist/components/card/card-header.svelte.d.ts +7 -4
- package/dist/components/card/card-media.svelte +41 -48
- package/dist/components/card/card-media.svelte.d.ts +7 -4
- package/dist/components/card/card-root.svelte +91 -91
- package/dist/components/card/card-root.svelte.d.ts +1 -1
- package/dist/components/card/card-subtitle.svelte +41 -48
- package/dist/components/card/card-subtitle.svelte.d.ts +12 -9
- package/dist/components/card/card-title.svelte +45 -48
- package/dist/components/card/card-title.svelte.d.ts +12 -9
- package/dist/components/card/index.d.ts +1 -0
- package/dist/components/card/index.js +1 -0
- package/dist/components/card/types.d.ts +57 -2
- package/dist/components/checkbox/checkbox.svelte +39 -28
- package/dist/components/checkbox/types.d.ts +7 -2
- package/dist/components/collapsible/collapsible-body.svelte +39 -52
- package/dist/components/collapsible/collapsible-body.svelte.d.ts +2 -9
- package/dist/components/collapsible/collapsible-header.svelte +39 -52
- package/dist/components/collapsible/collapsible-header.svelte.d.ts +2 -9
- package/dist/components/collapsible/collapsible-indicator.svelte +50 -65
- package/dist/components/collapsible/collapsible-indicator.svelte.d.ts +3 -10
- package/dist/components/collapsible/collapsible-root.svelte +66 -85
- package/dist/components/collapsible/collapsible-root.svelte.d.ts +2 -14
- package/dist/components/collapsible/index.d.ts +1 -0
- package/dist/components/collapsible/index.js +1 -0
- package/dist/components/collapsible/types.d.ts +54 -0
- package/dist/components/collapsible/types.js +1 -0
- package/dist/components/combobox/atoms.d.ts +5 -1
- package/dist/components/combobox/atoms.js +5 -1
- package/dist/components/combobox/{combobox-input.svelte → combobox-control.svelte} +3 -3
- package/dist/components/combobox/{combobox-input.svelte.d.ts → combobox-control.svelte.d.ts} +3 -3
- package/dist/components/combobox/combobox-root.svelte +65 -68
- package/dist/components/combobox/combobox-root.svelte.d.ts +5 -18
- package/dist/components/combobox/combobox-trigger.svelte +1 -1
- package/dist/components/combobox/compobox-item.svelte +1 -1
- package/dist/components/combobox/index.d.ts +1 -0
- package/dist/components/combobox/index.js +1 -0
- package/dist/components/combobox/types.d.ts +25 -0
- package/dist/components/combobox/types.js +1 -0
- package/dist/components/container/container.svelte +1 -1
- package/dist/components/container/types.d.ts +7 -2
- package/dist/components/contextmenu/types.d.ts +8 -0
- package/dist/components/contextmenu/types.js +1 -0
- package/dist/components/datagrid/datagrid-body.svelte +37 -44
- package/dist/components/datagrid/datagrid-body.svelte.d.ts +17 -20
- package/dist/components/datagrid/datagrid-checkbox.svelte +101 -108
- package/dist/components/datagrid/datagrid-checkbox.svelte.d.ts +4 -6
- package/dist/components/datagrid/datagrid-footer.svelte +34 -34
- package/dist/components/datagrid/datagrid-footer.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid-header.svelte +49 -49
- package/dist/components/datagrid/datagrid-header.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid-root.svelte +59 -59
- package/dist/components/datagrid/datagrid-root.svelte.d.ts +1 -1
- package/dist/components/datagrid/datagrid.stories.svelte +75 -75
- package/dist/components/datagrid/td/datagrid-td.svelte +66 -80
- package/dist/components/datagrid/td/datagrid-td.svelte.d.ts +7 -16
- package/dist/components/datagrid/th/datagrid-th-sort-icon.svelte +1 -1
- package/dist/components/datagrid/th/datagrid-th.svelte +106 -127
- package/dist/components/datagrid/th/datagrid-th.svelte.d.ts +2 -20
- package/dist/components/datagrid/tr/bond.svelte.d.ts +3 -1
- package/dist/components/datagrid/tr/bond.svelte.js +4 -2
- package/dist/components/datagrid/tr/datagrid-tr.svelte +88 -103
- package/dist/components/datagrid/tr/datagrid-tr.svelte.d.ts +2 -18
- package/dist/components/datagrid/types.d.ts +85 -37
- package/dist/components/dialog/dialog-body.svelte +39 -45
- package/dist/components/dialog/dialog-body.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-close-button.svelte +58 -61
- package/dist/components/dialog/dialog-close-button.svelte.d.ts +7 -7
- package/dist/components/dialog/dialog-content.svelte +62 -71
- package/dist/components/dialog/dialog-content.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-description.svelte +40 -46
- package/dist/components/dialog/dialog-description.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-footer.svelte +39 -45
- package/dist/components/dialog/dialog-footer.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-header.svelte +39 -45
- package/dist/components/dialog/dialog-header.svelte.d.ts +2 -2
- package/dist/components/dialog/dialog-root.svelte +110 -120
- package/dist/components/dialog/dialog-root.svelte.d.ts +2 -10
- package/dist/components/dialog/dialog-title.svelte +41 -47
- package/dist/components/dialog/dialog-title.svelte.d.ts +7 -7
- package/dist/components/dialog/index.d.ts +1 -0
- package/dist/components/dialog/index.js +1 -0
- package/dist/components/dialog/types.d.ts +67 -0
- package/dist/components/dialog/types.js +1 -0
- package/dist/components/divider/types.d.ts +10 -0
- package/dist/components/divider/types.js +1 -0
- package/dist/components/drawer/drawer-backdrop.svelte +38 -47
- package/dist/components/drawer/drawer-backdrop.svelte.d.ts +3 -26
- package/dist/components/drawer/drawer-body.svelte +42 -56
- package/dist/components/drawer/drawer-body.svelte.d.ts +3 -16
- package/dist/components/drawer/drawer-content.svelte +42 -55
- package/dist/components/drawer/drawer-content.svelte.d.ts +3 -14
- package/dist/components/drawer/drawer-description.svelte +44 -57
- package/dist/components/drawer/drawer-description.svelte.d.ts +3 -14
- package/dist/components/drawer/drawer-footer.svelte +41 -54
- package/dist/components/drawer/drawer-footer.svelte.d.ts +3 -14
- package/dist/components/drawer/drawer-header.svelte +43 -56
- package/dist/components/drawer/drawer-header.svelte.d.ts +3 -14
- package/dist/components/drawer/drawer-root.svelte +93 -113
- package/dist/components/drawer/drawer-root.svelte.d.ts +3 -31
- package/dist/components/drawer/drawer-title.svelte +44 -57
- package/dist/components/drawer/drawer-title.svelte.d.ts +3 -14
- package/dist/components/drawer/index.d.ts +1 -0
- package/dist/components/drawer/index.js +1 -0
- package/dist/components/drawer/types.d.ts +86 -0
- package/dist/components/drawer/types.js +1 -0
- package/dist/components/dropdown/dropdown-placeholder.svelte +1 -1
- package/dist/components/dropdown/dropdown-query.svelte +54 -53
- package/dist/components/dropdown/dropdown-query.svelte.d.ts +11 -10
- package/dist/components/dropdown/dropdown-root.svelte +59 -59
- package/dist/components/dropdown/dropdown-trigger.svelte +41 -52
- package/dist/components/dropdown/dropdown-trigger.svelte.d.ts +1 -8
- package/dist/components/dropdown/dropdown-value.svelte +60 -62
- package/dist/components/dropdown/index.d.ts +1 -0
- package/dist/components/dropdown/index.js +1 -0
- package/dist/components/dropdown/item/bond.svelte.d.ts +4 -0
- package/dist/components/dropdown/item/bond.svelte.js +9 -0
- package/dist/components/dropdown/item/dropdown-item.svelte +10 -6
- package/dist/components/dropdown/types.d.ts +37 -0
- package/dist/components/dropdown/types.js +1 -0
- package/dist/components/element/html-element.svelte.d.ts +2 -14
- package/dist/components/element/svg-element.svelte.d.ts +2 -14
- package/dist/components/element/types.d.ts +14 -7
- package/dist/components/form/field/bond.svelte.d.ts +8 -0
- package/dist/components/form/field/bond.svelte.js +13 -1
- package/dist/components/form/field/field-control.svelte +48 -58
- package/dist/components/form/field/field-control.svelte.d.ts +5 -19
- package/dist/components/form/field/field-label.svelte +24 -31
- package/dist/components/form/field/field-label.svelte.d.ts +1 -2
- package/dist/components/form/field/field-root.svelte +59 -88
- package/dist/components/form/field/field-root.svelte.d.ts +5 -20
- package/dist/components/form/form.stories.svelte +3 -3
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/types.d.ts +76 -0
- package/dist/components/form/types.js +1 -0
- package/dist/components/icon/icon.svelte +44 -55
- package/dist/components/icon/icon.svelte.d.ts +4 -29
- package/dist/components/icon/types.d.ts +11 -7
- package/dist/components/input/atoms.d.ts +5 -1
- package/dist/components/input/atoms.js +5 -1
- package/dist/components/input/index.d.ts +1 -0
- package/dist/components/input/index.js +1 -0
- package/dist/components/input/{input-value.svelte → input-control.svelte} +14 -24
- package/dist/components/input/input-control.svelte.d.ts +26 -0
- package/dist/components/input/input-icon.svelte +1 -1
- package/dist/components/input/input-icon.svelte.d.ts +1 -1
- package/dist/components/input/input-placeholder.svelte +54 -56
- package/dist/components/input/input-placeholder.svelte.d.ts +2 -19
- package/dist/components/input/input-root.svelte +5 -12
- package/dist/components/input/input-root.svelte.d.ts +3 -20
- package/dist/components/input/input.stories.svelte +2 -2
- package/dist/components/input/types.d.ts +33 -0
- package/dist/components/input/types.js +1 -0
- package/dist/components/label/index.d.ts +1 -0
- package/dist/components/label/index.js +1 -0
- package/dist/components/label/label.svelte +25 -41
- package/dist/components/label/label.svelte.d.ts +3 -27
- package/dist/components/label/types.d.ts +11 -0
- package/dist/components/label/types.js +1 -0
- package/dist/components/layer/layer-inner.svelte.d.ts +2 -19
- package/dist/components/layer/layer-root.svelte.d.ts +2 -19
- package/dist/components/layer/types.d.ts +11 -0
- package/dist/components/layer/types.js +1 -0
- package/dist/components/link/types.d.ts +8 -0
- package/dist/components/link/types.js +1 -0
- package/dist/components/list/list-group.svelte +1 -1
- package/dist/components/list/list-item.svelte +1 -1
- package/dist/components/list/list-root.svelte +6 -1
- package/dist/components/list/list-title.svelte +1 -1
- package/dist/components/list/types.d.ts +8 -0
- package/dist/components/list/types.js +1 -0
- package/dist/components/menu/index.d.ts +1 -0
- package/dist/components/menu/index.js +1 -0
- package/dist/components/menu/menu-list.svelte +1 -1
- package/dist/components/menu/types.d.ts +15 -0
- package/dist/components/menu/types.js +1 -0
- package/dist/components/popover/bond.svelte.d.ts +2 -0
- package/dist/components/popover/bond.svelte.js +1 -1
- package/dist/components/popover/index.d.ts +1 -0
- package/dist/components/popover/index.js +1 -0
- package/dist/components/popover/popover-arrow.svelte +111 -117
- package/dist/components/popover/popover-arrow.svelte.d.ts +3 -20
- package/dist/components/popover/popover-content.svelte +139 -147
- package/dist/components/popover/popover-content.svelte.d.ts +3 -17
- package/dist/components/popover/popover-indicator.svelte +1 -1
- package/dist/components/popover/popover-root.svelte +4 -18
- package/dist/components/popover/popover-root.svelte.d.ts +1 -15
- package/dist/components/popover/popover-trigger.svelte +3 -12
- package/dist/components/popover/popover-trigger.svelte.d.ts +2 -8
- package/dist/components/popover/types.d.ts +61 -0
- package/dist/components/popover/types.js +1 -0
- package/dist/components/portal/active-portal.svelte +8 -2
- package/dist/components/portal/active-portal.svelte.d.ts +2 -2
- package/dist/components/portal/index.d.ts +1 -0
- package/dist/components/portal/index.js +1 -0
- package/dist/components/portal/portal-inner.svelte +1 -1
- package/dist/components/portal/portal-inner.svelte.d.ts +2 -19
- package/dist/components/portal/portal-root.svelte +83 -88
- package/dist/components/portal/portal-root.svelte.d.ts +2 -22
- package/dist/components/portal/teleport.svelte +50 -49
- package/dist/components/portal/teleport.svelte.d.ts +5 -23
- package/dist/components/portal/types.d.ts +39 -0
- package/dist/components/portal/types.js +1 -0
- package/dist/components/radio/radio-group.stories.svelte +4 -4
- package/dist/components/radio/radio.svelte +109 -109
- package/dist/components/radio/radio.svelte.d.ts +14 -36
- package/dist/components/root/root.css +24 -66
- package/dist/components/root/root.svelte +121 -121
- package/dist/components/root/types.d.ts +8 -0
- package/dist/components/root/types.js +1 -0
- package/dist/components/scrollable/index.d.ts +1 -0
- package/dist/components/scrollable/index.js +1 -0
- package/dist/components/scrollable/scrollable-container.svelte +82 -89
- package/dist/components/scrollable/scrollable-container.svelte.d.ts +2 -6
- package/dist/components/scrollable/scrollable-content.svelte +41 -51
- package/dist/components/scrollable/scrollable-content.svelte.d.ts +1 -6
- package/dist/components/scrollable/scrollable-root.svelte +100 -120
- package/dist/components/scrollable/scrollable-root.svelte.d.ts +3 -19
- package/dist/components/scrollable/scrollable-thumb.svelte +75 -86
- package/dist/components/scrollable/scrollable-thumb.svelte.d.ts +1 -7
- package/dist/components/scrollable/scrollable-track.svelte +59 -70
- package/dist/components/scrollable/scrollable-track.svelte.d.ts +1 -7
- package/dist/components/scrollable/types.d.ts +62 -0
- package/dist/components/scrollable/types.js +1 -0
- package/dist/components/sidebar/index.d.ts +1 -0
- package/dist/components/sidebar/index.js +1 -0
- package/dist/components/sidebar/sidebar-content.svelte +2 -16
- package/dist/components/sidebar/sidebar-content.svelte.d.ts +2 -9
- package/dist/components/sidebar/sidebar-root.svelte +4 -23
- package/dist/components/sidebar/sidebar-root.svelte.d.ts +2 -13
- package/dist/components/sidebar/types.d.ts +30 -0
- package/dist/components/sidebar/types.js +1 -0
- package/dist/components/stack/stack-item.svelte +5 -1
- package/dist/components/stack/stack-root.svelte +5 -1
- package/dist/components/stack/stack-root.svelte.d.ts +2 -19
- package/dist/components/stack/types.d.ts +12 -0
- package/dist/components/stack/types.js +1 -0
- package/dist/components/tabs/index.d.ts +1 -0
- package/dist/components/tabs/index.js +1 -0
- package/dist/components/tabs/tab/tab-body.svelte +52 -61
- package/dist/components/tabs/tab/tab-body.svelte.d.ts +2 -8
- package/dist/components/tabs/tab/tab-description.svelte +41 -50
- package/dist/components/tabs/tab/tab-description.svelte.d.ts +2 -8
- package/dist/components/tabs/tab/tab-header.svelte +71 -81
- package/dist/components/tabs/tab/tab-header.svelte.d.ts +2 -11
- package/dist/components/tabs/tab/tab-root.svelte +86 -81
- package/dist/components/tabs/tabs-body.svelte +1 -1
- package/dist/components/tabs/tabs-header.svelte +1 -1
- package/dist/components/tabs/tabs-root.svelte +1 -1
- package/dist/components/tabs/types.d.ts +55 -0
- package/dist/components/tabs/types.js +1 -0
- package/dist/components/textarea/index.d.ts +1 -0
- package/dist/components/textarea/index.js +1 -0
- package/dist/components/textarea/textarea-input.svelte +2 -1
- package/dist/components/textarea/types.d.ts +28 -0
- package/dist/components/textarea/types.js +1 -0
- package/dist/components/toast/index.d.ts +1 -0
- package/dist/components/toast/index.js +1 -0
- package/dist/components/toast/toast-description.svelte +38 -44
- package/dist/components/toast/toast-description.svelte.d.ts +8 -34
- package/dist/components/toast/toast-root.svelte +61 -74
- package/dist/components/toast/toast-root.svelte.d.ts +4 -43
- package/dist/components/toast/toast-title.svelte +35 -43
- package/dist/components/toast/toast-title.svelte.d.ts +2 -34
- package/dist/components/toast/types.d.ts +40 -0
- package/dist/components/toast/types.js +1 -0
- package/dist/components/tooltip/types.d.ts +13 -0
- package/dist/components/tooltip/types.js +1 -0
- package/dist/components/tree/index.d.ts +1 -0
- package/dist/components/tree/index.js +1 -0
- package/dist/components/tree/tree-body.svelte +39 -50
- package/dist/components/tree/tree-body.svelte.d.ts +2 -10
- package/dist/components/tree/tree-header.svelte +54 -66
- package/dist/components/tree/tree-header.svelte.d.ts +3 -29
- package/dist/components/tree/tree-indicator.svelte +40 -50
- package/dist/components/tree/tree-indicator.svelte.d.ts +3 -9
- package/dist/components/tree/tree-root.svelte +65 -80
- package/dist/components/tree/tree-root.svelte.d.ts +2 -12
- package/dist/components/tree/types.d.ts +59 -0
- package/dist/components/tree/types.js +1 -0
- package/dist/components/virtual/types.d.ts +23 -0
- package/dist/components/virtual/types.js +1 -0
- package/dist/components/virtual/virtual-root.svelte +239 -258
- package/dist/components/virtual/virtual-root.svelte.d.ts +1 -18
- package/dist/context/preset.svelte.d.ts +1 -1
- package/llm/composition.md +395 -395
- package/llm/crafting.md +838 -838
- package/llm/motion.md +970 -970
- package/llm/philosophy.md +23 -23
- package/llm/preset-variant-integration.md +516 -516
- package/llm/preset.md +383 -383
- package/llm/styling.md +216 -216
- package/llm/usage.md +46 -46
- package/llm/variants.md +712 -712
- package/package.json +437 -437
- package/dist/components/input/input-value.svelte.d.ts +0 -19
package/llm/variants.md
CHANGED
|
@@ -1,712 +1,712 @@
|
|
|
1
|
-
# Variant System
|
|
2
|
-
|
|
3
|
-
## Overview
|
|
4
|
-
|
|
5
|
-
The variant system in @svelte-atoms/core provides a powerful way to define component styling variations. It's deeply integrated with the preset system, allowing both global theming and local customization.
|
|
6
|
-
|
|
7
|
-
## Problem Statement
|
|
8
|
-
|
|
9
|
-
Currently, creating component variants (size, variant, appearance, etc.) requires:
|
|
10
|
-
|
|
11
|
-
```svelte
|
|
12
|
-
<script>
|
|
13
|
-
let { variant = 'primary', size = 'md' } = $props();
|
|
14
|
-
|
|
15
|
-
const variantClasses = {
|
|
16
|
-
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
17
|
-
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
18
|
-
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const sizeClasses = {
|
|
22
|
-
sm: 'px-2 py-1 text-sm',
|
|
23
|
-
md: 'px-4 py-2 text-base',
|
|
24
|
-
lg: 'px-6 py-3 text-lg'
|
|
25
|
-
};
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<HtmlAtom class={`${variantClasses[variant]} ${sizeClasses[size]}`} />
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
**Pain points:**
|
|
32
|
-
|
|
33
|
-
- Repeated boilerplate in every component
|
|
34
|
-
- No access to component state (bond) for reactive variants
|
|
35
|
-
- Can't return attributes, only classes
|
|
36
|
-
- Not type-safe
|
|
37
|
-
|
|
38
|
-
## Integration with Preset System
|
|
39
|
-
|
|
40
|
-
**Key Feature:** Variants are now integrated with the preset system, enabling global variant definitions that can be overridden locally.
|
|
41
|
-
|
|
42
|
-
### Preset Structure
|
|
43
|
-
|
|
44
|
-
Presets support the full variant structure for comprehensive theming:
|
|
45
|
-
|
|
46
|
-
```typescript
|
|
47
|
-
// Preset: Full variant support
|
|
48
|
-
{
|
|
49
|
-
class: 'base-classes',
|
|
50
|
-
variants: {
|
|
51
|
-
variant: { primary: '...', secondary: '...' },
|
|
52
|
-
size: { sm: '...', md: '...', lg: '...' }
|
|
53
|
-
},
|
|
54
|
-
compounds: [
|
|
55
|
-
{ variant: 'primary', size: 'lg', class: 'shadow-lg' }
|
|
56
|
-
],
|
|
57
|
-
defaults: {
|
|
58
|
-
variant: 'primary',
|
|
59
|
-
size: 'md'
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Component: Same structure, overrides/extends preset
|
|
64
|
-
{
|
|
65
|
-
class: 'component-base',
|
|
66
|
-
variants: { ... },
|
|
67
|
-
compoundss: [...],
|
|
68
|
-
defaults: { ... }
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
**Merge Behavior:**
|
|
73
|
-
|
|
74
|
-
- `variants`: Deep merged (component extends preset)
|
|
75
|
-
- `compounds`: Concatenated (preset first, then component compounds)
|
|
76
|
-
- `defaults`: Deep merged (component overrides preset)
|
|
77
|
-
|
|
78
|
-
### Architecture
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
Preset (Global Theme)
|
|
82
|
-
├─ class: ClassValue (base styling)
|
|
83
|
-
├─ variants: Record<string, Record<string, ClassValue>>
|
|
84
|
-
│ ├─ variant: { primary: '...', secondary: '...' }
|
|
85
|
-
│ └─ size: { sm: '...', md: '...', lg: '...' }
|
|
86
|
-
├─ compounds: Array<CompoundVariant> (conditional styling)
|
|
87
|
-
├─ defaults: Record<string, string> (default values)
|
|
88
|
-
├─ base: Component (component override)
|
|
89
|
-
├─ as: string (element type)
|
|
90
|
-
└─ ...other props
|
|
91
|
-
|
|
92
|
-
Component (Local)
|
|
93
|
-
├─ variants: VariantDefinition (extends/overrides preset)
|
|
94
|
-
│ ├─ class: ClassValue (component-specific base)
|
|
95
|
-
│ ├─ variants: { ... } (extends/overrides preset variants)
|
|
96
|
-
│ ├─ compounds: [...] (appended to preset)
|
|
97
|
-
│ └─ defaults: {...} (overrides preset defaults)
|
|
98
|
-
└─ variant props (size, variant, etc.)
|
|
99
|
-
|
|
100
|
-
Final Output = merge(preset, component)
|
|
101
|
-
- variants: deep merged
|
|
102
|
-
- compounds: concatenated (preset + component)
|
|
103
|
-
- defaults: deep merged (component overrides)
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
### Hierarchy
|
|
107
|
-
|
|
108
|
-
The system merges variants in this order (later overrides earlier):
|
|
109
|
-
|
|
110
|
-
1. **Preset variants** - Global theme variants
|
|
111
|
-
2. **Component variants** - Local component-specific variants
|
|
112
|
-
3. **Props** - Runtime variant prop values
|
|
113
|
-
|
|
114
|
-
### Example: Global + Local Variants
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
// +layout.svelte - Global theme
|
|
118
|
-
import { setPreset } from '@svelte-atoms/core/context';
|
|
119
|
-
|
|
120
|
-
setPreset({
|
|
121
|
-
button: () => ({
|
|
122
|
-
class: 'font-medium transition-colors rounded-md focus:outline-none focus:ring-2',
|
|
123
|
-
variants: {
|
|
124
|
-
variant: {
|
|
125
|
-
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
126
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
|
|
127
|
-
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
|
|
128
|
-
},
|
|
129
|
-
size: {
|
|
130
|
-
sm: 'h-8 px-3 text-xs',
|
|
131
|
-
md: 'h-10 px-4 text-sm',
|
|
132
|
-
lg: 'h-12 px-6 text-base'
|
|
133
|
-
}
|
|
134
|
-
},
|
|
135
|
-
compounds: [
|
|
136
|
-
{
|
|
137
|
-
variant: 'primary',
|
|
138
|
-
size: 'lg',
|
|
139
|
-
class: 'shadow-lg font-semibold'
|
|
140
|
-
}
|
|
141
|
-
],
|
|
142
|
-
defaults: {
|
|
143
|
-
variant: 'primary',
|
|
144
|
-
size: 'md'
|
|
145
|
-
}
|
|
146
|
-
})
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
```svelte
|
|
151
|
-
<!-- Button.svelte - Local override -->
|
|
152
|
-
<script>
|
|
153
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
154
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
155
|
-
|
|
156
|
-
// Extend preset variants with additional local variants
|
|
157
|
-
const localVariants = defineVariants({
|
|
158
|
-
variants: {
|
|
159
|
-
variant: {
|
|
160
|
-
// Add new variant not in preset
|
|
161
|
-
ghost: 'hover:bg-accent hover:text-accent-foreground'
|
|
162
|
-
},
|
|
163
|
-
// Add new variant key
|
|
164
|
-
loading: {
|
|
165
|
-
true: 'opacity-50 cursor-wait',
|
|
166
|
-
false: ''
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
let {
|
|
172
|
-
variant = 'primary',
|
|
173
|
-
size = 'md',
|
|
174
|
-
loading = false,
|
|
175
|
-
...props
|
|
176
|
-
} = $props();
|
|
177
|
-
</script>
|
|
178
|
-
|
|
179
|
-
<HtmlAtom
|
|
180
|
-
preset="button" <!-- Uses global preset -->
|
|
181
|
-
variants={localVariants} <!-- Merges with preset -->
|
|
182
|
-
{variant}
|
|
183
|
-
{size}
|
|
184
|
-
{loading}
|
|
185
|
-
{...props}
|
|
186
|
-
>
|
|
187
|
-
{@render children?.()}
|
|
188
|
-
</HtmlAtom>
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
**Result:** The button will have:
|
|
192
|
-
|
|
193
|
-
- Base classes from preset
|
|
194
|
-
- Preset variant classes (primary, sm/md/lg)
|
|
195
|
-
- Local variant classes (ghost, loading)
|
|
196
|
-
- Merged intelligently - local overrides preset
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
200
|
-
|
|
201
|
-
const buttonVariants = defineVariants({
|
|
202
|
-
class: 'rounded-md font-medium transition-colors',
|
|
203
|
-
variants: {
|
|
204
|
-
variant: {
|
|
205
|
-
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
206
|
-
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
207
|
-
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
208
|
-
},
|
|
209
|
-
size: {
|
|
210
|
-
sm: 'px-2 py-1 text-sm',
|
|
211
|
-
md: 'px-4 py-2 text-base',
|
|
212
|
-
lg: 'px-6 py-3 text-lg'
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
compounds: [
|
|
216
|
-
{
|
|
217
|
-
variant: 'primary',
|
|
218
|
-
size: 'lg',
|
|
219
|
-
class: 'shadow-lg' // Applied when both conditions match
|
|
220
|
-
}
|
|
221
|
-
],
|
|
222
|
-
defaults: {
|
|
223
|
-
variant: 'primary',
|
|
224
|
-
size: 'md'
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
```svelte
|
|
230
|
-
<script>
|
|
231
|
-
import { buttonVariants } from './variants';
|
|
232
|
-
|
|
233
|
-
let { variant, size, ...props } = $props();
|
|
234
|
-
|
|
235
|
-
const variantProps = buttonVariants({ variant, size });
|
|
236
|
-
</script>
|
|
237
|
-
|
|
238
|
-
<HtmlAtom {...variantProps} {...props}>
|
|
239
|
-
{@render children?.()}
|
|
240
|
-
</HtmlAtom>
|
|
241
|
-
```
|
|
242
|
-
|
|
243
|
-
## Key Features
|
|
244
|
-
|
|
245
|
-
### 1. Access to Component State (Bond)
|
|
246
|
-
|
|
247
|
-
Variant values can be functions that receive the component's bond for reactive styling:
|
|
248
|
-
|
|
249
|
-
```typescript
|
|
250
|
-
const accordionVariants = defineVariants({
|
|
251
|
-
class: 'border rounded-md transition-all',
|
|
252
|
-
variants: {
|
|
253
|
-
state: {
|
|
254
|
-
open: (bond) => ({
|
|
255
|
-
class: bond?.state?.isOpen ? 'bg-blue-50 border-blue-200' : 'bg-white',
|
|
256
|
-
'aria-expanded': bond?.state?.isOpen,
|
|
257
|
-
'data-state': bond?.state?.isOpen ? 'open' : 'closed'
|
|
258
|
-
}),
|
|
259
|
-
disabled: (bond) => ({
|
|
260
|
-
class: bond?.state?.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
|
|
261
|
-
'aria-disabled': bond?.state?.disabled
|
|
262
|
-
})
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
});
|
|
266
|
-
|
|
267
|
-
// Usage:
|
|
268
|
-
const bond = AccordionBond.get();
|
|
269
|
-
const variantProps = accordionVariants(bond, { state: 'open' });
|
|
270
|
-
```
|
|
271
|
-
|
|
272
|
-
### 2. Return Both Classes and Attributes
|
|
273
|
-
|
|
274
|
-
Variants can return not just classes, but any HTML attributes:
|
|
275
|
-
|
|
276
|
-
```typescript
|
|
277
|
-
const buttonVariants = defineVariants({
|
|
278
|
-
variants: {
|
|
279
|
-
variant: {
|
|
280
|
-
primary: {
|
|
281
|
-
class: 'bg-blue-500 text-white',
|
|
282
|
-
'aria-label': 'Primary action',
|
|
283
|
-
'data-variant': 'primary'
|
|
284
|
-
},
|
|
285
|
-
danger: (bond) => ({
|
|
286
|
-
class: bond?.state?.disabled ? 'bg-red-300' : 'bg-red-500',
|
|
287
|
-
'aria-disabled': bond?.state?.disabled,
|
|
288
|
-
role: 'button'
|
|
289
|
-
})
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
// Returns: { class: '...', 'aria-label': '...', 'data-variant': '...', ... }
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### 3. Compound Variants
|
|
298
|
-
|
|
299
|
-
Apply additional styling when multiple conditions match:
|
|
300
|
-
|
|
301
|
-
```typescript
|
|
302
|
-
const alertVariants = defineVariants({
|
|
303
|
-
class: 'rounded-lg p-4 border',
|
|
304
|
-
variants: {
|
|
305
|
-
variant: {
|
|
306
|
-
error: 'bg-red-50 border-red-200 text-red-900'
|
|
307
|
-
},
|
|
308
|
-
size: {
|
|
309
|
-
lg: 'text-lg'
|
|
310
|
-
}
|
|
311
|
-
},
|
|
312
|
-
compounds: [
|
|
313
|
-
{
|
|
314
|
-
variant: 'error',
|
|
315
|
-
size: 'lg',
|
|
316
|
-
class: 'font-bold', // Only applied when both variant=error AND size=lg
|
|
317
|
-
role: 'alert'
|
|
318
|
-
}
|
|
319
|
-
]
|
|
320
|
-
});
|
|
321
|
-
```
|
|
322
|
-
|
|
323
|
-
### 4. Type Safety
|
|
324
|
-
|
|
325
|
-
Full TypeScript support with automatic type inference:
|
|
326
|
-
|
|
327
|
-
```typescript
|
|
328
|
-
type ButtonVariants = VariantPropsType<typeof buttonVariants>;
|
|
329
|
-
// Inferred type: { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg' }
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
### 5. Default Variants
|
|
333
|
-
|
|
334
|
-
Specify default values that are used when no variant is provided:
|
|
335
|
-
|
|
336
|
-
```typescript
|
|
337
|
-
const buttonVariants = defineVariants({
|
|
338
|
-
class: 'rounded-md',
|
|
339
|
-
variants: {
|
|
340
|
-
variant: {
|
|
341
|
-
primary: 'bg-blue-500',
|
|
342
|
-
secondary: 'bg-gray-500'
|
|
343
|
-
},
|
|
344
|
-
size: {
|
|
345
|
-
sm: 'text-sm',
|
|
346
|
-
md: 'text-base',
|
|
347
|
-
lg: 'text-lg'
|
|
348
|
-
}
|
|
349
|
-
},
|
|
350
|
-
defaults: {
|
|
351
|
-
variant: 'primary', // Used if variant prop is undefined
|
|
352
|
-
size: 'md' // Used if size prop is undefined
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Call without props - uses defaults
|
|
357
|
-
buttonVariants(null); // Returns variant='primary', size='md'
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
## Complete Examples
|
|
361
|
-
|
|
362
|
-
### Example 1: Preset-based Button (Recommended)
|
|
363
|
-
|
|
364
|
-
```typescript
|
|
365
|
-
// Theme.svelte - Define global button variants
|
|
366
|
-
import { setPreset } from '@svelte-atoms/core/context';
|
|
367
|
-
|
|
368
|
-
setPreset({
|
|
369
|
-
button: () => ({
|
|
370
|
-
class:
|
|
371
|
-
'inline-flex items-center justify-center font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
372
|
-
variants: {
|
|
373
|
-
variant: {
|
|
374
|
-
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
375
|
-
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
376
|
-
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
377
|
-
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
378
|
-
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
379
|
-
link: 'text-primary underline-offset-4 hover:underline'
|
|
380
|
-
},
|
|
381
|
-
size: {
|
|
382
|
-
default: 'h-10 px-4 py-2',
|
|
383
|
-
sm: 'h-9 rounded-md px-3',
|
|
384
|
-
lg: 'h-11 rounded-md px-8',
|
|
385
|
-
icon: 'h-10 w-10'
|
|
386
|
-
}
|
|
387
|
-
},
|
|
388
|
-
compounds: [
|
|
389
|
-
{
|
|
390
|
-
variant: 'default',
|
|
391
|
-
size: 'lg',
|
|
392
|
-
class: 'text-base font-semibold'
|
|
393
|
-
}
|
|
394
|
-
],
|
|
395
|
-
defaults: {
|
|
396
|
-
variant: 'default',
|
|
397
|
-
size: 'default'
|
|
398
|
-
}
|
|
399
|
-
})
|
|
400
|
-
});
|
|
401
|
-
```
|
|
402
|
-
|
|
403
|
-
```svelte
|
|
404
|
-
<!-- Button.svelte - Use preset variants -->
|
|
405
|
-
<script lang="ts">
|
|
406
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
407
|
-
|
|
408
|
-
type ButtonProps = {
|
|
409
|
-
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
410
|
-
size?: 'default' | 'sm' | 'lg' | 'icon';
|
|
411
|
-
disabled?: boolean;
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
|
|
415
|
-
</script>
|
|
416
|
-
|
|
417
|
-
<HtmlAtom preset="button" as="button" {variant} {size} {disabled} class={klass} {...props}>
|
|
418
|
-
{@render children?.()}
|
|
419
|
-
</HtmlAtom>
|
|
420
|
-
```
|
|
421
|
-
|
|
422
|
-
### Example 2: Local Variants Only
|
|
423
|
-
|
|
424
|
-
````svelte
|
|
425
|
-
### Example 3: Extending Preset with Local Variants
|
|
426
|
-
|
|
427
|
-
Combine global preset variants with component-specific variants:
|
|
428
|
-
|
|
429
|
-
```svelte
|
|
430
|
-
<!-- special-button.svelte -->
|
|
431
|
-
<script>
|
|
432
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
433
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
434
|
-
|
|
435
|
-
// Local variants that extend/override preset
|
|
436
|
-
const localVariants = defineVariants({
|
|
437
|
-
variants: {
|
|
438
|
-
variant: {
|
|
439
|
-
// Add new variants not in preset
|
|
440
|
-
gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
|
|
441
|
-
neon: 'bg-black text-neon-green border-2 border-neon-green'
|
|
442
|
-
},
|
|
443
|
-
// Add completely new variant key
|
|
444
|
-
animated: {
|
|
445
|
-
true: 'animate-pulse',
|
|
446
|
-
false: ''
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
let {
|
|
452
|
-
variant,
|
|
453
|
-
size,
|
|
454
|
-
animated = false,
|
|
455
|
-
...props
|
|
456
|
-
} = $props();
|
|
457
|
-
</script>
|
|
458
|
-
|
|
459
|
-
<HtmlAtom
|
|
460
|
-
preset="button" <!-- Gets base variants -->
|
|
461
|
-
variants={localVariants} <!-- Merges/extends -->
|
|
462
|
-
{variant}
|
|
463
|
-
{size}
|
|
464
|
-
{animated}
|
|
465
|
-
{...props}
|
|
466
|
-
>
|
|
467
|
-
{@render children?.()}
|
|
468
|
-
</HtmlAtom>
|
|
469
|
-
````
|
|
470
|
-
|
|
471
|
-
### Example 4: Reactive Variants with Bond State
|
|
472
|
-
|
|
473
|
-
<script>
|
|
474
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
475
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
476
|
-
|
|
477
|
-
const alertVariants = defineVariants({
|
|
478
|
-
class: 'rounded-lg p-4 border',
|
|
479
|
-
variants: {
|
|
480
|
-
variant: {
|
|
481
|
-
info: 'bg-blue-50 border-blue-200 text-blue-900',
|
|
482
|
-
success: 'bg-green-50 border-green-200 text-green-900',
|
|
483
|
-
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
|
|
484
|
-
error: 'bg-red-50 border-red-200 text-red-900'
|
|
485
|
-
},
|
|
486
|
-
size: {
|
|
487
|
-
sm: 'text-sm p-2',
|
|
488
|
-
md: 'text-base p-4',
|
|
489
|
-
lg: 'text-lg p-6'
|
|
490
|
-
}
|
|
491
|
-
},
|
|
492
|
-
compounds: [
|
|
493
|
-
{
|
|
494
|
-
variant: 'error',
|
|
495
|
-
size: 'lg',
|
|
496
|
-
class: 'font-bold',
|
|
497
|
-
role: 'alert',
|
|
498
|
-
'aria-live': 'assertive'
|
|
499
|
-
}
|
|
500
|
-
],
|
|
501
|
-
defaults: {
|
|
502
|
-
variant: 'info',
|
|
503
|
-
size: 'md'
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
let { variant, size, ...props } = $props();
|
|
508
|
-
|
|
509
|
-
const bond = null; // or get from context if needed
|
|
510
|
-
const variantProps = alertVariants(bond, { variant, size });
|
|
511
|
-
</script>
|
|
512
|
-
|
|
513
|
-
<HtmlAtom {...variantProps} {...props}>
|
|
514
|
-
{@render children?.()}
|
|
515
|
-
</HtmlAtom>
|
|
516
|
-
|
|
517
|
-
````
|
|
518
|
-
|
|
519
|
-
### Example 3: Accordion with Reactive Bond State
|
|
520
|
-
|
|
521
|
-
```svelte
|
|
522
|
-
<!-- accordion-item.svelte -->
|
|
523
|
-
<script>
|
|
524
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
525
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
526
|
-
import { AccordionBond } from './bond.svelte';
|
|
527
|
-
|
|
528
|
-
const accordionVariants = defineVariants({
|
|
529
|
-
class: 'border rounded-md transition-all duration-200',
|
|
530
|
-
variants: {
|
|
531
|
-
state: {
|
|
532
|
-
// Reactive: changes when bond.state.isOpen changes
|
|
533
|
-
open: (bond) => ({
|
|
534
|
-
class: bond?.state?.isOpen
|
|
535
|
-
? 'bg-blue-50 border-blue-200'
|
|
536
|
-
: 'bg-white border-gray-200',
|
|
537
|
-
'aria-expanded': bond?.state?.isOpen,
|
|
538
|
-
'data-state': bond?.state?.isOpen ? 'open' : 'closed'
|
|
539
|
-
}),
|
|
540
|
-
disabled: (bond) => ({
|
|
541
|
-
class: bond?.state?.disabled
|
|
542
|
-
? 'opacity-50 cursor-not-allowed'
|
|
543
|
-
: 'cursor-pointer',
|
|
544
|
-
'aria-disabled': bond?.state?.disabled
|
|
545
|
-
})
|
|
546
|
-
}
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
const bond = AccordionBond.get();
|
|
551
|
-
|
|
552
|
-
// Automatically reactive - updates when bond state changes
|
|
553
|
-
const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
|
|
554
|
-
</script>
|
|
555
|
-
|
|
556
|
-
<HtmlAtom {...variantProps}>
|
|
557
|
-
{@render children?.({ accordion: bond })}
|
|
558
|
-
</HtmlAtom>
|
|
559
|
-
````
|
|
560
|
-
|
|
561
|
-
## Migration Guide
|
|
562
|
-
|
|
563
|
-
### Before: Manual Variant Management
|
|
564
|
-
|
|
565
|
-
```svelte
|
|
566
|
-
<script>
|
|
567
|
-
let { variant = 'primary', size = 'md' } = $props();
|
|
568
|
-
|
|
569
|
-
const variantClasses = {
|
|
570
|
-
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
571
|
-
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
572
|
-
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
const sizeClasses = {
|
|
576
|
-
sm: 'px-2 py-1 text-sm',
|
|
577
|
-
md: 'px-4 py-2 text-base',
|
|
578
|
-
lg: 'px-6 py-3 text-lg'
|
|
579
|
-
};
|
|
580
|
-
|
|
581
|
-
const classes = `rounded-md ${variantClasses[variant]} ${sizeClasses[size]}`;
|
|
582
|
-
</script>
|
|
583
|
-
|
|
584
|
-
<button class={classes}> Click me </button>
|
|
585
|
-
```
|
|
586
|
-
|
|
587
|
-
### After: defineVariants
|
|
588
|
-
|
|
589
|
-
```svelte
|
|
590
|
-
<script>
|
|
591
|
-
import { HtmlAtom } from '@svelte-atoms/core';
|
|
592
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
593
|
-
|
|
594
|
-
const buttonVariants = defineVariants({
|
|
595
|
-
class: 'rounded-md',
|
|
596
|
-
variants: {
|
|
597
|
-
variant: {
|
|
598
|
-
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
599
|
-
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
600
|
-
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
601
|
-
},
|
|
602
|
-
size: {
|
|
603
|
-
sm: 'px-2 py-1 text-sm',
|
|
604
|
-
md: 'px-4 py-2 text-base',
|
|
605
|
-
lg: 'px-6 py-3 text-lg'
|
|
606
|
-
}
|
|
607
|
-
},
|
|
608
|
-
defaults: {
|
|
609
|
-
variant: 'primary',
|
|
610
|
-
size: 'md'
|
|
611
|
-
}
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
let { variant, size, ...props } = $props();
|
|
615
|
-
|
|
616
|
-
const bond = null; // or get from context if needed
|
|
617
|
-
const variantProps = buttonVariants(bond, { variant, size });
|
|
618
|
-
</script>
|
|
619
|
-
|
|
620
|
-
<HtmlAtom as="button" {...variantProps} {...props}>Click me</HtmlAtom>
|
|
621
|
-
```
|
|
622
|
-
|
|
623
|
-
### Benefits After Migration
|
|
624
|
-
|
|
625
|
-
✅ **Less boilerplate** - Define once, use everywhere
|
|
626
|
-
✅ **Type safety** - Automatic type inference
|
|
627
|
-
✅ **Compound variants** - Complex styling combinations
|
|
628
|
-
✅ **Default values** - No need for fallback logic
|
|
629
|
-
✅ **Bond integration** - Access component state for reactive variants
|
|
630
|
-
✅ **Return attributes** - Not just classes, any HTML attributes
|
|
631
|
-
|
|
632
|
-
## Best Practices
|
|
633
|
-
|
|
634
|
-
### 1. Organize Variants in Separate Files
|
|
635
|
-
|
|
636
|
-
```typescript
|
|
637
|
-
// variants.ts
|
|
638
|
-
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
639
|
-
|
|
640
|
-
export const buttonVariants = defineVariants({
|
|
641
|
-
class: 'rounded-md font-medium transition-colors',
|
|
642
|
-
variants: {
|
|
643
|
-
/* ... */
|
|
644
|
-
},
|
|
645
|
-
defaults: {
|
|
646
|
-
/* ... */
|
|
647
|
-
}
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
export const cardVariants = defineVariants({
|
|
651
|
-
class: 'rounded-lg border',
|
|
652
|
-
variants: {
|
|
653
|
-
/* ... */
|
|
654
|
-
},
|
|
655
|
-
defaults: {
|
|
656
|
-
/* ... */
|
|
657
|
-
}
|
|
658
|
-
});
|
|
659
|
-
```
|
|
660
|
-
|
|
661
|
-
### 2. Use $derived for Reactive Variants
|
|
662
|
-
|
|
663
|
-
When using bond state, wrap the variant call in `$derived`:
|
|
664
|
-
|
|
665
|
-
```svelte
|
|
666
|
-
<script>
|
|
667
|
-
const bond = AccordionBond.get();
|
|
668
|
-
|
|
669
|
-
// Reactive - updates when bond state changes
|
|
670
|
-
const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
|
|
671
|
-
</script>
|
|
672
|
-
```
|
|
673
|
-
|
|
674
|
-
### 3. Extend with Additional Classes
|
|
675
|
-
|
|
676
|
-
Merge variant classes with custom classes:
|
|
677
|
-
|
|
678
|
-
```svelte
|
|
679
|
-
<script>
|
|
680
|
-
const bond = null; // or get from context if needed
|
|
681
|
-
const variantProps = buttonVariants(bond, { variant, size });
|
|
682
|
-
</script>
|
|
683
|
-
|
|
684
|
-
<HtmlAtom class={[variantProps.class, 'custom-class']} {...variantProps} />
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
### 4. Type Props from Variants
|
|
688
|
-
|
|
689
|
-
Extract variant types for component props:
|
|
690
|
-
|
|
691
|
-
```typescript
|
|
692
|
-
import { VariantPropsType } from '@svelte-atoms/core/utils';
|
|
693
|
-
import { buttonVariants } from './variants';
|
|
694
|
-
|
|
695
|
-
type ButtonProps = VariantPropsType<typeof buttonVariants> & {
|
|
696
|
-
disabled?: boolean;
|
|
697
|
-
// other props...
|
|
698
|
-
};
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
## Summary
|
|
702
|
-
|
|
703
|
-
`defineVariants()` provides:
|
|
704
|
-
|
|
705
|
-
✅ **Single function** - One API for all variant needs
|
|
706
|
-
✅ **Type-safe** - Automatic TypeScript inference
|
|
707
|
-
✅ **Reactive** - Access bond state for dynamic styling
|
|
708
|
-
✅ **Powerful** - Base classes, compound variants, defaults
|
|
709
|
-
✅ **Flexible** - Return both classes and attributes
|
|
710
|
-
✅ **Clean** - No manual object merging or conditionals
|
|
711
|
-
|
|
712
|
-
Inspired by Class Variance Authority but integrated with @svelte-atoms/core's bond system.
|
|
1
|
+
# Variant System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The variant system in @svelte-atoms/core provides a powerful way to define component styling variations. It's deeply integrated with the preset system, allowing both global theming and local customization.
|
|
6
|
+
|
|
7
|
+
## Problem Statement
|
|
8
|
+
|
|
9
|
+
Currently, creating component variants (size, variant, appearance, etc.) requires:
|
|
10
|
+
|
|
11
|
+
```svelte
|
|
12
|
+
<script>
|
|
13
|
+
let { variant = 'primary', size = 'md' } = $props();
|
|
14
|
+
|
|
15
|
+
const variantClasses = {
|
|
16
|
+
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
17
|
+
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
18
|
+
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const sizeClasses = {
|
|
22
|
+
sm: 'px-2 py-1 text-sm',
|
|
23
|
+
md: 'px-4 py-2 text-base',
|
|
24
|
+
lg: 'px-6 py-3 text-lg'
|
|
25
|
+
};
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<HtmlAtom class={`${variantClasses[variant]} ${sizeClasses[size]}`} />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Pain points:**
|
|
32
|
+
|
|
33
|
+
- Repeated boilerplate in every component
|
|
34
|
+
- No access to component state (bond) for reactive variants
|
|
35
|
+
- Can't return attributes, only classes
|
|
36
|
+
- Not type-safe
|
|
37
|
+
|
|
38
|
+
## Integration with Preset System
|
|
39
|
+
|
|
40
|
+
**Key Feature:** Variants are now integrated with the preset system, enabling global variant definitions that can be overridden locally.
|
|
41
|
+
|
|
42
|
+
### Preset Structure
|
|
43
|
+
|
|
44
|
+
Presets support the full variant structure for comprehensive theming:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Preset: Full variant support
|
|
48
|
+
{
|
|
49
|
+
class: 'base-classes',
|
|
50
|
+
variants: {
|
|
51
|
+
variant: { primary: '...', secondary: '...' },
|
|
52
|
+
size: { sm: '...', md: '...', lg: '...' }
|
|
53
|
+
},
|
|
54
|
+
compounds: [
|
|
55
|
+
{ variant: 'primary', size: 'lg', class: 'shadow-lg' }
|
|
56
|
+
],
|
|
57
|
+
defaults: {
|
|
58
|
+
variant: 'primary',
|
|
59
|
+
size: 'md'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Component: Same structure, overrides/extends preset
|
|
64
|
+
{
|
|
65
|
+
class: 'component-base',
|
|
66
|
+
variants: { ... },
|
|
67
|
+
compoundss: [...],
|
|
68
|
+
defaults: { ... }
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Merge Behavior:**
|
|
73
|
+
|
|
74
|
+
- `variants`: Deep merged (component extends preset)
|
|
75
|
+
- `compounds`: Concatenated (preset first, then component compounds)
|
|
76
|
+
- `defaults`: Deep merged (component overrides preset)
|
|
77
|
+
|
|
78
|
+
### Architecture
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
Preset (Global Theme)
|
|
82
|
+
├─ class: ClassValue (base styling)
|
|
83
|
+
├─ variants: Record<string, Record<string, ClassValue>>
|
|
84
|
+
│ ├─ variant: { primary: '...', secondary: '...' }
|
|
85
|
+
│ └─ size: { sm: '...', md: '...', lg: '...' }
|
|
86
|
+
├─ compounds: Array<CompoundVariant> (conditional styling)
|
|
87
|
+
├─ defaults: Record<string, string> (default values)
|
|
88
|
+
├─ base: Component (component override)
|
|
89
|
+
├─ as: string (element type)
|
|
90
|
+
└─ ...other props
|
|
91
|
+
|
|
92
|
+
Component (Local)
|
|
93
|
+
├─ variants: VariantDefinition (extends/overrides preset)
|
|
94
|
+
│ ├─ class: ClassValue (component-specific base)
|
|
95
|
+
│ ├─ variants: { ... } (extends/overrides preset variants)
|
|
96
|
+
│ ├─ compounds: [...] (appended to preset)
|
|
97
|
+
│ └─ defaults: {...} (overrides preset defaults)
|
|
98
|
+
└─ variant props (size, variant, etc.)
|
|
99
|
+
|
|
100
|
+
Final Output = merge(preset, component)
|
|
101
|
+
- variants: deep merged
|
|
102
|
+
- compounds: concatenated (preset + component)
|
|
103
|
+
- defaults: deep merged (component overrides)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Hierarchy
|
|
107
|
+
|
|
108
|
+
The system merges variants in this order (later overrides earlier):
|
|
109
|
+
|
|
110
|
+
1. **Preset variants** - Global theme variants
|
|
111
|
+
2. **Component variants** - Local component-specific variants
|
|
112
|
+
3. **Props** - Runtime variant prop values
|
|
113
|
+
|
|
114
|
+
### Example: Global + Local Variants
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
// +layout.svelte - Global theme
|
|
118
|
+
import { setPreset } from '@svelte-atoms/core/context';
|
|
119
|
+
|
|
120
|
+
setPreset({
|
|
121
|
+
button: () => ({
|
|
122
|
+
class: 'font-medium transition-colors rounded-md focus:outline-none focus:ring-2',
|
|
123
|
+
variants: {
|
|
124
|
+
variant: {
|
|
125
|
+
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
126
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/90',
|
|
127
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90'
|
|
128
|
+
},
|
|
129
|
+
size: {
|
|
130
|
+
sm: 'h-8 px-3 text-xs',
|
|
131
|
+
md: 'h-10 px-4 text-sm',
|
|
132
|
+
lg: 'h-12 px-6 text-base'
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
compounds: [
|
|
136
|
+
{
|
|
137
|
+
variant: 'primary',
|
|
138
|
+
size: 'lg',
|
|
139
|
+
class: 'shadow-lg font-semibold'
|
|
140
|
+
}
|
|
141
|
+
],
|
|
142
|
+
defaults: {
|
|
143
|
+
variant: 'primary',
|
|
144
|
+
size: 'md'
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```svelte
|
|
151
|
+
<!-- Button.svelte - Local override -->
|
|
152
|
+
<script>
|
|
153
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
154
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
155
|
+
|
|
156
|
+
// Extend preset variants with additional local variants
|
|
157
|
+
const localVariants = defineVariants({
|
|
158
|
+
variants: {
|
|
159
|
+
variant: {
|
|
160
|
+
// Add new variant not in preset
|
|
161
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground'
|
|
162
|
+
},
|
|
163
|
+
// Add new variant key
|
|
164
|
+
loading: {
|
|
165
|
+
true: 'opacity-50 cursor-wait',
|
|
166
|
+
false: ''
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
let {
|
|
172
|
+
variant = 'primary',
|
|
173
|
+
size = 'md',
|
|
174
|
+
loading = false,
|
|
175
|
+
...props
|
|
176
|
+
} = $props();
|
|
177
|
+
</script>
|
|
178
|
+
|
|
179
|
+
<HtmlAtom
|
|
180
|
+
preset="button" <!-- Uses global preset -->
|
|
181
|
+
variants={localVariants} <!-- Merges with preset -->
|
|
182
|
+
{variant}
|
|
183
|
+
{size}
|
|
184
|
+
{loading}
|
|
185
|
+
{...props}
|
|
186
|
+
>
|
|
187
|
+
{@render children?.()}
|
|
188
|
+
</HtmlAtom>
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
**Result:** The button will have:
|
|
192
|
+
|
|
193
|
+
- Base classes from preset
|
|
194
|
+
- Preset variant classes (primary, sm/md/lg)
|
|
195
|
+
- Local variant classes (ghost, loading)
|
|
196
|
+
- Merged intelligently - local overrides preset
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
200
|
+
|
|
201
|
+
const buttonVariants = defineVariants({
|
|
202
|
+
class: 'rounded-md font-medium transition-colors',
|
|
203
|
+
variants: {
|
|
204
|
+
variant: {
|
|
205
|
+
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
206
|
+
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
207
|
+
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
208
|
+
},
|
|
209
|
+
size: {
|
|
210
|
+
sm: 'px-2 py-1 text-sm',
|
|
211
|
+
md: 'px-4 py-2 text-base',
|
|
212
|
+
lg: 'px-6 py-3 text-lg'
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
compounds: [
|
|
216
|
+
{
|
|
217
|
+
variant: 'primary',
|
|
218
|
+
size: 'lg',
|
|
219
|
+
class: 'shadow-lg' // Applied when both conditions match
|
|
220
|
+
}
|
|
221
|
+
],
|
|
222
|
+
defaults: {
|
|
223
|
+
variant: 'primary',
|
|
224
|
+
size: 'md'
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```svelte
|
|
230
|
+
<script>
|
|
231
|
+
import { buttonVariants } from './variants';
|
|
232
|
+
|
|
233
|
+
let { variant, size, ...props } = $props();
|
|
234
|
+
|
|
235
|
+
const variantProps = buttonVariants({ variant, size });
|
|
236
|
+
</script>
|
|
237
|
+
|
|
238
|
+
<HtmlAtom {...variantProps} {...props}>
|
|
239
|
+
{@render children?.()}
|
|
240
|
+
</HtmlAtom>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Key Features
|
|
244
|
+
|
|
245
|
+
### 1. Access to Component State (Bond)
|
|
246
|
+
|
|
247
|
+
Variant values can be functions that receive the component's bond for reactive styling:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
const accordionVariants = defineVariants({
|
|
251
|
+
class: 'border rounded-md transition-all',
|
|
252
|
+
variants: {
|
|
253
|
+
state: {
|
|
254
|
+
open: (bond) => ({
|
|
255
|
+
class: bond?.state?.isOpen ? 'bg-blue-50 border-blue-200' : 'bg-white',
|
|
256
|
+
'aria-expanded': bond?.state?.isOpen,
|
|
257
|
+
'data-state': bond?.state?.isOpen ? 'open' : 'closed'
|
|
258
|
+
}),
|
|
259
|
+
disabled: (bond) => ({
|
|
260
|
+
class: bond?.state?.disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
|
|
261
|
+
'aria-disabled': bond?.state?.disabled
|
|
262
|
+
})
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// Usage:
|
|
268
|
+
const bond = AccordionBond.get();
|
|
269
|
+
const variantProps = accordionVariants(bond, { state: 'open' });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### 2. Return Both Classes and Attributes
|
|
273
|
+
|
|
274
|
+
Variants can return not just classes, but any HTML attributes:
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const buttonVariants = defineVariants({
|
|
278
|
+
variants: {
|
|
279
|
+
variant: {
|
|
280
|
+
primary: {
|
|
281
|
+
class: 'bg-blue-500 text-white',
|
|
282
|
+
'aria-label': 'Primary action',
|
|
283
|
+
'data-variant': 'primary'
|
|
284
|
+
},
|
|
285
|
+
danger: (bond) => ({
|
|
286
|
+
class: bond?.state?.disabled ? 'bg-red-300' : 'bg-red-500',
|
|
287
|
+
'aria-disabled': bond?.state?.disabled,
|
|
288
|
+
role: 'button'
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Returns: { class: '...', 'aria-label': '...', 'data-variant': '...', ... }
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### 3. Compound Variants
|
|
298
|
+
|
|
299
|
+
Apply additional styling when multiple conditions match:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
const alertVariants = defineVariants({
|
|
303
|
+
class: 'rounded-lg p-4 border',
|
|
304
|
+
variants: {
|
|
305
|
+
variant: {
|
|
306
|
+
error: 'bg-red-50 border-red-200 text-red-900'
|
|
307
|
+
},
|
|
308
|
+
size: {
|
|
309
|
+
lg: 'text-lg'
|
|
310
|
+
}
|
|
311
|
+
},
|
|
312
|
+
compounds: [
|
|
313
|
+
{
|
|
314
|
+
variant: 'error',
|
|
315
|
+
size: 'lg',
|
|
316
|
+
class: 'font-bold', // Only applied when both variant=error AND size=lg
|
|
317
|
+
role: 'alert'
|
|
318
|
+
}
|
|
319
|
+
]
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### 4. Type Safety
|
|
324
|
+
|
|
325
|
+
Full TypeScript support with automatic type inference:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
type ButtonVariants = VariantPropsType<typeof buttonVariants>;
|
|
329
|
+
// Inferred type: { variant?: 'primary' | 'secondary' | 'danger'; size?: 'sm' | 'md' | 'lg' }
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### 5. Default Variants
|
|
333
|
+
|
|
334
|
+
Specify default values that are used when no variant is provided:
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const buttonVariants = defineVariants({
|
|
338
|
+
class: 'rounded-md',
|
|
339
|
+
variants: {
|
|
340
|
+
variant: {
|
|
341
|
+
primary: 'bg-blue-500',
|
|
342
|
+
secondary: 'bg-gray-500'
|
|
343
|
+
},
|
|
344
|
+
size: {
|
|
345
|
+
sm: 'text-sm',
|
|
346
|
+
md: 'text-base',
|
|
347
|
+
lg: 'text-lg'
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
defaults: {
|
|
351
|
+
variant: 'primary', // Used if variant prop is undefined
|
|
352
|
+
size: 'md' // Used if size prop is undefined
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Call without props - uses defaults
|
|
357
|
+
buttonVariants(null); // Returns variant='primary', size='md'
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Complete Examples
|
|
361
|
+
|
|
362
|
+
### Example 1: Preset-based Button (Recommended)
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Theme.svelte - Define global button variants
|
|
366
|
+
import { setPreset } from '@svelte-atoms/core/context';
|
|
367
|
+
|
|
368
|
+
setPreset({
|
|
369
|
+
button: () => ({
|
|
370
|
+
class:
|
|
371
|
+
'inline-flex items-center justify-center font-medium rounded-md transition-colors focus-visible:outline-none focus-visible:ring-2',
|
|
372
|
+
variants: {
|
|
373
|
+
variant: {
|
|
374
|
+
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
|
375
|
+
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
|
376
|
+
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
|
377
|
+
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
|
378
|
+
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
|
379
|
+
link: 'text-primary underline-offset-4 hover:underline'
|
|
380
|
+
},
|
|
381
|
+
size: {
|
|
382
|
+
default: 'h-10 px-4 py-2',
|
|
383
|
+
sm: 'h-9 rounded-md px-3',
|
|
384
|
+
lg: 'h-11 rounded-md px-8',
|
|
385
|
+
icon: 'h-10 w-10'
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
compounds: [
|
|
389
|
+
{
|
|
390
|
+
variant: 'default',
|
|
391
|
+
size: 'lg',
|
|
392
|
+
class: 'text-base font-semibold'
|
|
393
|
+
}
|
|
394
|
+
],
|
|
395
|
+
defaults: {
|
|
396
|
+
variant: 'default',
|
|
397
|
+
size: 'default'
|
|
398
|
+
}
|
|
399
|
+
})
|
|
400
|
+
});
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
```svelte
|
|
404
|
+
<!-- Button.svelte - Use preset variants -->
|
|
405
|
+
<script lang="ts">
|
|
406
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
407
|
+
|
|
408
|
+
type ButtonProps = {
|
|
409
|
+
variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link';
|
|
410
|
+
size?: 'default' | 'sm' | 'lg' | 'icon';
|
|
411
|
+
disabled?: boolean;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
let { variant, size, disabled = false, class: klass = '', ...props }: ButtonProps = $props();
|
|
415
|
+
</script>
|
|
416
|
+
|
|
417
|
+
<HtmlAtom preset="button" as="button" {variant} {size} {disabled} class={klass} {...props}>
|
|
418
|
+
{@render children?.()}
|
|
419
|
+
</HtmlAtom>
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Example 2: Local Variants Only
|
|
423
|
+
|
|
424
|
+
````svelte
|
|
425
|
+
### Example 3: Extending Preset with Local Variants
|
|
426
|
+
|
|
427
|
+
Combine global preset variants with component-specific variants:
|
|
428
|
+
|
|
429
|
+
```svelte
|
|
430
|
+
<!-- special-button.svelte -->
|
|
431
|
+
<script>
|
|
432
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
433
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
434
|
+
|
|
435
|
+
// Local variants that extend/override preset
|
|
436
|
+
const localVariants = defineVariants({
|
|
437
|
+
variants: {
|
|
438
|
+
variant: {
|
|
439
|
+
// Add new variants not in preset
|
|
440
|
+
gradient: 'bg-gradient-to-r from-purple-500 to-pink-500 text-white',
|
|
441
|
+
neon: 'bg-black text-neon-green border-2 border-neon-green'
|
|
442
|
+
},
|
|
443
|
+
// Add completely new variant key
|
|
444
|
+
animated: {
|
|
445
|
+
true: 'animate-pulse',
|
|
446
|
+
false: ''
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
let {
|
|
452
|
+
variant,
|
|
453
|
+
size,
|
|
454
|
+
animated = false,
|
|
455
|
+
...props
|
|
456
|
+
} = $props();
|
|
457
|
+
</script>
|
|
458
|
+
|
|
459
|
+
<HtmlAtom
|
|
460
|
+
preset="button" <!-- Gets base variants -->
|
|
461
|
+
variants={localVariants} <!-- Merges/extends -->
|
|
462
|
+
{variant}
|
|
463
|
+
{size}
|
|
464
|
+
{animated}
|
|
465
|
+
{...props}
|
|
466
|
+
>
|
|
467
|
+
{@render children?.()}
|
|
468
|
+
</HtmlAtom>
|
|
469
|
+
````
|
|
470
|
+
|
|
471
|
+
### Example 4: Reactive Variants with Bond State
|
|
472
|
+
|
|
473
|
+
<script>
|
|
474
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
475
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
476
|
+
|
|
477
|
+
const alertVariants = defineVariants({
|
|
478
|
+
class: 'rounded-lg p-4 border',
|
|
479
|
+
variants: {
|
|
480
|
+
variant: {
|
|
481
|
+
info: 'bg-blue-50 border-blue-200 text-blue-900',
|
|
482
|
+
success: 'bg-green-50 border-green-200 text-green-900',
|
|
483
|
+
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
|
|
484
|
+
error: 'bg-red-50 border-red-200 text-red-900'
|
|
485
|
+
},
|
|
486
|
+
size: {
|
|
487
|
+
sm: 'text-sm p-2',
|
|
488
|
+
md: 'text-base p-4',
|
|
489
|
+
lg: 'text-lg p-6'
|
|
490
|
+
}
|
|
491
|
+
},
|
|
492
|
+
compounds: [
|
|
493
|
+
{
|
|
494
|
+
variant: 'error',
|
|
495
|
+
size: 'lg',
|
|
496
|
+
class: 'font-bold',
|
|
497
|
+
role: 'alert',
|
|
498
|
+
'aria-live': 'assertive'
|
|
499
|
+
}
|
|
500
|
+
],
|
|
501
|
+
defaults: {
|
|
502
|
+
variant: 'info',
|
|
503
|
+
size: 'md'
|
|
504
|
+
}
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
let { variant, size, ...props } = $props();
|
|
508
|
+
|
|
509
|
+
const bond = null; // or get from context if needed
|
|
510
|
+
const variantProps = alertVariants(bond, { variant, size });
|
|
511
|
+
</script>
|
|
512
|
+
|
|
513
|
+
<HtmlAtom {...variantProps} {...props}>
|
|
514
|
+
{@render children?.()}
|
|
515
|
+
</HtmlAtom>
|
|
516
|
+
|
|
517
|
+
````
|
|
518
|
+
|
|
519
|
+
### Example 3: Accordion with Reactive Bond State
|
|
520
|
+
|
|
521
|
+
```svelte
|
|
522
|
+
<!-- accordion-item.svelte -->
|
|
523
|
+
<script>
|
|
524
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
525
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
526
|
+
import { AccordionBond } from './bond.svelte';
|
|
527
|
+
|
|
528
|
+
const accordionVariants = defineVariants({
|
|
529
|
+
class: 'border rounded-md transition-all duration-200',
|
|
530
|
+
variants: {
|
|
531
|
+
state: {
|
|
532
|
+
// Reactive: changes when bond.state.isOpen changes
|
|
533
|
+
open: (bond) => ({
|
|
534
|
+
class: bond?.state?.isOpen
|
|
535
|
+
? 'bg-blue-50 border-blue-200'
|
|
536
|
+
: 'bg-white border-gray-200',
|
|
537
|
+
'aria-expanded': bond?.state?.isOpen,
|
|
538
|
+
'data-state': bond?.state?.isOpen ? 'open' : 'closed'
|
|
539
|
+
}),
|
|
540
|
+
disabled: (bond) => ({
|
|
541
|
+
class: bond?.state?.disabled
|
|
542
|
+
? 'opacity-50 cursor-not-allowed'
|
|
543
|
+
: 'cursor-pointer',
|
|
544
|
+
'aria-disabled': bond?.state?.disabled
|
|
545
|
+
})
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
const bond = AccordionBond.get();
|
|
551
|
+
|
|
552
|
+
// Automatically reactive - updates when bond state changes
|
|
553
|
+
const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
|
|
554
|
+
</script>
|
|
555
|
+
|
|
556
|
+
<HtmlAtom {...variantProps}>
|
|
557
|
+
{@render children?.({ accordion: bond })}
|
|
558
|
+
</HtmlAtom>
|
|
559
|
+
````
|
|
560
|
+
|
|
561
|
+
## Migration Guide
|
|
562
|
+
|
|
563
|
+
### Before: Manual Variant Management
|
|
564
|
+
|
|
565
|
+
```svelte
|
|
566
|
+
<script>
|
|
567
|
+
let { variant = 'primary', size = 'md' } = $props();
|
|
568
|
+
|
|
569
|
+
const variantClasses = {
|
|
570
|
+
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
571
|
+
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
572
|
+
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const sizeClasses = {
|
|
576
|
+
sm: 'px-2 py-1 text-sm',
|
|
577
|
+
md: 'px-4 py-2 text-base',
|
|
578
|
+
lg: 'px-6 py-3 text-lg'
|
|
579
|
+
};
|
|
580
|
+
|
|
581
|
+
const classes = `rounded-md ${variantClasses[variant]} ${sizeClasses[size]}`;
|
|
582
|
+
</script>
|
|
583
|
+
|
|
584
|
+
<button class={classes}> Click me </button>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
### After: defineVariants
|
|
588
|
+
|
|
589
|
+
```svelte
|
|
590
|
+
<script>
|
|
591
|
+
import { HtmlAtom } from '@svelte-atoms/core';
|
|
592
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
593
|
+
|
|
594
|
+
const buttonVariants = defineVariants({
|
|
595
|
+
class: 'rounded-md',
|
|
596
|
+
variants: {
|
|
597
|
+
variant: {
|
|
598
|
+
primary: 'bg-blue-500 text-white hover:bg-blue-600',
|
|
599
|
+
secondary: 'bg-gray-500 text-white hover:bg-gray-600',
|
|
600
|
+
danger: 'bg-red-500 text-white hover:bg-red-600'
|
|
601
|
+
},
|
|
602
|
+
size: {
|
|
603
|
+
sm: 'px-2 py-1 text-sm',
|
|
604
|
+
md: 'px-4 py-2 text-base',
|
|
605
|
+
lg: 'px-6 py-3 text-lg'
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
defaults: {
|
|
609
|
+
variant: 'primary',
|
|
610
|
+
size: 'md'
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
let { variant, size, ...props } = $props();
|
|
615
|
+
|
|
616
|
+
const bond = null; // or get from context if needed
|
|
617
|
+
const variantProps = buttonVariants(bond, { variant, size });
|
|
618
|
+
</script>
|
|
619
|
+
|
|
620
|
+
<HtmlAtom as="button" {...variantProps} {...props}>Click me</HtmlAtom>
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
### Benefits After Migration
|
|
624
|
+
|
|
625
|
+
✅ **Less boilerplate** - Define once, use everywhere
|
|
626
|
+
✅ **Type safety** - Automatic type inference
|
|
627
|
+
✅ **Compound variants** - Complex styling combinations
|
|
628
|
+
✅ **Default values** - No need for fallback logic
|
|
629
|
+
✅ **Bond integration** - Access component state for reactive variants
|
|
630
|
+
✅ **Return attributes** - Not just classes, any HTML attributes
|
|
631
|
+
|
|
632
|
+
## Best Practices
|
|
633
|
+
|
|
634
|
+
### 1. Organize Variants in Separate Files
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// variants.ts
|
|
638
|
+
import { defineVariants } from '@svelte-atoms/core/utils';
|
|
639
|
+
|
|
640
|
+
export const buttonVariants = defineVariants({
|
|
641
|
+
class: 'rounded-md font-medium transition-colors',
|
|
642
|
+
variants: {
|
|
643
|
+
/* ... */
|
|
644
|
+
},
|
|
645
|
+
defaults: {
|
|
646
|
+
/* ... */
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
export const cardVariants = defineVariants({
|
|
651
|
+
class: 'rounded-lg border',
|
|
652
|
+
variants: {
|
|
653
|
+
/* ... */
|
|
654
|
+
},
|
|
655
|
+
defaults: {
|
|
656
|
+
/* ... */
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### 2. Use $derived for Reactive Variants
|
|
662
|
+
|
|
663
|
+
When using bond state, wrap the variant call in `$derived`:
|
|
664
|
+
|
|
665
|
+
```svelte
|
|
666
|
+
<script>
|
|
667
|
+
const bond = AccordionBond.get();
|
|
668
|
+
|
|
669
|
+
// Reactive - updates when bond state changes
|
|
670
|
+
const variantProps = $derived(accordionVariants(bond, { state: 'open' }));
|
|
671
|
+
</script>
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### 3. Extend with Additional Classes
|
|
675
|
+
|
|
676
|
+
Merge variant classes with custom classes:
|
|
677
|
+
|
|
678
|
+
```svelte
|
|
679
|
+
<script>
|
|
680
|
+
const bond = null; // or get from context if needed
|
|
681
|
+
const variantProps = buttonVariants(bond, { variant, size });
|
|
682
|
+
</script>
|
|
683
|
+
|
|
684
|
+
<HtmlAtom class={[variantProps.class, 'custom-class']} {...variantProps} />
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
### 4. Type Props from Variants
|
|
688
|
+
|
|
689
|
+
Extract variant types for component props:
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
import { VariantPropsType } from '@svelte-atoms/core/utils';
|
|
693
|
+
import { buttonVariants } from './variants';
|
|
694
|
+
|
|
695
|
+
type ButtonProps = VariantPropsType<typeof buttonVariants> & {
|
|
696
|
+
disabled?: boolean;
|
|
697
|
+
// other props...
|
|
698
|
+
};
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
## Summary
|
|
702
|
+
|
|
703
|
+
`defineVariants()` provides:
|
|
704
|
+
|
|
705
|
+
✅ **Single function** - One API for all variant needs
|
|
706
|
+
✅ **Type-safe** - Automatic TypeScript inference
|
|
707
|
+
✅ **Reactive** - Access bond state for dynamic styling
|
|
708
|
+
✅ **Powerful** - Base classes, compound variants, defaults
|
|
709
|
+
✅ **Flexible** - Return both classes and attributes
|
|
710
|
+
✅ **Clean** - No manual object merging or conditionals
|
|
711
|
+
|
|
712
|
+
Inspired by Class Variance Authority but integrated with @svelte-atoms/core's bond system.
|