aeon-ui-engine 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COMPONENTS.md +7 -2
- package/INTEGRATION.md +14 -0
- package/README.md +8 -4
- package/apps/demo/og.config.mjs +8 -3
- package/apps/demo/public/og-aeon-ui-engine.png +0 -0
- package/apps/demo/public/og.svg +2 -2
- package/apps/demo/scripts/generate-og-png.mjs +4 -2
- package/apps/demo/src/App.tsx +2 -2
- package/apps/demo/src/aeonDiagrams.ts +17 -75
- package/apps/demo/src/components/DiagramsSection.tsx +8 -9
- package/apps/demo/src/components/HeroInstall.tsx +7 -4
- package/apps/demo/src/components/LandingFaq.tsx +1 -1
- package/apps/demo/src/components/LandingFooter.tsx +8 -0
- package/apps/demo/src/components/LandingHeader.tsx +4 -2
- package/apps/demo/src/components/LayersSection.tsx +6 -10
- package/apps/demo/src/components/MermaidDiagram.tsx +5 -3
- package/apps/demo/src/components/PhilosophySection.tsx +0 -2
- package/apps/demo/src/components/PlaygroundBlock.tsx +3 -8
- package/apps/demo/src/components/PlaygroundControlsDemo.tsx +63 -0
- package/apps/demo/src/components/PlaygroundSection.tsx +7 -91
- package/apps/demo/src/components/ThemeSwitcher.tsx +12 -9
- package/apps/demo/src/demo-layout.css +29 -0
- package/apps/demo/src/landing.css +136 -79
- package/apps/demo/src/nav.ts +3 -3
- package/apps/demo/src/site.ts +99 -228
- package/docs/MACHINES.md +6 -1
- package/package.json +1 -1
- package/packages/core/src/anatomy.ts +23 -0
- package/packages/panda/panda.config.ts +19 -1
- package/packages/panda/src/recipes/index.ts +5 -0
- package/packages/panda/src/recipes/pin-input.ts +29 -0
- package/packages/panda/src/recipes/progress.ts +44 -0
- package/packages/panda/src/recipes/radio-group.ts +51 -0
- package/packages/panda/src/recipes/select.ts +2 -0
- package/packages/panda/src/recipes/separator.ts +35 -0
- package/packages/panda/src/recipes/slider.ts +57 -0
- package/packages/panda/styled-system/recipes/aeon-pin-input.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-pin-input.mjs +36 -0
- package/packages/panda/styled-system/recipes/aeon-progress.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-progress.mjs +44 -0
- package/packages/panda/styled-system/recipes/aeon-radio-group.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-radio-group.mjs +48 -0
- package/packages/panda/styled-system/recipes/aeon-separator.d.ts +31 -0
- package/packages/panda/styled-system/recipes/aeon-separator.mjs +41 -0
- package/packages/panda/styled-system/recipes/aeon-slider.d.ts +28 -0
- package/packages/panda/styled-system/recipes/aeon-slider.mjs +48 -0
- package/packages/panda/styled-system/recipes/index.d.ts +5 -0
- package/packages/panda/styled-system/recipes/index.mjs +5 -0
- package/packages/panda/styled-system/styles.css +223 -46
- package/packages/panda/styled-system/types/conditions.d.ts +1 -1
- package/packages/primitives/src/index.ts +1 -0
- package/packages/primitives/src/machines/slider.ts +93 -0
- package/packages/react/src/checkbox/index.tsx +14 -0
- package/packages/react/src/floating/FloatingPositioner.tsx +59 -0
- package/packages/react/src/hooks/use-anchor-position.ts +33 -5
- package/packages/react/src/hooks/use-prefers-hover.ts +16 -0
- package/packages/react/src/index.ts +5 -0
- package/packages/react/src/menu/index.tsx +38 -11
- package/packages/react/src/pin-input/index.tsx +223 -0
- package/packages/react/src/popover/index.tsx +38 -11
- package/packages/react/src/progress/index.tsx +110 -0
- package/packages/react/src/radio-group/index.tsx +199 -0
- package/packages/react/src/select/index.tsx +51 -28
- package/packages/react/src/select/select.test.tsx +21 -0
- package/packages/react/src/separator/index.tsx +23 -0
- package/packages/react/src/slider/index.tsx +264 -0
- package/packages/react/src/toast/primitives.tsx +8 -1
- package/packages/react/src/toast/toast.test.tsx +40 -0
- package/packages/react/src/tooltip/index.tsx +124 -20
- package/packages/react/src/tooltip/tooltip.test.tsx +114 -0
- package/packages/site/README.md +1 -1
- package/packages/ui/src/index.ts +5 -0
- package/packages/ui/src/pin-input.tsx +16 -0
- package/packages/ui/src/progress.tsx +21 -0
- package/packages/ui/src/radio-group.tsx +24 -0
- package/packages/ui/src/separator.tsx +15 -0
- package/packages/ui/src/slider.tsx +24 -0
- package/apps/demo/public/og.png +0 -0
- package/apps/demo/src/components/SceneSection.tsx +0 -157
|
@@ -336,6 +336,21 @@
|
|
|
336
336
|
pointer-events: none;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
@keyframes aeonProgressIndeterminate {
|
|
340
|
+
0% {
|
|
341
|
+
transform: translateX(-100%);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
100% {
|
|
345
|
+
transform: translateX(250%);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
[data-aeon-scope="radioGroup"] [data-aeon-part="item"] {
|
|
350
|
+
display: inline-flex;
|
|
351
|
+
align-items: center;
|
|
352
|
+
}
|
|
353
|
+
|
|
339
354
|
*,::before,::after,::backdrop {
|
|
340
355
|
--blur: /*-*/ /*-*/;
|
|
341
356
|
--brightness: /*-*/ /*-*/;
|
|
@@ -1239,7 +1254,7 @@
|
|
|
1239
1254
|
transition-duration: var(--durations-normal);
|
|
1240
1255
|
}
|
|
1241
1256
|
|
|
1242
|
-
.aeonCheckbox__control[data-aeon-state
|
|
1257
|
+
.aeonCheckbox__control[data-aeon-state~="checked"] {
|
|
1243
1258
|
background: var(--colors-accent);
|
|
1244
1259
|
border-color: var(--colors-accent);
|
|
1245
1260
|
}
|
|
@@ -1640,6 +1655,36 @@
|
|
|
1640
1655
|
background: var(--colors-border);
|
|
1641
1656
|
}
|
|
1642
1657
|
|
|
1658
|
+
.aeonPinInput__root {
|
|
1659
|
+
display: inline-flex;
|
|
1660
|
+
gap: var(--spacing-gap-sm);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
.aeonPinInput__input {
|
|
1664
|
+
width: 2.5rem;
|
|
1665
|
+
height: 2.75rem;
|
|
1666
|
+
text-align: center;
|
|
1667
|
+
border-radius: var(--radii-md);
|
|
1668
|
+
background: var(--colors-surface);
|
|
1669
|
+
color: var(--colors-fg);
|
|
1670
|
+
outline: var(--borders-none);
|
|
1671
|
+
font-size: var(--font-sizes-lg);
|
|
1672
|
+
font-family: var(--fonts-ui);
|
|
1673
|
+
font-weight: var(--font-weights-semibold);
|
|
1674
|
+
border-width: 1px;
|
|
1675
|
+
border-style: solid;
|
|
1676
|
+
border-color: var(--colors-border);
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
.aeonPinInput__input[data-aeon-state=filled] {
|
|
1680
|
+
border-color: var(--colors-accent);
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
.aeonPinInput__input:is(:focus-visible, [data-focus-visible]) {
|
|
1684
|
+
box-shadow: var(--shadows-focus-ring);
|
|
1685
|
+
border-color: var(--colors-accent);
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1643
1688
|
.aeonPopover__root {
|
|
1644
1689
|
position: relative;
|
|
1645
1690
|
display: inline-block;
|
|
@@ -1701,6 +1746,108 @@
|
|
|
1701
1746
|
color: var(--colors-fg);
|
|
1702
1747
|
}
|
|
1703
1748
|
|
|
1749
|
+
.aeonProgress__root {
|
|
1750
|
+
display: flex;
|
|
1751
|
+
gap: var(--spacing-gap-sm);
|
|
1752
|
+
width: 100%;
|
|
1753
|
+
flex-direction: column;
|
|
1754
|
+
font-family: var(--fonts-ui);
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.aeonProgress__root[data-aeon-state=loading] .aeonProgress__range {
|
|
1758
|
+
width: 40% !important;
|
|
1759
|
+
animation: aeonProgressIndeterminate 1.2s ease-in-out infinite;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
.aeonProgress__track {
|
|
1763
|
+
position: relative;
|
|
1764
|
+
width: 100%;
|
|
1765
|
+
height: 0.5rem;
|
|
1766
|
+
border-radius: var(--radii-full);
|
|
1767
|
+
background: var(--colors-surface);
|
|
1768
|
+
overflow: hidden;
|
|
1769
|
+
border-width: 1px;
|
|
1770
|
+
border-style: solid;
|
|
1771
|
+
border-color: var(--colors-border);
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
.aeonProgress__range {
|
|
1775
|
+
height: 100%;
|
|
1776
|
+
border-radius: var(--radii-full);
|
|
1777
|
+
background: var(--colors-accent);
|
|
1778
|
+
--transition-prop: width;
|
|
1779
|
+
transition-property: width;
|
|
1780
|
+
--transition-duration: var(--durations-normal);
|
|
1781
|
+
transition-duration: var(--durations-normal);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
.aeonProgress__label {
|
|
1785
|
+
color: var(--colors-muted);
|
|
1786
|
+
font-size: var(--font-sizes-sm);
|
|
1787
|
+
line-height: var(--line-heights-snug);
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
.aeonRadioGroup__root {
|
|
1791
|
+
display: flex;
|
|
1792
|
+
gap: var(--spacing-gap-sm);
|
|
1793
|
+
flex-direction: column;
|
|
1794
|
+
font-family: var(--fonts-ui);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
.aeonRadioGroup__item {
|
|
1798
|
+
gap: var(--spacing-gap-lg);
|
|
1799
|
+
vertical-align: middle;
|
|
1800
|
+
font-family: var(--fonts-ui);
|
|
1801
|
+
line-height: 1.5;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
.aeonRadioGroup__item,.aeonRadioGroup__itemControl {
|
|
1805
|
+
display: inline-flex;
|
|
1806
|
+
cursor: pointer;
|
|
1807
|
+
align-items: center;
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
.aeonRadioGroup__itemControl {
|
|
1811
|
+
width: 1.5rem;
|
|
1812
|
+
height: 1.5rem;
|
|
1813
|
+
border-radius: var(--radii-full);
|
|
1814
|
+
background: var(--colors-surface);
|
|
1815
|
+
outline: var(--borders-none);
|
|
1816
|
+
justify-content: center;
|
|
1817
|
+
flex-shrink: 0;
|
|
1818
|
+
border-width: 1px;
|
|
1819
|
+
border-style: solid;
|
|
1820
|
+
border-color: var(--colors-border);
|
|
1821
|
+
--transition-prop: background-color, border-color;
|
|
1822
|
+
transition-property: background-color, border-color;
|
|
1823
|
+
--transition-duration: var(--durations-normal);
|
|
1824
|
+
transition-duration: var(--durations-normal);
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
.aeonRadioGroup__itemControl[data-aeon-state=selected] {
|
|
1828
|
+
border-color: var(--colors-accent);
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
.aeonRadioGroup__itemControl:is(:focus-visible, [data-focus-visible]) {
|
|
1832
|
+
box-shadow: var(--shadows-focus-ring);
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
.aeonRadioGroup__itemIndicator {
|
|
1836
|
+
width: 0.625rem;
|
|
1837
|
+
height: 0.625rem;
|
|
1838
|
+
border-radius: var(--radii-full);
|
|
1839
|
+
background: var(--colors-accent);
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
.aeonRadioGroup__itemLabel {
|
|
1843
|
+
color: var(--colors-fg);
|
|
1844
|
+
-webkit-user-select: none;
|
|
1845
|
+
user-select: none;
|
|
1846
|
+
cursor: pointer;
|
|
1847
|
+
font-size: var(--font-sizes-sm);
|
|
1848
|
+
line-height: 1.5;
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1704
1851
|
.aeonScroll__root {
|
|
1705
1852
|
display: flex;
|
|
1706
1853
|
gap: var(--spacing-gap-sm);
|
|
@@ -1733,6 +1880,7 @@
|
|
|
1733
1880
|
display: inline-flex;
|
|
1734
1881
|
gap: var(--spacing-gap-sm);
|
|
1735
1882
|
width: 100%;
|
|
1883
|
+
box-sizing: border-box;
|
|
1736
1884
|
min-height: 2rem;
|
|
1737
1885
|
padding-block: var(--spacing-control-y);
|
|
1738
1886
|
padding-inline: var(--spacing-control-x);
|
|
@@ -1787,6 +1935,7 @@
|
|
|
1787
1935
|
border-right: 2px solid;
|
|
1788
1936
|
border-bottom: 2px solid;
|
|
1789
1937
|
transform: rotate(45deg) translateY(-1px);
|
|
1938
|
+
transform-origin: center;
|
|
1790
1939
|
flex-shrink: 0;
|
|
1791
1940
|
border-color: var(--colors-muted);
|
|
1792
1941
|
--transition-prop: transform;
|
|
@@ -1843,6 +1992,73 @@
|
|
|
1843
1992
|
color: var(--colors-accent);
|
|
1844
1993
|
}
|
|
1845
1994
|
|
|
1995
|
+
.aeonSeparator__root {
|
|
1996
|
+
background: var(--colors-border);
|
|
1997
|
+
border: var(--borders-none);
|
|
1998
|
+
flex-shrink: 0;
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
.aeonSlider__root {
|
|
2002
|
+
position: relative;
|
|
2003
|
+
display: flex;
|
|
2004
|
+
gap: var(--spacing-gap-sm);
|
|
2005
|
+
width: 100%;
|
|
2006
|
+
min-width: 8rem;
|
|
2007
|
+
flex-direction: column;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
.aeonSlider__track {
|
|
2011
|
+
position: relative;
|
|
2012
|
+
height: 0.35rem;
|
|
2013
|
+
border-radius: var(--radii-full);
|
|
2014
|
+
background: var(--colors-surface);
|
|
2015
|
+
cursor: pointer;
|
|
2016
|
+
touch-action: none;
|
|
2017
|
+
border-width: 1px;
|
|
2018
|
+
border-style: solid;
|
|
2019
|
+
border-color: var(--colors-border);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
.aeonSlider__range {
|
|
2023
|
+
height: 100%;
|
|
2024
|
+
background: var(--colors-accent-dim);
|
|
2025
|
+
pointer-events: none;
|
|
2026
|
+
top: var(--spacing-0);
|
|
2027
|
+
left: var(--spacing-0);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
.aeonSlider__range,.aeonSlider__thumb {
|
|
2031
|
+
position: absolute;
|
|
2032
|
+
border-radius: var(--radii-full);
|
|
2033
|
+
}
|
|
2034
|
+
|
|
2035
|
+
.aeonSlider__thumb {
|
|
2036
|
+
width: 1rem;
|
|
2037
|
+
height: 1rem;
|
|
2038
|
+
background: var(--colors-accent);
|
|
2039
|
+
transform: translate(-50%, -50%);
|
|
2040
|
+
cursor: grab;
|
|
2041
|
+
box-shadow: var(--shadows-panel);
|
|
2042
|
+
top: 50%;
|
|
2043
|
+
border-width: 2px;
|
|
2044
|
+
border-style: solid;
|
|
2045
|
+
border-color: var(--colors-surface-raised);
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
.aeonSlider__thumb[data-aeon-state=dragging] {
|
|
2049
|
+
cursor: grabbing;
|
|
2050
|
+
}
|
|
2051
|
+
|
|
2052
|
+
.aeonSlider__thumb:is(:focus-visible, [data-focus-visible]) {
|
|
2053
|
+
box-shadow: var(--shadows-focus-ring);
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
.aeonSlider__valueText {
|
|
2057
|
+
color: var(--colors-muted);
|
|
2058
|
+
font-size: var(--font-sizes-xs);
|
|
2059
|
+
font-family: var(--fonts-ui);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
1846
2062
|
.aeonSwitch__root {
|
|
1847
2063
|
gap: var(--spacing-gap-lg);
|
|
1848
2064
|
font-family: var(--fonts-ui);
|
|
@@ -1880,7 +2096,7 @@
|
|
|
1880
2096
|
transition-duration: var(--durations-normal);
|
|
1881
2097
|
}
|
|
1882
2098
|
|
|
1883
|
-
.aeonSwitch__control[data-aeon-state
|
|
2099
|
+
.aeonSwitch__control[data-aeon-state~="checked"] {
|
|
1884
2100
|
background: var(--colors-accent-dim);
|
|
1885
2101
|
border-color: var(--colors-accent);
|
|
1886
2102
|
}
|
|
@@ -1902,7 +2118,7 @@
|
|
|
1902
2118
|
transition-duration: var(--durations-normal);
|
|
1903
2119
|
}
|
|
1904
2120
|
|
|
1905
|
-
.aeonSwitch__thumb[data-aeon-state
|
|
2121
|
+
.aeonSwitch__thumb[data-aeon-state~="checked"] {
|
|
1906
2122
|
background: var(--colors-accent);
|
|
1907
2123
|
margin-left: auto;
|
|
1908
2124
|
}
|
|
@@ -2091,53 +2307,14 @@
|
|
|
2091
2307
|
.aeonScroll__viewport--maxW_full {
|
|
2092
2308
|
max-width: 100%;
|
|
2093
2309
|
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
@layer utilities{
|
|
2097
|
-
|
|
2098
|
-
.min-h_340 {
|
|
2099
|
-
min-height: 340px;
|
|
2100
|
-
}
|
|
2101
|
-
|
|
2102
|
-
.distance_980 {
|
|
2103
|
-
distance: 980px;
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
.origin_50\%_36\% {
|
|
2107
|
-
origin: 50% 36%;
|
|
2108
|
-
}
|
|
2109
2310
|
|
|
2110
|
-
.
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
.translate-z_-85 {
|
|
2115
|
-
--translate-z: -85;
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
.translate-z_-55 {
|
|
2119
|
-
--translate-z: -55;
|
|
2120
|
-
}
|
|
2121
|
-
|
|
2122
|
-
.rotate-y_42 {
|
|
2123
|
-
--rotate-y: 42;
|
|
2124
|
-
}
|
|
2125
|
-
|
|
2126
|
-
.rotate-y_-42 {
|
|
2127
|
-
--rotate-y: -42;
|
|
2128
|
-
}
|
|
2129
|
-
|
|
2130
|
-
.translate-z_0 {
|
|
2131
|
-
--translate-z: var(--spacing-0);
|
|
2311
|
+
.aeonSeparator__root--orientation_horizontal {
|
|
2312
|
+
width: 100%;
|
|
2313
|
+
height: 1px;
|
|
2132
2314
|
}
|
|
2133
|
-
|
|
2134
|
-
.rotate-x_56 {
|
|
2135
|
-
--rotate-x: 56;
|
|
2136
2315
|
}
|
|
2137
2316
|
|
|
2138
|
-
|
|
2139
|
-
--translate-z: var(--spacing-28);
|
|
2140
|
-
}
|
|
2317
|
+
@layer utilities{
|
|
2141
2318
|
|
|
2142
2319
|
.max-h_sm {
|
|
2143
2320
|
max-height: var(--sizes-sm);
|
|
@@ -214,7 +214,7 @@ export interface Conditions {
|
|
|
214
214
|
"_starting": string
|
|
215
215
|
/** `&[data-aeon-state=selected]` */
|
|
216
216
|
"_aeonSelected": string
|
|
217
|
-
/** `&[data-aeon-state
|
|
217
|
+
/** `&[data-aeon-state~="checked"]` */
|
|
218
218
|
"_aeonChecked": string
|
|
219
219
|
/** `&[data-aeon-state=open]` */
|
|
220
220
|
"_aeonOpen": string
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { assign, setup } from 'xstate'
|
|
2
|
+
|
|
3
|
+
export interface SliderContext {
|
|
4
|
+
value: number
|
|
5
|
+
min: number
|
|
6
|
+
max: number
|
|
7
|
+
step: number
|
|
8
|
+
disabled: boolean
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type SliderEvent =
|
|
12
|
+
| { type: 'SET_VALUE'; value: number }
|
|
13
|
+
| { type: 'STEP'; delta: number }
|
|
14
|
+
| { type: 'POINTER_DOWN' }
|
|
15
|
+
| { type: 'POINTER_UP' }
|
|
16
|
+
| { type: 'HOME' }
|
|
17
|
+
| { type: 'END' }
|
|
18
|
+
|
|
19
|
+
function clamp(value: number, min: number, max: number) {
|
|
20
|
+
return Math.min(max, Math.max(min, value))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function snap(value: number, min: number, max: number, step: number) {
|
|
24
|
+
if (step <= 0) return clamp(value, min, max)
|
|
25
|
+
const snapped = min + Math.round((value - min) / step) * step
|
|
26
|
+
return clamp(snapped, min, max)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Range control — idle vs dragging interaction regions. */
|
|
30
|
+
export const sliderMachine = setup({
|
|
31
|
+
types: {
|
|
32
|
+
context: {} as SliderContext,
|
|
33
|
+
events: {} as SliderEvent,
|
|
34
|
+
input: {} as SliderContext,
|
|
35
|
+
},
|
|
36
|
+
guards: {
|
|
37
|
+
notDisabled: ({ context }) => !context.disabled,
|
|
38
|
+
},
|
|
39
|
+
actions: {
|
|
40
|
+
setValue: assign({
|
|
41
|
+
value: ({ context, event }) =>
|
|
42
|
+
event.type === 'SET_VALUE'
|
|
43
|
+
? snap(event.value, context.min, context.max, context.step)
|
|
44
|
+
: context.value,
|
|
45
|
+
}),
|
|
46
|
+
step: assign({
|
|
47
|
+
value: ({ context, event }) =>
|
|
48
|
+
event.type === 'STEP'
|
|
49
|
+
? snap(context.value + event.delta, context.min, context.max, context.step)
|
|
50
|
+
: context.value,
|
|
51
|
+
}),
|
|
52
|
+
home: assign({
|
|
53
|
+
value: ({ context }) => context.min,
|
|
54
|
+
}),
|
|
55
|
+
end: assign({
|
|
56
|
+
value: ({ context }) => context.max,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
}).createMachine({
|
|
60
|
+
id: 'slider',
|
|
61
|
+
context: ({ input }) => ({
|
|
62
|
+
value: snap(input.value ?? input.min, input.min, input.max, input.step),
|
|
63
|
+
min: input.min,
|
|
64
|
+
max: input.max,
|
|
65
|
+
step: input.step,
|
|
66
|
+
disabled: input.disabled ?? false,
|
|
67
|
+
}),
|
|
68
|
+
initial: 'interaction',
|
|
69
|
+
states: {
|
|
70
|
+
interaction: {
|
|
71
|
+
initial: 'idle',
|
|
72
|
+
states: {
|
|
73
|
+
idle: {
|
|
74
|
+
on: {
|
|
75
|
+
SET_VALUE: { actions: 'setValue', guard: 'notDisabled' },
|
|
76
|
+
STEP: { actions: 'step', guard: 'notDisabled' },
|
|
77
|
+
HOME: { actions: 'home', guard: 'notDisabled' },
|
|
78
|
+
END: { actions: 'end', guard: 'notDisabled' },
|
|
79
|
+
POINTER_DOWN: { target: 'dragging', guard: 'notDisabled' },
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
dragging: {
|
|
83
|
+
on: {
|
|
84
|
+
SET_VALUE: { actions: 'setValue' },
|
|
85
|
+
POINTER_UP: 'idle',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
export { clamp as clampSliderValue, snap as snapSliderValue }
|
|
@@ -69,6 +69,13 @@ const Root = forwardRef<HTMLLabelElement, CheckboxRootProps>(function CheckboxRo
|
|
|
69
69
|
[resolvedChecked, disabled, checked, onCheckedChange, send],
|
|
70
70
|
)
|
|
71
71
|
|
|
72
|
+
const toggle = () => {
|
|
73
|
+
if (disabled) return
|
|
74
|
+
const next = !resolvedChecked
|
|
75
|
+
if (checked !== undefined) onCheckedChange?.(next)
|
|
76
|
+
else send({ type: 'TOGGLE' })
|
|
77
|
+
}
|
|
78
|
+
|
|
72
79
|
return (
|
|
73
80
|
<CheckboxCtx.Provider value={value}>
|
|
74
81
|
<label
|
|
@@ -79,6 +86,13 @@ const Root = forwardRef<HTMLLabelElement, CheckboxRootProps>(function CheckboxRo
|
|
|
79
86
|
state: resolvedChecked ? 'checked' : 'unchecked',
|
|
80
87
|
disabled,
|
|
81
88
|
}),
|
|
89
|
+
{
|
|
90
|
+
onClick: (e: MouseEvent<HTMLLabelElement>) => {
|
|
91
|
+
const target = e.target as HTMLElement
|
|
92
|
+
if (target.closest(`[data-aeon-part="${checkboxAnatomy.control}"]`)) return
|
|
93
|
+
toggle()
|
|
94
|
+
},
|
|
95
|
+
},
|
|
82
96
|
rest as ButtonHTMLAttributes<HTMLLabelElement>,
|
|
83
97
|
)}
|
|
84
98
|
>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { partAttrs } from '@aeon-ui/core'
|
|
2
|
+
import { forwardRef, type HTMLAttributes, type ReactNode, type RefObject } from 'react'
|
|
3
|
+
import { useAnchorPosition, type AnchorPlacement } from '../hooks/use-anchor-position.js'
|
|
4
|
+
import { renderPortalled, usePortalledMount } from '../hooks/use-portalled.js'
|
|
5
|
+
import { mergeProps } from '../utils/merge-props.js'
|
|
6
|
+
|
|
7
|
+
export interface FloatingPositionerProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
open: boolean
|
|
9
|
+
scope: string
|
|
10
|
+
part: string
|
|
11
|
+
triggerRef: RefObject<HTMLElement | null>
|
|
12
|
+
positionerRef: RefObject<HTMLDivElement | null>
|
|
13
|
+
portalled?: boolean
|
|
14
|
+
placement?: AnchorPlacement
|
|
15
|
+
matchAnchorWidth?: boolean
|
|
16
|
+
children?: ReactNode
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const FloatingPositioner = forwardRef<HTMLDivElement, FloatingPositionerProps>(
|
|
20
|
+
function FloatingPositioner(
|
|
21
|
+
{
|
|
22
|
+
open,
|
|
23
|
+
scope,
|
|
24
|
+
part,
|
|
25
|
+
triggerRef,
|
|
26
|
+
positionerRef,
|
|
27
|
+
portalled = true,
|
|
28
|
+
placement = 'bottom-start',
|
|
29
|
+
matchAnchorWidth = false,
|
|
30
|
+
children,
|
|
31
|
+
className,
|
|
32
|
+
...rest
|
|
33
|
+
},
|
|
34
|
+
ref,
|
|
35
|
+
) {
|
|
36
|
+
const mounted = usePortalledMount()
|
|
37
|
+
useAnchorPosition(triggerRef, positionerRef, open && portalled, placement, {
|
|
38
|
+
matchWidth: matchAnchorWidth,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (!open) return null
|
|
42
|
+
|
|
43
|
+
const node = (
|
|
44
|
+
<div
|
|
45
|
+
ref={(el) => {
|
|
46
|
+
positionerRef.current = el
|
|
47
|
+
if (typeof ref === 'function') ref(el)
|
|
48
|
+
else if (ref) ref.current = el
|
|
49
|
+
}}
|
|
50
|
+
className={className}
|
|
51
|
+
{...mergeProps(partAttrs(scope, part, { state: 'open' }), rest)}
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</div>
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
return renderPortalled(node, portalled, mounted)
|
|
58
|
+
},
|
|
59
|
+
)
|
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
import { useEffect, type RefObject } from 'react'
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
export type AnchorPlacement = 'bottom-start' | 'top-start'
|
|
4
|
+
|
|
5
|
+
export interface AnchorPositionOptions {
|
|
6
|
+
/** Match floating width to anchor (select listboxes). */
|
|
7
|
+
matchWidth?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Pin floating surface to trigger rect (for portalled overlays). */
|
|
4
11
|
export function useAnchorPosition(
|
|
5
12
|
anchorRef: RefObject<HTMLElement | null>,
|
|
6
13
|
floatingRef: RefObject<HTMLElement | null>,
|
|
7
14
|
active: boolean,
|
|
15
|
+
placement: AnchorPlacement = 'bottom-start',
|
|
16
|
+
options: AnchorPositionOptions = {},
|
|
8
17
|
) {
|
|
18
|
+
const { matchWidth = false } = options
|
|
19
|
+
|
|
9
20
|
useEffect(() => {
|
|
10
21
|
if (!active || !anchorRef.current || !floatingRef.current) return
|
|
11
22
|
|
|
@@ -13,21 +24,38 @@ export function useAnchorPosition(
|
|
|
13
24
|
const anchor = anchorRef.current
|
|
14
25
|
const floating = floatingRef.current
|
|
15
26
|
if (!anchor || !floating) return
|
|
27
|
+
|
|
16
28
|
const rect = anchor.getBoundingClientRect()
|
|
17
29
|
floating.style.position = 'fixed'
|
|
18
|
-
floating.style.top = `${rect.bottom + 4}px`
|
|
19
30
|
floating.style.left = `${rect.left}px`
|
|
20
|
-
floating.style.
|
|
21
|
-
floating.style.
|
|
31
|
+
floating.style.bottom = ''
|
|
32
|
+
floating.style.right = ''
|
|
22
33
|
floating.style.zIndex = '50'
|
|
34
|
+
|
|
35
|
+
if (matchWidth) {
|
|
36
|
+
floating.style.width = `${rect.width}px`
|
|
37
|
+
floating.style.minWidth = `${rect.width}px`
|
|
38
|
+
} else {
|
|
39
|
+
floating.style.width = ''
|
|
40
|
+
floating.style.minWidth = ''
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (placement === 'top-start') {
|
|
44
|
+
const height = floating.offsetHeight || floating.getBoundingClientRect().height
|
|
45
|
+
floating.style.top = `${rect.top - height - 4}px`
|
|
46
|
+
} else {
|
|
47
|
+
floating.style.top = `${rect.bottom + 4}px`
|
|
48
|
+
}
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
update()
|
|
52
|
+
requestAnimationFrame(update)
|
|
53
|
+
|
|
26
54
|
window.addEventListener('resize', update)
|
|
27
55
|
window.addEventListener('scroll', update, true)
|
|
28
56
|
return () => {
|
|
29
57
|
window.removeEventListener('resize', update)
|
|
30
58
|
window.removeEventListener('scroll', update, true)
|
|
31
59
|
}
|
|
32
|
-
}, [active, anchorRef, floatingRef])
|
|
60
|
+
}, [active, anchorRef, floatingRef, placement, matchWidth])
|
|
33
61
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
|
|
3
|
+
/** True when the primary input supports hover (typical mouse/trackpad). */
|
|
4
|
+
export function usePrefersHover() {
|
|
5
|
+
const [prefersHover, setPrefersHover] = useState(true)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
const mq = window.matchMedia('(hover: hover) and (pointer: fine)')
|
|
9
|
+
const update = () => setPrefersHover(mq.matches)
|
|
10
|
+
update()
|
|
11
|
+
mq.addEventListener('change', update)
|
|
12
|
+
return () => mq.removeEventListener('change', update)
|
|
13
|
+
}, [])
|
|
14
|
+
|
|
15
|
+
return prefersHover
|
|
16
|
+
}
|
|
@@ -3,6 +3,11 @@ export * from './switch/index.js'
|
|
|
3
3
|
export * from './checkbox/index.js'
|
|
4
4
|
export * from './dialog/index.js'
|
|
5
5
|
export * from './tabs/index.js'
|
|
6
|
+
export * from './separator/index.js'
|
|
7
|
+
export * from './progress/index.js'
|
|
8
|
+
export * from './radio-group/index.js'
|
|
9
|
+
export * from './slider/index.js'
|
|
10
|
+
export * from './pin-input/index.js'
|
|
6
11
|
export * from './accordion/index.js'
|
|
7
12
|
export * from './async/index.js'
|
|
8
13
|
export * from './field/index.js'
|