@a-type/ui 5.0.8 → 5.0.10

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.
Files changed (110) hide show
  1. package/dist/css/main.css +7 -7
  2. package/dist/esm/components/button/Button.d.ts +1 -1
  3. package/dist/esm/components/button/Button.js +1 -1
  4. package/dist/esm/components/button/Button.js.map +1 -1
  5. package/dist/esm/components/button/classes.js +2 -2
  6. package/dist/esm/components/button/classes.js.map +1 -1
  7. package/dist/esm/components/camera/Camera.js +1 -1
  8. package/dist/esm/components/camera/Camera.js.map +1 -1
  9. package/dist/esm/components/card/Card.js +3 -3
  10. package/dist/esm/components/card/Card.js.map +1 -1
  11. package/dist/esm/components/checkbox/Checkbox.js +2 -2
  12. package/dist/esm/components/checkbox/Checkbox.js.map +1 -1
  13. package/dist/esm/components/datePicker/Calendar.js +1 -1
  14. package/dist/esm/components/datePicker/Calendar.js.map +1 -1
  15. package/dist/esm/components/datePicker/DateRangePicker.js +1 -1
  16. package/dist/esm/components/datePicker/DateRangePicker.js.map +1 -1
  17. package/dist/esm/components/editableText/EditableText.js +1 -1
  18. package/dist/esm/components/editableText/EditableText.js.map +1 -1
  19. package/dist/esm/components/forms/SubmitButton.js +1 -1
  20. package/dist/esm/components/forms/SubmitButton.js.map +1 -1
  21. package/dist/esm/components/forms/TextField.js +2 -2
  22. package/dist/esm/components/forms/TextField.js.map +1 -1
  23. package/dist/esm/components/horizontalList/HorizontalList.js +2 -2
  24. package/dist/esm/components/horizontalList/HorizontalList.js.map +1 -1
  25. package/dist/esm/components/input/Input.js +1 -1
  26. package/dist/esm/components/input/Input.js.map +1 -1
  27. package/dist/esm/components/lightbox/Lightbox.js +1 -1
  28. package/dist/esm/components/lightbox/Lightbox.js.map +1 -1
  29. package/dist/esm/components/navBar/NavBar.js +5 -5
  30. package/dist/esm/components/navBar/NavBar.js.map +1 -1
  31. package/dist/esm/components/note/Note.js +1 -1
  32. package/dist/esm/components/note/Note.js.map +1 -1
  33. package/dist/esm/components/primitives/menus.js +1 -1
  34. package/dist/esm/components/primitives/menus.js.map +1 -1
  35. package/dist/esm/components/progress/Progress.js +5 -5
  36. package/dist/esm/components/progress/Progress.js.map +1 -1
  37. package/dist/esm/components/provider/Provider.d.ts +2 -1
  38. package/dist/esm/components/provider/Provider.js +3 -2
  39. package/dist/esm/components/provider/Provider.js.map +1 -1
  40. package/dist/esm/components/provider/TweakPane.d.ts +1 -0
  41. package/dist/esm/components/provider/TweakPane.js +36 -0
  42. package/dist/esm/components/provider/TweakPane.js.map +1 -0
  43. package/dist/esm/components/scrollArea/ScrollArea.js +5 -5
  44. package/dist/esm/components/scrollArea/ScrollArea.js.map +1 -1
  45. package/dist/esm/components/slider/Slider.d.ts +1 -0
  46. package/dist/esm/components/slider/Slider.js +9 -4
  47. package/dist/esm/components/slider/Slider.js.map +1 -1
  48. package/dist/esm/components/slider/Slider.stories.d.ts +1 -0
  49. package/dist/esm/components/switch/Switch.js +2 -2
  50. package/dist/esm/components/switch/Switch.js.map +1 -1
  51. package/dist/esm/components/tabs/tabs.js +1 -1
  52. package/dist/esm/components/tabs/tabs.js.map +1 -1
  53. package/dist/esm/components/toggleGroup/toggleGroup.js +1 -1
  54. package/dist/esm/components/toggleGroup/toggleGroup.js.map +1 -1
  55. package/dist/esm/components/viewport/Viewport.js +1 -1
  56. package/dist/esm/components/viewport/Viewport.js.map +1 -1
  57. package/dist/esm/hooks/useVisualViewportOffset.d.ts +2 -2
  58. package/dist/esm/hooks/useVisualViewportOffset.js +15 -9
  59. package/dist/esm/hooks/useVisualViewportOffset.js.map +1 -1
  60. package/dist/esm/themes.stories.js +2 -2
  61. package/dist/esm/themes.stories.js.map +1 -1
  62. package/dist/esm/uno/logic/properties.d.ts +6 -0
  63. package/dist/esm/uno/logic/properties.js +6 -0
  64. package/dist/esm/uno/logic/properties.js.map +1 -1
  65. package/dist/esm/uno/preflights/user.d.ts +2 -1
  66. package/dist/esm/uno/preflights/user.js +10 -2
  67. package/dist/esm/uno/preflights/user.js.map +1 -1
  68. package/dist/esm/uno/rules/focus.d.ts +3 -0
  69. package/dist/esm/uno/rules/focus.js +43 -0
  70. package/dist/esm/uno/rules/focus.js.map +1 -0
  71. package/dist/esm/uno/rules/index.d.ts +1 -1
  72. package/dist/esm/uno/rules/index.js +2 -0
  73. package/dist/esm/uno/rules/index.js.map +1 -1
  74. package/dist/esm/uno/theme/colors.d.ts +1 -1
  75. package/dist/esm/uno/theme/colors.js +3 -3
  76. package/dist/esm/uno/theme/colors.js.map +1 -1
  77. package/package.json +1 -1
  78. package/src/__screenshots__/themes.snapshots.tsx/snapshot-chromium-win32.png +0 -0
  79. package/src/components/button/Button.tsx +2 -1
  80. package/src/components/button/classes.tsx +3 -4
  81. package/src/components/camera/Camera.tsx +5 -5
  82. package/src/components/card/Card.tsx +6 -6
  83. package/src/components/checkbox/Checkbox.tsx +2 -2
  84. package/src/components/datePicker/Calendar.tsx +2 -2
  85. package/src/components/datePicker/DateRangePicker.tsx +3 -3
  86. package/src/components/editableText/EditableText.tsx +2 -1
  87. package/src/components/forms/SubmitButton.tsx +0 -1
  88. package/src/components/forms/TextField.tsx +2 -2
  89. package/src/components/horizontalList/HorizontalList.tsx +2 -1
  90. package/src/components/input/Input.tsx +2 -2
  91. package/src/components/lightbox/Lightbox.tsx +1 -1
  92. package/src/components/navBar/NavBar.tsx +101 -101
  93. package/src/components/note/Note.tsx +3 -3
  94. package/src/components/primitives/menus.tsx +1 -1
  95. package/src/components/progress/Progress.tsx +101 -101
  96. package/src/components/provider/Provider.tsx +4 -0
  97. package/src/components/provider/TweakPane.tsx +139 -0
  98. package/src/components/scrollArea/ScrollArea.tsx +15 -15
  99. package/src/components/slider/Slider.tsx +103 -97
  100. package/src/components/switch/Switch.tsx +38 -38
  101. package/src/components/tabs/tabs.tsx +2 -1
  102. package/src/components/toggleGroup/toggleGroup.tsx +1 -1
  103. package/src/components/viewport/Viewport.tsx +1 -1
  104. package/src/hooks/useVisualViewportOffset.ts +15 -13
  105. package/src/themes.stories.tsx +2 -0
  106. package/src/uno/logic/properties.ts +6 -0
  107. package/src/uno/preflights/user.ts +16 -2
  108. package/src/uno/rules/focus.ts +46 -0
  109. package/src/uno/rules/index.ts +2 -0
  110. package/src/uno/theme/colors.ts +7 -3
@@ -1,97 +1,103 @@
1
- import { Slider as BaseSlider, SliderRootProps } from '@base-ui/react/slider';
2
- import clsx from 'clsx';
3
- import { Ref } from 'react';
4
- import { withClassName } from '../../hooks.js';
5
- import { PaletteName } from '../../uno/index.js';
6
-
7
- export const SliderRoot = withClassName(
8
- BaseSlider.Root,
9
- 'layer-components:w-full',
10
- );
11
-
12
- export const SliderControl = withClassName(
13
- BaseSlider.Control,
14
- 'layer-components:(relative flex items-center select-none touch-none w-full h-30px translate-z-0)',
15
- 'layer-variants:[&[data-orientation=vertical]]:(flex-col h-full w-30px)',
16
- );
17
- export const SliderTrack = withClassName(
18
- BaseSlider.Track,
19
- 'layer-components:(bg-transparent relative grow rounded-lg h-7px ring-1 ring-black transition-colors select-none)',
20
- 'layer-variants:[&[data-orientation=vertical]]:(w-7px h-full flex-1)',
21
- );
22
- export const SliderRange = withClassName(
23
- BaseSlider.Indicator,
24
- 'layer-components:(bg-main rounded-lg transition-colors)',
25
- 'layer-variants:[&[data-orientation=vertical]]:(bg-main)',
26
- );
27
- export const SliderThumb = withClassName(
28
- BaseSlider.Thumb,
29
- 'layer-components:(flex items-center justify-center leading-none cursor-pointer w-5 h-5 bg-white shadow-sm rounded-lg ring-2 ring-black touch-none transition-color)',
30
- 'layer-components:hover:(shadow-md)',
31
- 'layer-components:active:(shadow-lg ring-4 ring-main-dark ring-opacity-50 bg-main-light)',
32
- 'layer-components:focus-visible:(shadow-lg ring-4 ring-accent ring-opacity-50 outline-none bg-main-light)',
33
- 'layer-components:focus:(outline-none)',
34
- 'layer-components:disabled:(opacity-50)',
35
- );
36
-
37
- export const SliderBase = ({
38
- children,
39
- color,
40
- className,
41
- ...props
42
- }: SliderProps) => {
43
- return (
44
- <SliderRoot
45
- {...props}
46
- className={clsx(color && `palette-${color}`, className)}
47
- >
48
- <SliderControl>{children}</SliderControl>
49
- </SliderRoot>
50
- );
51
- };
52
-
53
- /**
54
- * A pre-configured Slider UI component. Meant to compose
55
- * under Slider.Root when you want to use Slider.Value, too
56
- */
57
- export const SliderUi = ({
58
- label,
59
- className,
60
- }: {
61
- label?: string;
62
- className?: string;
63
- }) => (
64
- <Slider.Control className={className}>
65
- <SliderTrack>
66
- <SliderRange />
67
- <SliderThumb aria-label={label} />
68
- </SliderTrack>
69
- </Slider.Control>
70
- );
71
-
72
- export interface SliderProps extends SliderRootProps {
73
- color?: PaletteName;
74
- ref?: Ref<HTMLDivElement>;
75
- }
76
-
77
- export const Slider = Object.assign(
78
- function Slider(props: SliderProps) {
79
- return (
80
- <SliderRoot {...props}>
81
- <SliderUi />
82
- </SliderRoot>
83
- );
84
- },
85
- {
86
- Root: SliderRoot,
87
- Base: SliderBase,
88
- Control: SliderControl,
89
- Track: SliderTrack,
90
- /** @deprecated - use Indicator */
91
- Range: SliderRange,
92
- Indicator: SliderRange,
93
- Thumb: SliderThumb,
94
- Value: BaseSlider.Value,
95
- Ui: SliderUi,
96
- },
97
- );
1
+ import { Slider as BaseSlider, SliderRootProps } from '@base-ui/react/slider';
2
+ import clsx from 'clsx';
3
+ import { Ref } from 'react';
4
+ import { withClassName } from '../../hooks.js';
5
+ import { PaletteName } from '../../uno/index.js';
6
+
7
+ export const SliderRoot = withClassName(
8
+ BaseSlider.Root,
9
+ 'layer-components:w-full',
10
+ );
11
+
12
+ export const SliderControl = withClassName(
13
+ BaseSlider.Control,
14
+ 'layer-components:(relative h-30px w-full flex translate-z-0 touch-none select-none items-center)',
15
+ 'layer-variants:[&[data-orientation=vertical]]:(h-full w-30px flex-col)',
16
+ );
17
+ export const SliderTrack = withClassName(
18
+ BaseSlider.Track,
19
+ 'layer-components:(relative h-7px grow select-none rounded-lg ring-1 transition-colors bg-transparent ring-black)',
20
+ 'layer-variants:[&[data-orientation=vertical]]:(h-full w-7px flex-1)',
21
+ );
22
+ export const SliderRange = withClassName(
23
+ BaseSlider.Indicator,
24
+ 'layer-components:(rounded-lg transition-colors bg-main)',
25
+ 'layer-variants:[&[data-orientation=vertical]]:(bg-main)',
26
+ );
27
+ export const SliderThumb = withClassName(
28
+ BaseSlider.Thumb,
29
+ 'layer-components:(h-5 w-5 flex cursor-pointer touch-none items-center justify-center rounded-lg leading-none shadow-sm ring-2 transition-color bg-white ring-black)',
30
+ 'layer-components:hover:(shadow-md)',
31
+ 'layer-components:active:(shadow-lg ring-4 ring-opacity-50 bg-main-light ring-main-dark)',
32
+ 'layer-components:focus-visible:(shadow-lg bg-main-light)',
33
+ 'foc-lg',
34
+ 'layer-components:focus:(outline-none)',
35
+ 'layer-components:disabled:(opacity-50)',
36
+ );
37
+
38
+ export const SliderBase = ({
39
+ children,
40
+ color,
41
+ className,
42
+ ...props
43
+ }: SliderProps) => {
44
+ return (
45
+ <SliderRoot
46
+ {...props}
47
+ className={clsx(color && `palette-${color}`, className)}
48
+ >
49
+ <SliderControl>{children}</SliderControl>
50
+ </SliderRoot>
51
+ );
52
+ };
53
+
54
+ /**
55
+ * A pre-configured Slider UI component. Meant to compose
56
+ * under Slider.Root when you want to use Slider.Value, too
57
+ */
58
+ export const SliderUi = ({
59
+ label,
60
+ className,
61
+ }: {
62
+ label?: string;
63
+ className?: string;
64
+ }) => (
65
+ <Slider.Control className={className}>
66
+ <SliderTrack>
67
+ <SliderRange />
68
+ <SliderThumb aria-label={label} />
69
+ </SliderTrack>
70
+ </Slider.Control>
71
+ );
72
+
73
+ export interface SliderProps extends SliderRootProps {
74
+ color?: PaletteName;
75
+ ref?: Ref<HTMLDivElement>;
76
+ }
77
+
78
+ export const Slider = Object.assign(
79
+ function Slider(props: SliderProps) {
80
+ return (
81
+ <SliderRoot {...props}>
82
+ <SliderUi />
83
+ </SliderRoot>
84
+ );
85
+ },
86
+ {
87
+ Root: SliderRoot,
88
+ Base: SliderBase,
89
+ Control: SliderControl,
90
+ Track: SliderTrack,
91
+ /** @deprecated - use Indicator */
92
+ Range: SliderRange,
93
+ Indicator: SliderRange,
94
+ Thumb: SliderThumb,
95
+ Value: BaseSlider.Value,
96
+ Ui: SliderUi,
97
+
98
+ getSingle: (v: number | readonly number[]) => {
99
+ if (Array.isArray(v)) return v[0];
100
+ return v;
101
+ },
102
+ },
103
+ );
@@ -1,38 +1,38 @@
1
- import { Switch as BaseSwitch, SwitchRootProps } from '@base-ui/react/switch';
2
- import { withClassName } from '../../hooks/withClassName.js';
3
-
4
- const SwitchRoot = withClassName(
5
- BaseSwitch.Root,
6
- 'layer-components:(unset appearance-none flex transition-all cursor-pointer w-42px h-25px p-3px bg-gray-light rounded-lg relative border-default flex-shrink-0 shadow-sm shadow-inset)',
7
- 'layer-components:data-[checked]:bg-main',
8
- 'layer-components:hover:[&:not(:disabled)]:(bg-lighten-2 ring-2 ring-bg)',
9
- 'layer-components:active:[&:not(:disabled)]:(bg-darken-1 ring-4)',
10
- 'layer-components:focus:outline-none',
11
- 'layer-components:focus-visible:(ring-4 ring-accent outline-off)',
12
- );
13
-
14
- const SwitchThumb = withClassName(
15
- BaseSwitch.Thumb,
16
- 'layer-components:(block aspect-1 h-full bg-white rounded-lg border-default transition-transform will-change-transform shadow-sm)',
17
- 'layer-components:data-[checked]:(translate-x-1rem)',
18
- 'layer-components:data-[checked]:before:(content-["✓"] flex items-center justify-center absolute top-0 left-0 w-full h-full text-xs text-main-ink)',
19
- );
20
-
21
- export const Switch = Object.assign(
22
- function Switch({
23
- ref,
24
- ...props
25
- }: SwitchRootProps & {
26
- ref?: React.Ref<HTMLButtonElement>;
27
- }) {
28
- return (
29
- <SwitchRoot {...props} ref={ref}>
30
- <SwitchThumb />
31
- </SwitchRoot>
32
- );
33
- },
34
- {
35
- Root: SwitchRoot,
36
- Thumb: SwitchThumb,
37
- },
38
- );
1
+ import { Switch as BaseSwitch, SwitchRootProps } from '@base-ui/react/switch';
2
+ import { withClassName } from '../../hooks/withClassName.js';
3
+
4
+ const SwitchRoot = withClassName(
5
+ BaseSwitch.Root,
6
+ 'layer-components:(unset relative h-25px w-42px flex flex-shrink-0 cursor-pointer appearance-none border-default rounded-lg p-3px shadow-sm shadow-inset transition-all bg-gray-light)',
7
+ 'layer-components:data-[checked]:bg-main',
8
+ 'layer-components:hover:[&:not(:disabled)]:(ring-2 bg-lighten-2 ring-bg)',
9
+ 'layer-components:active:[&:not(:disabled)]:(ring-4 bg-darken-1)',
10
+ 'layer-components:focus:outline-none',
11
+ 'layer-components:foc',
12
+ );
13
+
14
+ const SwitchThumb = withClassName(
15
+ BaseSwitch.Thumb,
16
+ 'layer-components:(block aspect-1 h-full border-default rounded-lg shadow-sm transition-transform will-change-transform bg-white)',
17
+ 'layer-components:data-[checked]:(translate-x-1rem)',
18
+ 'layer-components:data-[checked]:before:(absolute left-0 top-0 h-full w-full flex items-center justify-center text-xs text-main-ink content-["✓"])',
19
+ );
20
+
21
+ export const Switch = Object.assign(
22
+ function Switch({
23
+ ref,
24
+ ...props
25
+ }: SwitchRootProps & {
26
+ ref?: React.Ref<HTMLButtonElement>;
27
+ }) {
28
+ return (
29
+ <SwitchRoot {...props} ref={ref}>
30
+ <SwitchThumb />
31
+ </SwitchRoot>
32
+ );
33
+ },
34
+ {
35
+ Root: SwitchRoot,
36
+ Thumb: SwitchThumb,
37
+ },
38
+ );
@@ -24,7 +24,8 @@ export const TabsTrigger = withClassName(
24
24
  'layer-components:(min-h-touch min-w-100px flex-shrink-0)',
25
25
  'layer-components:(border rounded-lg bg-transparent border-transparent)',
26
26
  'layer-components:hover:[&[data-state=inactive]]:(ring-4 bg-gray-light bg-darken-1 ring-bg)',
27
- 'layer-components:focus-visible:(outline-off border ring-4 bg-darken-1 border-black ring-accent)',
27
+ 'layer-components:focus-visible:(border bg-darken-1 border-black)',
28
+ 'foc',
28
29
  'data-[active]:(cursor-default)',
29
30
  );
30
31
 
@@ -14,7 +14,7 @@ export const ToggleGroupItem = withClassName(
14
14
  'layer-components:(flex cursor-pointer items-center justify-center border-1 rounded-lg border-solid px-md py-sm text-nowrap transition-all color-black bg-gray/30 border-transparent)',
15
15
  'layer-components:hover:(ring-2 bg-lighten-2 border-main-ink/20 ring-bg)',
16
16
  'layer-components:active:(ring-4 bg-darken-1 border-main-ink/30)',
17
- 'layer-components:focus-visible:(outline-off ring-4 ring-accent)',
17
+ 'layer-components:foc',
18
18
  'layer-components:data-[pressed]:(border-default shadow-sm bg-main hover:border-default)',
19
19
  );
20
20
 
@@ -118,7 +118,7 @@ export function ViewportRoot({
118
118
  <div
119
119
  className={clsx(
120
120
  'contain-strict relative h-full w-full flex-1 touch-none select-none overflow-hidden',
121
- 'focus-visible:(ring ring-accent)',
121
+ 'foc',
122
122
  className,
123
123
  )}
124
124
  {...gestureProps}
@@ -181,14 +181,18 @@ function useReactToViewportChanges(
181
181
  }
182
182
 
183
183
  export interface VirtualKeyboardFocusOptions {
184
- focusElementTypes?: string[];
184
+ shouldScrollIntoView?: (el: Element) => boolean;
185
+ }
186
+ function defaultShouldScrollIntoView(el: Element) {
187
+ if (['input', 'textarea', 'select'].includes(el.tagName.toLowerCase()))
188
+ return true;
189
+ if (el.hasAttribute('contenteditable')) return true;
190
+ return false;
185
191
  }
186
192
  export function useVirtualKeyboardFocusBehavior({
187
- focusElementTypes = ['input', 'textarea', 'select'],
193
+ shouldScrollIntoView = defaultShouldScrollIntoView,
188
194
  }: VirtualKeyboardFocusOptions = {}) {
189
- const stableFocusElementTypes = focusElementTypes
190
- .map((type) => type.toLowerCase())
191
- .join(',');
195
+ const stableShouldScroll = useStableCallback(shouldScrollIntoView);
192
196
  useEffect(() => {
193
197
  if (typeof navigator === 'undefined' || typeof window === 'undefined') {
194
198
  return;
@@ -204,21 +208,19 @@ export function useVirtualKeyboardFocusBehavior({
204
208
 
205
209
  const virtualKeyboard = navigator.virtualKeyboard as any;
206
210
 
207
- const matchElements = stableFocusElementTypes.split(',');
208
-
209
211
  function update() {
210
212
  const open = virtualKeyboard.boundingRect.height > 0;
211
213
  if (open) {
212
214
  console.log('keyboard opened');
213
215
  }
214
216
  const activeElement = document.activeElement;
215
- if (
216
- activeElement &&
217
- matchElements.includes(activeElement.tagName.toLowerCase())
218
- ) {
217
+ if (activeElement && stableShouldScroll(activeElement)) {
219
218
  setTimeout(() => {
220
219
  console.log('scroll focused element');
221
- activeElement.scrollIntoView(true);
220
+ activeElement.scrollIntoView({
221
+ behavior: 'instant',
222
+ block: 'start',
223
+ });
222
224
  }, 10);
223
225
  }
224
226
  }
@@ -227,5 +229,5 @@ export function useVirtualKeyboardFocusBehavior({
227
229
  return () => {
228
230
  virtualKeyboard.removeEventListener('geometrychange', update);
229
231
  };
230
- }, [stableFocusElementTypes]);
232
+ }, [stableShouldScroll]);
231
233
  }
@@ -35,6 +35,7 @@ import {
35
35
  Progress,
36
36
  Provider,
37
37
  Select,
38
+ Slider,
38
39
  TextSkeleton,
39
40
  ToggleGroup,
40
41
  Tooltip,
@@ -242,6 +243,7 @@ export function DemoUI({ className }: { className?: string }) {
242
243
  onChange={() => {}}
243
244
  />
244
245
  <Progress value={50} className="m-auto" />
246
+ <Slider defaultValue={50} min={0} max={100} />
245
247
  <Box surface color="primary" p gap d="col">
246
248
  <H1>Primary surface</H1>
247
249
  <H2>Primary surface</H2>
@@ -17,6 +17,7 @@ export const PROPS = {
17
17
  SERIF: '--u-font-serif',
18
18
  TITLE: '--u-font-title',
19
19
  },
20
+ FOCUS_COLOR: '--u-focus-color',
20
21
  },
21
22
 
22
23
  PALETTE: {
@@ -134,5 +135,10 @@ export const PROPS = {
134
135
  SHADOW_COLOR: '--un-shadow-color',
135
136
  SHADOW_OPACITY: '--un-shadow-opacity',
136
137
  SHADOW: '--un-shadow',
138
+ RING_WIDTH: '--un-ring-width',
139
+ RING_STYLE: '--un-ring-style',
140
+ RING_OFFSET_SHADOW: '--un-ring-offset-shadow',
141
+ RING_SHADOW: '--un-ring-shadow',
142
+ RING_INSET: '--un-ring-inset',
137
143
  },
138
144
  };
@@ -18,10 +18,11 @@ export interface UserPreflightOptions {
18
18
  cornerScale?: number;
19
19
  shadowSpread?: number;
20
20
  fontSize?: number;
21
+ focusColor?: 'black' | 'primary' | 'accent' | 'gray';
21
22
  }
22
23
 
23
24
  export const userPreflight = ({
24
- saturation = 50,
25
+ saturation = 0.5,
25
26
  primaryHue = defaultPresetHues.lemon.sourceHue,
26
27
  accentHue = defaultPresetHues.leek.sourceHue,
27
28
  spacingScale = 1,
@@ -30,13 +31,15 @@ export const userPreflight = ({
30
31
  shadowSpread = 1,
31
32
  namedHues,
32
33
  fontSize = 16,
34
+ focusColor = 'accent',
33
35
  }: UserPreflightOptions) =>
34
36
  preflight({
35
37
  getCSS: () => {
38
+ const sat = saturation > 1 ? saturation / 100 : saturation;
36
39
  return `
37
40
  @layer preflightBase {
38
41
  :root {
39
- ${PROPS.USER.SATURATION}: ${saturation / 100};
42
+ ${PROPS.USER.SATURATION}: ${sat};
40
43
  ${PROPS.USER.CORNER_SCALE}: ${cornerScale};
41
44
  ${PROPS.USER.SPACING_SCALE}: ${spacingScale};
42
45
  ${PROPS.USER.BORDER_SCALE}: ${borderScale};
@@ -44,6 +47,7 @@ export const userPreflight = ({
44
47
 
45
48
  ${PROPS.USER.COLOR.PRIMARY_HUE}: ${primaryHue};
46
49
  ${PROPS.USER.COLOR.ACCENT_HUE}: ${accentHue};
50
+ ${PROPS.USER.FOCUS_COLOR}: var(${focusColors[focusColor]});
47
51
  ${
48
52
  namedHues
49
53
  ? Object.entries(namedHues)
@@ -65,3 +69,13 @@ export const userPreflight = ({
65
69
  `;
66
70
  },
67
71
  });
72
+
73
+ const focusColors: Record<
74
+ NonNullable<UserPreflightOptions['focusColor']>,
75
+ string
76
+ > = {
77
+ black: PROPS.MODE.BLACK,
78
+ primary: PROPS.PALETTE.NAMED_SHADES('primary').DARK,
79
+ accent: PROPS.PALETTE.NAMED_SHADES('accent').MID,
80
+ gray: PROPS.PALETTE.GRAY_SHADES.DARK,
81
+ };
@@ -0,0 +1,46 @@
1
+ import { Theme } from '@unocss/preset-mini';
2
+ import { Rule } from 'unocss';
3
+ import { PROPS } from '../logic/properties.js';
4
+
5
+ export const focusRules: Rule<Theme>[] = [
6
+ [
7
+ /^foc(?:-effect)?(-lg)?$/,
8
+ function* ([, size], { symbols }) {
9
+ yield {
10
+ [symbols.selector]: (selector) =>
11
+ `${selector}:focus-visible:not([data-focus-clicked]), ${selector}[data-focus-visible]`,
12
+ [PROPS.BUILT_IN.RING_COLOR]: `var(${PROPS.USER.FOCUS_COLOR})`,
13
+ [PROPS.BUILT_IN.RING_WIDTH]: size === '-lg' ? '4px' : '2px',
14
+ [PROPS.BUILT_IN.RING_STYLE]: 'solid',
15
+ [PROPS.BUILT_IN
16
+ .RING_SHADOW]: `var(${PROPS.BUILT_IN.RING_INSET}) 0 0 0 var(${PROPS.BUILT_IN.RING_WIDTH}) var(${PROPS.BUILT_IN.RING_COLOR})`,
17
+ outline: 'none',
18
+ 'box-shadow': `var(${PROPS.BUILT_IN.RING_OFFSET_SHADOW}), var(${PROPS.BUILT_IN.RING_SHADOW}), var(${PROPS.BUILT_IN.SHADOW})`,
19
+ };
20
+ yield {
21
+ [symbols.selector]: (selector) => `${selector}:focus`,
22
+ outline: 'none',
23
+ };
24
+ },
25
+ ],
26
+ [
27
+ /^foc-contained(-lg)?$/,
28
+ function* ([, size], { symbols }) {
29
+ yield {
30
+ [symbols.selector]: (selector) =>
31
+ `${selector}:has(:focus-visible:not([data-focus-clicked])), ${selector}:has([data-focus-visible])`,
32
+ [PROPS.BUILT_IN.RING_COLOR]: `var(${PROPS.USER.FOCUS_COLOR})`,
33
+ [PROPS.BUILT_IN.RING_WIDTH]: size === '-lg' ? '4px' : '2px',
34
+ [PROPS.BUILT_IN.RING_STYLE]: 'solid',
35
+ [PROPS.BUILT_IN
36
+ .RING_SHADOW]: `0 0 0 var(${PROPS.BUILT_IN.RING_WIDTH}) var(${PROPS.BUILT_IN.RING_COLOR})`,
37
+ outline: 'none',
38
+ 'box-shadow': `var(${PROPS.BUILT_IN.RING_OFFSET_SHADOW}), var(${PROPS.BUILT_IN.RING_SHADOW})`,
39
+ };
40
+ yield {
41
+ [symbols.selector]: (selector) => `${selector}>*:focus`,
42
+ outline: 'none',
43
+ };
44
+ },
45
+ ],
46
+ ];
@@ -2,6 +2,7 @@ import { anchorRules } from './anchor.js';
2
2
  import { clipPathRules } from './clipPath.js';
3
3
  import { colorRules } from './color.js';
4
4
  import { containerRules } from './container.js';
5
+ import { focusRules } from './focus.js';
5
6
  import { overflowRules } from './overflow.js';
6
7
  import { shadowRules } from './shadow.js';
7
8
  import { utilRules } from './util.js';
@@ -21,4 +22,5 @@ export const rules = [
21
22
  ...utilRules,
22
23
  ...clipPathRules,
23
24
  ...containerRules,
25
+ ...focusRules,
24
26
  ];
@@ -4,7 +4,9 @@ import { PreflightConfig } from '../preflights/index.js';
4
4
 
5
5
  const contrastClamp = 'clamp(0, (0.36 / y - 1) * infinity, 1)';
6
6
 
7
- export function makeThemeColors(options: PreflightConfig): Theme['colors'] {
7
+ export function makeThemeColors({
8
+ namedHues,
9
+ }: PreflightConfig): Theme['colors'] {
8
10
  return {
9
11
  none: 'transparent',
10
12
  transparent: 'transparent',
@@ -26,12 +28,14 @@ export function makeThemeColors(options: PreflightConfig): Theme['colors'] {
26
28
  attention: shadesOf(PROPS.PALETTE.NAMED_SHADES('attention')),
27
29
  success: shadesOf(PROPS.PALETTE.NAMED_SHADES('success')),
28
30
 
31
+ focus: `var(${PROPS.USER.FOCUS_COLOR})`,
32
+
29
33
  main: shadesOf(PROPS.PALETTE.SHADES),
30
34
  gray: shadesOf(PROPS.PALETTE.GRAY_SHADES),
31
35
 
32
- ...(options.namedHues
36
+ ...(namedHues
33
37
  ? Object.fromEntries(
34
- Object.keys(options.namedHues).map(([name]) => [
38
+ Object.keys(namedHues).map(([name]) => [
35
39
  name,
36
40
  shadesOf(PROPS.PALETTE.NAMED_SHADES(name)),
37
41
  ]),