@a-type/ui 5.0.4 → 5.0.6

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 (48) hide show
  1. package/dist/css/main.css +5 -4
  2. package/dist/esm/components/box/Box.js +1 -1
  3. package/dist/esm/components/box/Box.js.map +1 -1
  4. package/dist/esm/components/box/Box.stories.js +1 -1
  5. package/dist/esm/components/box/Box.stories.js.map +1 -1
  6. package/dist/esm/components/button/classes.js +1 -1
  7. package/dist/esm/components/button/classes.js.map +1 -1
  8. package/dist/esm/components/card/Card.stories.js +1 -1
  9. package/dist/esm/components/card/Card.stories.js.map +1 -1
  10. package/dist/esm/components/forms/TextField.js +2 -1
  11. package/dist/esm/components/forms/TextField.js.map +1 -1
  12. package/dist/esm/components/input/Input.js +1 -1
  13. package/dist/esm/components/input/Input.js.map +1 -1
  14. package/dist/esm/components/popover/Popover.d.ts +7 -2
  15. package/dist/esm/components/popover/Popover.js +7 -2
  16. package/dist/esm/components/popover/Popover.js.map +1 -1
  17. package/dist/esm/components/popover/Popover.stories.js +1 -1
  18. package/dist/esm/components/popover/Popover.stories.js.map +1 -1
  19. package/dist/esm/components/toasts/toasts.js +3 -3
  20. package/dist/esm/components/toasts/toasts.js.map +1 -1
  21. package/dist/esm/components/toasts/toasts.stories.js +1 -1
  22. package/dist/esm/components/toasts/toasts.stories.js.map +1 -1
  23. package/dist/esm/hooks/useVisualViewportOffset.d.ts +4 -0
  24. package/dist/esm/hooks/useVisualViewportOffset.js +32 -0
  25. package/dist/esm/hooks/useVisualViewportOffset.js.map +1 -1
  26. package/dist/esm/keyboard.stories.d.ts +14 -0
  27. package/dist/esm/keyboard.stories.js +20 -0
  28. package/dist/esm/keyboard.stories.js.map +1 -0
  29. package/dist/esm/systems/GroupScale.d.ts +2 -1
  30. package/dist/esm/systems/GroupScale.js +8 -2
  31. package/dist/esm/systems/GroupScale.js.map +1 -1
  32. package/dist/esm/uno/preflights/index.js +2 -0
  33. package/dist/esm/uno/preflights/index.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/components/box/Box.stories.tsx +6 -8
  36. package/src/components/box/Box.tsx +4 -1
  37. package/src/components/button/classes.tsx +1 -1
  38. package/src/components/card/Card.stories.tsx +48 -30
  39. package/src/components/forms/TextField.tsx +2 -1
  40. package/src/components/input/Input.tsx +1 -1
  41. package/src/components/popover/Popover.stories.tsx +1 -0
  42. package/src/components/popover/Popover.tsx +25 -5
  43. package/src/components/toasts/toasts.stories.tsx +3 -0
  44. package/src/components/toasts/toasts.tsx +4 -4
  45. package/src/hooks/useVisualViewportOffset.ts +47 -0
  46. package/src/keyboard.stories.tsx +34 -0
  47. package/src/systems/GroupScale.tsx +16 -2
  48. package/src/uno/preflights/index.ts +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/uno/preflights/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAyB,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,cAAc,EAAyB,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAwB,MAAM,WAAW,CAAC;AAOhE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAuB,EAAoB,EAAE,CAAC;IACxE,cAAc;IACd,aAAa;IACb,eAAe,CAAC,MAAM,CAAC;IACvB,cAAc,CAAC,MAAM,CAAC;IACtB,aAAa,CAAC,MAAM,CAAC;IACrB,cAAc,CAAC,MAAM,CAAC;IACtB,mBAAmB;IACnB,aAAa,CAAC,MAAM,CAAC;CACrB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/uno/preflights/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAyB,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,cAAc,EAAyB,MAAM,YAAY,CAAC;AACnE,OAAO,EAAE,eAAe,EAA0B,MAAM,cAAc,CAAC;AACvE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAwB,MAAM,WAAW,CAAC;AAOhE,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,MAAuB,EAAoB,EAAE,CAAC;IACxE,cAAc;IACd,aAAa;IACb,eAAe,CAAC,MAAM,CAAC;IACvB,cAAc,CAAC,MAAM,CAAC;IACtB,aAAa,CAAC,MAAM,CAAC;IACrB,cAAc,CAAC,MAAM,CAAC;IACtB,mBAAmB;IACnB,aAAa,CAAC,MAAM,CAAC;IACrB,iBAAiB;CACjB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "5.0.4",
3
+ "version": "5.0.6",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "url": "https://github.com/a-type/ui"
@@ -93,18 +93,16 @@ export const Default: Story = {
93
93
  export const NestedContainers: Story = {
94
94
  render(args) {
95
95
  return (
96
- <Box {...args} container surface color="accent" direction="col">
97
- <Box {...args} container surface color="primary">
98
- <Box {...args} surface>
99
- <Button>Button</Button>
100
- <Button>Button</Button>
101
- </Box>
102
- <Box {...args} surface color="attention">
96
+ <Box {...args} p gap container surface color="accent" direction="col">
97
+ <Box {...args} p gap container surface items="center" color="primary">
98
+ <Button>Button</Button>
99
+ <Button>Button</Button>
100
+ <Box {...args} p gap surface color="attention">
103
101
  <Button>Button</Button>
104
102
  <Button>Button</Button>
105
103
  </Box>
106
104
  </Box>
107
- <Box {...args} surface color="gray">
105
+ <Box {...args} p gap surface color="gray">
108
106
  <Button>Button</Button>
109
107
  <Button>Button</Button>
110
108
  </Box>
@@ -89,7 +89,10 @@ export function Box({
89
89
  ref,
90
90
  ...rest
91
91
  }: BoxProps) {
92
- const style = useGroupScaleStyles(userStyle);
92
+ const style = useGroupScaleStyles(
93
+ userStyle,
94
+ container === 'reset' ? 1 : undefined,
95
+ );
93
96
 
94
97
  const items = itemsSolo ?? align?.split(' ')[0];
95
98
  const justify = justifySolo ?? align?.split(' ')[1];
@@ -36,7 +36,7 @@ export function getButtonClassName({
36
36
  'layer-components:(shadow-sm ring-bg)',
37
37
  'layer-components:color-contrast',
38
38
 
39
- emphasis === 'ghost' && 'layer-variants:(border-none shadow-none)',
39
+ emphasis === 'ghost' && 'layer-variants:(border-transparent shadow-none)',
40
40
  emphasis === 'contrast' &&
41
41
  'layer-variants:border-bg layer-variants:hover:bg-gray-ink',
42
42
 
@@ -262,36 +262,54 @@ export const VisuallyFocused: Story = {
262
262
  export const CardsInBox: Story = {
263
263
  render() {
264
264
  return (
265
- <Box container p gap>
266
- <CardRoot>
267
- <CardMain onClick={() => {}}>
268
- <CardTitle>Card Title</CardTitle>
269
- <CardContent>Other stuff</CardContent>
270
- </CardMain>
271
- <CardFooter>
272
- <CardActions>
273
- <Button size="small">Button</Button>
274
- <Button emphasis="ghost">
275
- <Icon name="placeholder" />
276
- </Button>
277
- </CardActions>
278
- </CardFooter>
279
- </CardRoot>
280
- <CardRoot>
281
- <CardMain onClick={() => {}}>
282
- <CardTitle>Card Title</CardTitle>
283
- <CardContent>Other stuff</CardContent>
284
- </CardMain>
285
- <CardFooter>
286
- <CardActions>
287
- <Button size="small">Button</Button>
288
- <Button emphasis="ghost">
289
- <Icon name="placeholder" />
290
- </Button>
291
- </CardActions>
292
- </CardFooter>
293
- </CardRoot>
294
- </Box>
265
+ <div className="w-full flex flex-row gap-lg">
266
+ <div className="flex-1">
267
+ <CardRoot>
268
+ <CardMain onClick={() => {}}>
269
+ <CardTitle>Card Title</CardTitle>
270
+ <CardContent>Other stuff</CardContent>
271
+ </CardMain>
272
+ <CardFooter>
273
+ <CardActions>
274
+ <Button size="small">Button</Button>
275
+ <Button emphasis="ghost" size="small">
276
+ <Icon name="placeholder" />
277
+ </Button>
278
+ </CardActions>
279
+ </CardFooter>
280
+ </CardRoot>
281
+ </div>
282
+ <Box p gap col grow>
283
+ <CardRoot>
284
+ <CardMain onClick={() => {}}>
285
+ <CardTitle>Card Title</CardTitle>
286
+ <CardContent>Other stuff</CardContent>
287
+ </CardMain>
288
+ <CardFooter>
289
+ <CardActions>
290
+ <Button size="small">Button</Button>
291
+ <Button emphasis="ghost" size="small">
292
+ <Icon name="placeholder" />
293
+ </Button>
294
+ </CardActions>
295
+ </CardFooter>
296
+ </CardRoot>
297
+ <CardRoot>
298
+ <CardMain onClick={() => {}}>
299
+ <CardTitle>Card Title</CardTitle>
300
+ <CardContent>Other stuff</CardContent>
301
+ </CardMain>
302
+ <CardFooter>
303
+ <CardActions>
304
+ <Button size="small">Button</Button>
305
+ <Button emphasis="ghost" size="small">
306
+ <Icon name="placeholder" />
307
+ </Button>
308
+ </CardActions>
309
+ </CardFooter>
310
+ </CardRoot>
311
+ </Box>
312
+ </div>
295
313
  );
296
314
  },
297
315
  };
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import clsx from 'clsx';
3
4
  import { useField, useFormikContext } from 'formik';
4
5
  import {
5
6
  InputHTMLAttributes,
@@ -119,7 +120,7 @@ export function TextAreaField({
119
120
  ref={inputRef as any}
120
121
  {...props}
121
122
  {...rest}
122
- className={textAreaClassName}
123
+ className={clsx('layer-composed:w-full', textAreaClassName)}
123
124
  id={id}
124
125
  onKeyDown={onKeyDownInner}
125
126
  />
@@ -20,7 +20,7 @@ export const inputClassName = clsx(
20
20
  );
21
21
 
22
22
  const inputBorderClassName = clsx(
23
- 'layer-components:(flex flex-row items-center gap-xs border-1 rounded-lg border-solid text-md shadow-sm shadow-inset transition-shadow color-black bg-white border-black)',
23
+ 'layer-components:(min-w-60px flex flex-row items-center gap-xs border-1 rounded-lg border-solid text-md shadow-sm shadow-inset transition-shadow color-black bg-white border-black)',
24
24
  'layer-components:(w-max-content overflow-clip)',
25
25
  'layer-components:[&:has(input:disabled),&:has(textarea:disabled)]:(bg-transparent border-gray shadow-none placeholder-gray-dark)',
26
26
  'layer-components:[&:has(input:focus-visible),&:has(textarea:focus-visible)]:(outline-none ring ring-4 ring-accent)',
@@ -26,6 +26,7 @@ export const Default: Story = {
26
26
  <Popover.Arrow />
27
27
  <Popover.Title>Hello</Popover.Title>
28
28
  <Popover.Description>This is a popover content.</Popover.Description>
29
+ <Popover.Close />
29
30
  </Popover.Content>
30
31
  </Popover>
31
32
  );
@@ -5,10 +5,12 @@ import {
5
5
  } from '@base-ui/react/popover';
6
6
 
7
7
  import { MenuArrowProps } from '@base-ui/react/menu';
8
- import classNames from 'clsx';
8
+ import classNames, { clsx } from 'clsx';
9
9
  import { Ref } from 'react';
10
10
  import { withClassName } from '../../hooks/withClassName.js';
11
11
  import { GroupScaleReset } from '../../systems/GroupScale.js';
12
+ import { Button, ButtonProps } from '../button/Button.js';
13
+ import { Icon } from '../icon/Icon.js';
12
14
  import { popupClassName } from '../primitives/menus.js';
13
15
  import { ArrowSvg } from '../utility/ArrowSvg.js';
14
16
 
@@ -25,11 +27,29 @@ const StyledArrow = withClassName(
25
27
  'layer-components:data-[open]:(scale-100 opacity-100)',
26
28
  );
27
29
 
28
- const StyledClose = withClassName(
29
- PopoverPrimitive.Close,
30
- 'layer-components:hover:bg-lightBlend layer-components:focus:shadow-focus layer-components:([all:unset] [font-family:inherit] absolute right-5px top-5px h-25px w-25px inline-flex items-center justify-center rounded-lg color-gray-dark/80)',
30
+ const StyledClose = ({
31
+ className,
32
+ inline,
33
+ ...props
34
+ }: ButtonProps & {
35
+ inline?: boolean;
36
+ }) => (
37
+ <PopoverPrimitive.Close
38
+ render={
39
+ <Button
40
+ emphasis="ghost"
41
+ size="small"
42
+ className={clsx(
43
+ !inline && 'layer-composed:(absolute right-sm top-sm)',
44
+ className,
45
+ )}
46
+ {...props}
47
+ />
48
+ }
49
+ >
50
+ <Icon name="x" />
51
+ </PopoverPrimitive.Close>
31
52
  );
32
-
33
53
  // Exports
34
54
  export const PopoverRoot = PopoverPrimitive.Root;
35
55
  export const PopoverTrigger = PopoverPrimitive.Trigger;
@@ -19,6 +19,9 @@ export const Default: Story = {
19
19
  render(args) {
20
20
  return (
21
21
  <Box col gap>
22
+ <Box full="width" justify="end">
23
+ <Button>Test</Button>
24
+ </Box>
22
25
  <Button
23
26
  onClick={() => {
24
27
  toast(
@@ -30,7 +30,7 @@ export const DefaultToastProvider = ({
30
30
  export function Toaster() {
31
31
  return (
32
32
  <Toast.Portal>
33
- <Toast.Viewport className="overflow-clip">
33
+ <Toast.Viewport className="pointer-events-none overflow-clip">
34
34
  <ToastList />
35
35
  </Toast.Viewport>
36
36
  </Toast.Portal>
@@ -58,7 +58,7 @@ function ToastList() {
58
58
  'h-[--height]',
59
59
  'flex flex-col items-center gap-xs',
60
60
  // other properties
61
- 'select-none',
61
+ 'pointer-events-none select-none',
62
62
  // animation and interaction
63
63
  'translate-x-[--toast-swipe-movement-x] translate-y-[calc(var(--toast-swipe-movement-y)+(var(--toast-index)*var(--peek))+(var(--shrink)*var(--height)))] scale-[var(--scale)]',
64
64
  '[transition:transform_0.5s_cubic-bezier(0.22,1,0.36,1),opacity_0.5s,height_0.15s]',
@@ -95,10 +95,10 @@ function ToastList() {
95
95
  mode === 'dark' ? 'override-light' : 'override-dark',
96
96
  )}
97
97
  >
98
- <Toast.Content className="max-w-sm flex flex-col gap-2px [&[data-behind]:not([data-expanded])]:pointer-events-none">
98
+ <Toast.Content className="pointer-events-auto max-w-sm flex flex-col gap-2px [&[data-behind]:not([data-expanded])]:pointer-events-none">
99
99
  <div
100
100
  className={clsx(
101
- 'layer-components:(relative b-1 b-black rounded-md b-solid py-sm pl-md pr-sm shadow-md color-black bg-main-wash)',
101
+ 'layer-components:(relative b-1 rounded-md b-solid py-sm pl-md pr-sm shadow-md color-black bg-main-wash b-black)',
102
102
  'layer-components:(flex flex-row gap-sm)',
103
103
  '[[data-behind]:not([data-expanded])_&]:(max-h-[--height] bg-darken-2)',
104
104
  )}
@@ -40,6 +40,7 @@ export function useVisualViewportOffset(disable?: boolean) {
40
40
  export function useIsKeyboardOpen() {
41
41
  const { virtualKeyboardBehavior } = useConfig();
42
42
 
43
+ // viewport heuristic
43
44
  const [isViewportConstrained, setIsViewportConstrained] = useState(false);
44
45
  useReactToViewportChanges((viewport) => {
45
46
  // heuristic - 100px difference between visual viewport and window height
@@ -48,6 +49,7 @@ export function useIsKeyboardOpen() {
48
49
  );
49
50
  }, virtualKeyboardBehavior !== 'displace');
50
51
 
52
+ // simulated keyboard
51
53
  const [simulateKeyboardOpen, setSimulateKeyboardOpen] = useState<
52
54
  boolean | undefined
53
55
  >(undefined);
@@ -69,6 +71,7 @@ export function useIsKeyboardOpen() {
69
71
  };
70
72
  }, []);
71
73
 
74
+ // virtual keyboard API
72
75
  const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
73
76
  useEffect(() => {
74
77
  if (typeof navigator === 'undefined' || typeof window === 'undefined') {
@@ -176,3 +179,47 @@ function useReactToViewportChanges(
176
179
  };
177
180
  }, [stableCb, disable]);
178
181
  }
182
+
183
+ export interface VirtualKeyboardFocusOptions {
184
+ focusElementTypes?: string[];
185
+ }
186
+ export function useVirtualKeyboardFocusBehavior({
187
+ focusElementTypes = ['input', 'textarea', 'select'],
188
+ }: VirtualKeyboardFocusOptions = {}) {
189
+ const stableFocusElementTypes = focusElementTypes
190
+ .map((type) => type.toLowerCase())
191
+ .join(',');
192
+ useEffect(() => {
193
+ if (typeof navigator === 'undefined' || typeof window === 'undefined') {
194
+ return;
195
+ }
196
+
197
+ if (!('virtualKeyboard' in navigator)) {
198
+ // no support
199
+ console.warn(
200
+ `virtual keyboard behavior set to 'overlay', but virtualKeyboard API is not supported in this browser.`,
201
+ );
202
+ return;
203
+ }
204
+
205
+ const virtualKeyboard = navigator.virtualKeyboard as any;
206
+
207
+ const matchElements = stableFocusElementTypes.split(',');
208
+
209
+ function update() {
210
+ // const open = virtualKeyboard.boundingRect.height > 0;
211
+ const activeElement = document.activeElement;
212
+ if (
213
+ activeElement &&
214
+ matchElements.includes(activeElement.tagName.toLowerCase())
215
+ ) {
216
+ activeElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
217
+ }
218
+ }
219
+
220
+ virtualKeyboard.addEventListener('geometrychange', update);
221
+ return () => {
222
+ virtualKeyboard.removeEventListener('geometrychange', update);
223
+ };
224
+ }, [stableFocusElementTypes]);
225
+ }
@@ -0,0 +1,34 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Input, TextArea } from './components/index.js';
3
+ import { useVirtualKeyboardBehavior } from './hooks/useVirtualKeyboardBehavior.js';
4
+
5
+ const meta = {
6
+ title: 'Virtual Keyboard Test',
7
+ argTypes: {},
8
+ parameters: {
9
+ controls: { expanded: false },
10
+ layout: 'fullscreen',
11
+ },
12
+ } satisfies Meta;
13
+
14
+ export default meta;
15
+
16
+ type Story = StoryObj;
17
+
18
+ export const Default: Story = {
19
+ render(args) {
20
+ useVirtualKeyboardBehavior('overlay');
21
+ return (
22
+ <>
23
+ <div className="h-screen flex flex-col">
24
+ <div className="flex flex-grow flex-col items-center justify-center p-lg">
25
+ Focus the inputs below to see how the virtual keyboard behavior
26
+ works.
27
+ </div>
28
+ <Input className="w-full" />
29
+ <TextArea className="w-full" />
30
+ </div>
31
+ </>
32
+ );
33
+ },
34
+ };
@@ -18,8 +18,12 @@ export function useGroupScale() {
18
18
  return useContext(GroupScaleContext);
19
19
  }
20
20
 
21
- export function useGroupScaleStyles(composeStyles?: CSSProperties) {
22
- const scale = useGroupScale();
21
+ export function useGroupScaleStyles(
22
+ composeStyles?: CSSProperties,
23
+ forceScale?: number,
24
+ ) {
25
+ const ctxScale = useGroupScale();
26
+ const scale = forceScale ?? ctxScale;
23
27
  return {
24
28
  [PROPS.LOCALS.SPACING_SCALE]: scale,
25
29
  [PROPS.LOCALS.CORNER_SCALE]: scale,
@@ -34,3 +38,13 @@ export const GroupScaleReset = ({ children }: { children: ReactNode }) => {
34
38
  </GroupScaleContext.Provider>
35
39
  );
36
40
  };
41
+
42
+ export function withGroupScaleReset<T>(Component: React.ComponentType<T>) {
43
+ return function WithGroupScaleReset(props: T) {
44
+ return (
45
+ <GroupScaleReset>
46
+ <Component {...(props as any)} />
47
+ </GroupScaleReset>
48
+ );
49
+ };
50
+ }
@@ -3,6 +3,7 @@ import { basePreflight } from './base.js';
3
3
  import { colorPreflight, ColorPreflightOptions } from './colors.js';
4
4
  import { fontsPreflight, FontsPreflightOptions } from './fonts.js';
5
5
  import { globalPreflight, GlobalsPreflightConfig } from './globals.js';
6
+ import { keyboardPreflight } from './keyboard.js';
6
7
  import { layerPreflight } from './layers.js';
7
8
  import { modePreflight } from './mode.js';
8
9
  import { propertiesPreflight } from './properties.js';
@@ -22,4 +23,5 @@ export const preflights = (config: PreflightConfig): Preflight<any>[] => [
22
23
  fontsPreflight(config),
23
24
  propertiesPreflight,
24
25
  userPreflight(config),
26
+ keyboardPreflight,
25
27
  ];