@a-type/ui 1.1.18 → 1.1.20

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 (88) hide show
  1. package/dist/cjs/components/card/Card.d.ts +2 -2
  2. package/dist/cjs/components/dialog/Dialog.js +10 -2
  3. package/dist/cjs/components/dialog/Dialog.js.map +1 -1
  4. package/dist/cjs/components/dialog/Dialog.stories.js +3 -1
  5. package/dist/cjs/components/dialog/Dialog.stories.js.map +1 -1
  6. package/dist/cjs/components/errorBoundary/ErrorBoundary.d.ts +1 -1
  7. package/dist/cjs/components/input/Input.d.ts +3 -0
  8. package/dist/cjs/components/input/Input.js +13 -2
  9. package/dist/cjs/components/input/Input.js.map +1 -1
  10. package/dist/cjs/components/layouts/PageNav.js +3 -2
  11. package/dist/cjs/components/layouts/PageNav.js.map +1 -1
  12. package/dist/cjs/components/layouts/PageNowPlaying.js +6 -1
  13. package/dist/cjs/components/layouts/PageNowPlaying.js.map +1 -1
  14. package/dist/cjs/components/layouts/layouts.stories.js +2 -1
  15. package/dist/cjs/components/layouts/layouts.stories.js.map +1 -1
  16. package/dist/cjs/components/masonry/masonry.js +5 -1
  17. package/dist/cjs/components/masonry/masonry.js.map +1 -1
  18. package/dist/cjs/components/particles/ParticleLayer.stories.js +2 -2
  19. package/dist/cjs/components/particles/ParticleLayer.stories.js.map +1 -1
  20. package/dist/cjs/components/particles/particlesState.js +5 -1
  21. package/dist/cjs/components/particles/particlesState.js.map +1 -1
  22. package/dist/cjs/components/provider/Provider.d.ts +6 -0
  23. package/dist/cjs/components/provider/Provider.js +14 -5
  24. package/dist/cjs/components/provider/Provider.js.map +1 -1
  25. package/dist/cjs/components/utility/HideWhileKeyboardOpen.d.ts +5 -0
  26. package/dist/cjs/components/utility/HideWhileKeyboardOpen.js +30 -0
  27. package/dist/cjs/components/utility/HideWhileKeyboardOpen.js.map +1 -0
  28. package/dist/cjs/hooks/useSize.d.ts +2 -1
  29. package/dist/cjs/hooks/useSize.js +17 -5
  30. package/dist/cjs/hooks/useSize.js.map +1 -1
  31. package/dist/cjs/hooks/useVirtualKeyboardBehavior.d.ts +1 -1
  32. package/dist/cjs/hooks/useVirtualKeyboardBehavior.js +1 -1
  33. package/dist/cjs/hooks/useVirtualKeyboardBehavior.js.map +1 -1
  34. package/dist/cjs/hooks/useVisualViewportOffset.d.ts +1 -0
  35. package/dist/cjs/hooks/useVisualViewportOffset.js +56 -6
  36. package/dist/cjs/hooks/useVisualViewportOffset.js.map +1 -1
  37. package/dist/css/main.css +1 -1
  38. package/dist/esm/components/card/Card.d.ts +2 -2
  39. package/dist/esm/components/dialog/Dialog.js +10 -2
  40. package/dist/esm/components/dialog/Dialog.js.map +1 -1
  41. package/dist/esm/components/dialog/Dialog.stories.js +3 -1
  42. package/dist/esm/components/dialog/Dialog.stories.js.map +1 -1
  43. package/dist/esm/components/errorBoundary/ErrorBoundary.d.ts +1 -1
  44. package/dist/esm/components/input/Input.d.ts +3 -0
  45. package/dist/esm/components/input/Input.js +14 -3
  46. package/dist/esm/components/input/Input.js.map +1 -1
  47. package/dist/esm/components/layouts/PageNav.js +3 -2
  48. package/dist/esm/components/layouts/PageNav.js.map +1 -1
  49. package/dist/esm/components/layouts/PageNowPlaying.js +6 -1
  50. package/dist/esm/components/layouts/PageNowPlaying.js.map +1 -1
  51. package/dist/esm/components/layouts/layouts.stories.js +2 -1
  52. package/dist/esm/components/layouts/layouts.stories.js.map +1 -1
  53. package/dist/esm/components/masonry/masonry.js +2 -1
  54. package/dist/esm/components/masonry/masonry.js.map +1 -1
  55. package/dist/esm/components/particles/ParticleLayer.stories.js +2 -2
  56. package/dist/esm/components/particles/ParticleLayer.stories.js.map +1 -1
  57. package/dist/esm/components/particles/particlesState.js +5 -1
  58. package/dist/esm/components/particles/particlesState.js.map +1 -1
  59. package/dist/esm/components/provider/Provider.d.ts +6 -0
  60. package/dist/esm/components/provider/Provider.js +12 -4
  61. package/dist/esm/components/provider/Provider.js.map +1 -1
  62. package/dist/esm/components/utility/HideWhileKeyboardOpen.d.ts +5 -0
  63. package/dist/esm/components/utility/HideWhileKeyboardOpen.js +24 -0
  64. package/dist/esm/components/utility/HideWhileKeyboardOpen.js.map +1 -0
  65. package/dist/esm/hooks/useSize.d.ts +2 -1
  66. package/dist/esm/hooks/useSize.js +17 -5
  67. package/dist/esm/hooks/useSize.js.map +1 -1
  68. package/dist/esm/hooks/useVirtualKeyboardBehavior.d.ts +1 -1
  69. package/dist/esm/hooks/useVirtualKeyboardBehavior.js +1 -1
  70. package/dist/esm/hooks/useVirtualKeyboardBehavior.js.map +1 -1
  71. package/dist/esm/hooks/useVisualViewportOffset.d.ts +1 -0
  72. package/dist/esm/hooks/useVisualViewportOffset.js +54 -5
  73. package/dist/esm/hooks/useVisualViewportOffset.js.map +1 -1
  74. package/package.json +1 -1
  75. package/src/components/dialog/Dialog.stories.tsx +6 -3
  76. package/src/components/dialog/Dialog.tsx +18 -3
  77. package/src/components/input/Input.tsx +35 -2
  78. package/src/components/layouts/PageNav.tsx +5 -3
  79. package/src/components/layouts/PageNowPlaying.tsx +7 -0
  80. package/src/components/layouts/layouts.stories.tsx +3 -1
  81. package/src/components/masonry/masonry.tsx +6 -1
  82. package/src/components/particles/ParticleLayer.stories.tsx +2 -4
  83. package/src/components/particles/particlesState.ts +5 -3
  84. package/src/components/provider/Provider.tsx +34 -14
  85. package/src/components/utility/HideWhileKeyboardOpen.tsx +26 -0
  86. package/src/hooks/useSize.ts +24 -3
  87. package/src/hooks/useVirtualKeyboardBehavior.ts +1 -3
  88. package/src/hooks/useVisualViewportOffset.ts +82 -12
@@ -1,6 +1,6 @@
1
1
  // @unocss-include
2
2
  import { useEffect } from 'react';
3
- export function useVirtualKeyboardBehavior(behavior = 'overlay') {
3
+ export function useVirtualKeyboardBehavior(behavior) {
4
4
  useEffect(() => {
5
5
  if ('virtualKeyboard' in navigator) {
6
6
  // @ts-ignore
@@ -1 +1 @@
1
- {"version":3,"file":"useVirtualKeyboardBehavior.js","sourceRoot":"","sources":["../../../src/hooks/useVirtualKeyboardBehavior.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,UAAU,0BAA0B,CACzC,WAAmC,SAAS;IAE5C,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,iBAAiB,IAAI,SAAS,EAAE;YACnC,aAAa;YACb,SAAS,CAAC,eAAe,CAAC,eAAe,GAAG,QAAQ,KAAK,SAAS,CAAC;SACnE;IACF,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChB,CAAC"}
1
+ {"version":3,"file":"useVirtualKeyboardBehavior.js","sourceRoot":"","sources":["../../../src/hooks/useVirtualKeyboardBehavior.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,MAAM,UAAU,0BAA0B,CAAC,QAAgC;IAC1E,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,iBAAiB,IAAI,SAAS,EAAE;YACnC,aAAa;YACb,SAAS,CAAC,eAAe,CAAC,eAAe,GAAG,QAAQ,KAAK,SAAS,CAAC;SACnE;IACF,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChB,CAAC"}
@@ -2,3 +2,4 @@
2
2
  * Applies bottom offset px as a CSS custom property to the document root.
3
3
  */
4
4
  export declare function useVisualViewportOffset(disable?: boolean): void;
5
+ export declare function useIsKeyboardOpen(): boolean;
@@ -1,27 +1,76 @@
1
1
  // @unocss-include
2
- import { useEffect } from 'react';
2
+ import { useEffect, useState } from 'react';
3
+ import { useStableCallback } from './useStableCallback.js';
4
+ import { useConfig } from '../components/provider.js';
3
5
  /**
4
6
  * Applies bottom offset px as a CSS custom property to the document root.
5
7
  */
6
8
  export function useVisualViewportOffset(disable) {
9
+ useReactToViewportChanges((viewport) => {
10
+ document.documentElement.style.setProperty('--viewport-bottom-offset', `${window.innerHeight - viewport.height - viewport.offsetTop}px`);
11
+ document.documentElement.style.setProperty('--viewport-height', `${viewport.height}px`);
12
+ document.documentElement.style.setProperty('--viewport-width', `${viewport.width}px`);
13
+ document.documentElement.style.setProperty('--viewport-top-offset', `${viewport.offsetTop}px`);
14
+ document.documentElement.style.setProperty('--viewport-left-offset', `${viewport.offsetLeft}px`);
15
+ document.documentElement.style.setProperty('--keyboard-open', viewport.height < window.innerHeight ? '1' : '0');
16
+ }, disable);
17
+ }
18
+ export function useIsKeyboardOpen() {
19
+ const { virtualKeyboardBehavior } = useConfig();
20
+ const [isViewportConstrained, setIsViewportConstrained] = useState(false);
21
+ useReactToViewportChanges((viewport) => {
22
+ setIsViewportConstrained(viewport.height < window.innerHeight);
23
+ }, virtualKeyboardBehavior !== 'displace');
24
+ const [isKeyboardOpen, setIsKeyboardOpen] = useState(false);
25
+ useEffect(() => {
26
+ if (!('virtualKeyboard' in navigator)) {
27
+ // no support
28
+ console.warn(`virtual keyboard behavior set to 'overlay', but virtualKeyboard API is not supported in this browser.`);
29
+ return;
30
+ }
31
+ const virtualKeyboard = navigator.virtualKeyboard;
32
+ const update = () => {
33
+ setIsKeyboardOpen(virtualKeyboard.boundingRect.height > 0);
34
+ };
35
+ update();
36
+ virtualKeyboard.addEventListener('geometrychange', update);
37
+ return () => {
38
+ virtualKeyboard.removeEventListener('geometrychange', update);
39
+ };
40
+ }, []);
41
+ if (virtualKeyboardBehavior === 'displace') {
42
+ return isViewportConstrained;
43
+ }
44
+ return isKeyboardOpen;
45
+ }
46
+ function useReactToViewportChanges(cb, disable) {
47
+ const stableCb = useStableCallback(cb);
7
48
  useEffect(() => {
8
49
  if (disable)
9
50
  return;
10
- const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;
51
+ const viewport = window.visualViewport;
11
52
  if (!viewport) {
12
53
  return;
13
54
  }
14
55
  const update = () => {
15
- document.documentElement.style.setProperty('--viewport-bottom-offset', `${window.innerHeight - viewport.height}px`);
16
- document.documentElement.style.setProperty('--viewport-height', `${viewport.height}px`);
56
+ stableCb(viewport);
57
+ };
58
+ let prevTimeout;
59
+ const debouncedUpdate = () => {
60
+ if (prevTimeout) {
61
+ clearTimeout(prevTimeout);
62
+ }
63
+ prevTimeout = window.setTimeout(update, 50);
17
64
  };
18
65
  update();
19
66
  window.addEventListener('scroll', update, { passive: true });
20
67
  viewport.addEventListener('resize', update);
68
+ viewport.addEventListener('scroll', debouncedUpdate, { passive: true });
21
69
  return () => {
22
70
  viewport.removeEventListener('resize', update);
23
71
  window.removeEventListener('scroll', update);
72
+ viewport.removeEventListener('scroll', debouncedUpdate);
24
73
  };
25
- }, [disable]);
74
+ }, [stableCb, disable]);
26
75
  }
27
76
  //# sourceMappingURL=useVisualViewportOffset.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useVisualViewportOffset.js","sourceRoot":"","sources":["../../../src/hooks/useVisualViewportOffset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiB;IACxD,SAAS,CAAC,GAAG,EAAE;QACd,IAAI,OAAO;YAAE,OAAO;QAEpB,MAAM,QAAQ,GACb,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;QAEnE,IAAI,CAAC,QAAQ,EAAE;YACd,OAAO;SACP;QAED,MAAM,MAAM,GAAG,GAAG,EAAE;YACnB,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,0BAA0B,EAC1B,GAAG,MAAM,CAAC,WAAW,GAAG,QAAQ,CAAC,MAAM,IAAI,CAC3C,CAAC;YACF,QAAQ,CAAC,eAAe,CAAC,KAAK,CAAC,WAAW,CACzC,mBAAmB,EACnB,GAAG,QAAQ,CAAC,MAAM,IAAI,CACtB,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,EAAE,CAAC;QAET,MAAM,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAE5C,OAAO,GAAG,EAAE;YACX,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YAC/C,MAAM,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC,CAAC;IACH,CAAC,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"useVisualViewportOffset.js","sourceRoot":"","sources":["../../../src/hooks/useVisualViewportOffset.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@a-type/ui",
3
- "version": "1.1.18",
3
+ "version": "1.1.20",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "/dist",
@@ -11,6 +11,8 @@ import { ParticleLayer } from '../particles.js';
11
11
  import { H1, P } from '../typography.js';
12
12
  import { Button } from '../button.js';
13
13
  import { useEffect, useState } from 'react';
14
+ import { Provider } from '../provider.js';
15
+ import { Input } from '../input.js';
14
16
 
15
17
  const meta = {
16
18
  title: 'Dialog',
@@ -124,7 +126,7 @@ export const VirtualKeyboard: Story = {
124
126
  }, [keyboard]);
125
127
 
126
128
  return (
127
- <ParticleLayer noPortal>
129
+ <Provider virtualKeyboardBehavior="overlay">
128
130
  <Dialog>
129
131
  <DialogTrigger asChild>
130
132
  <Button>Open</Button>
@@ -132,18 +134,19 @@ export const VirtualKeyboard: Story = {
132
134
  <DummyContent />
133
135
  <DialogContent>
134
136
  <DialogTitle>Hello world</DialogTitle>
137
+ <Input />
135
138
  <DialogActions>
136
139
  <DialogClose asChild>
137
140
  <Button>Close</Button>
138
141
  </DialogClose>
139
142
  <Button onClick={() => setKeyboard((v) => !v)}>
140
- Toggle fake virtual kb
143
+ Toggle fake kb
141
144
  </Button>
142
145
  </DialogActions>
143
146
  </DialogContent>
144
147
  </Dialog>
145
148
  <div className="fixed bottom-0 h-[var(--mock-virtual-keyboard-height,0)] bg-black w-full transition-height left-0 right-0" />
146
- </ParticleLayer>
149
+ </Provider>
147
150
  );
148
151
  },
149
152
  };
@@ -19,6 +19,7 @@ import { CheckIcon, ChevronDownIcon } from '@radix-ui/react-icons';
19
19
  import { selectTriggerClassName } from '../select.js';
20
20
  import { useDrag } from '@use-gesture/react';
21
21
  import { Button } from '../button.js';
22
+ import { useConfig } from '../provider.js';
22
23
 
23
24
  const StyledOverlay = withClassName(
24
25
  DialogPrimitive.Overlay,
@@ -35,10 +36,16 @@ const StyledContent = withClassName(
35
36
  'layer-components:(left-50% top-50% translate-[-50%] w-90vw max-w-450px max-h-85vh p-6 pt-8 rounded-lg border-b-1 pt-6)',
36
37
  'layer-components:(animate-dialog-in [&[data-state=closed]]:animate-dialog-out motion-reduce:animate-none)',
37
38
  );
38
- const sheetClassName = classNames(
39
- 'layer-variants:lt-sm:(translate-0 bottom-[calc(var(--mock-virtual-keyboard-height,env(keyboard-inset-height,0px))+var(--gesture-y,0px))] left-0 right-0 top-auto h-min-content max-h-[calc(85vh-var(--mock-virtual-keyboard-height,env(keyboard-inset-height,0px)))] rounded-tl-xl rounded-tr-xl rounded-b-0 p-6 pt-8 w-full max-w-none pb-[calc(3rem+env(safe-area-inset-bottom,0px))] border-b-0)',
39
+ const sheetClassNames = classNames(
40
+ 'layer-variants:lt-sm:(translate-0 left-0 right-0 top-auto h-min-content rounded-tl-xl rounded-tr-xl rounded-b-0 p-6 pt-8 w-full max-w-none pb-[calc(3rem+env(safe-area-inset-bottom,0px))] border-b-0)',
40
41
  'layer-variants:lt-sm:(animate-ease-in animate-fade-in-up [&[data-state=closed]]:animate-fade-out-down)',
41
42
  );
43
+ const sheetClassNameWithOverlayKeyboard = classNames(
44
+ 'layer-variants:lt-sm:(bottom-[calc(var(--mock-virtual-keyboard-height,env(keyboard-inset-height,0px))+var(--gesture-y,0px))] max-h-[calc(95vh-var(--mock-virtual-keyboard-height,env(keyboard-inset-height,0px)))])',
45
+ );
46
+ const sheetClassNameWithDisplaceKeyboard = classNames(
47
+ 'layer-variants:lt-sm:(bottom-[calc(var(--viewport-bottom-offset,0px)+var(--gesture-y,0px))] max-h-[calc(0.85*var(--viewport-height,100vh))])',
48
+ );
42
49
 
43
50
  export const Content = forwardRef<
44
51
  HTMLDivElement,
@@ -107,6 +114,8 @@ export const Content = forwardRef<
107
114
 
108
115
  const finalRef = useMergedRef(ref, openRef, gestureRef);
109
116
 
117
+ const { virtualKeyboardBehavior } = useConfig();
118
+
110
119
  return (
111
120
  <DialogPrimitive.Portal>
112
121
  <StyledOverlay />
@@ -120,7 +129,13 @@ export const Content = forwardRef<
120
129
  'max-w-600px': width === 'md',
121
130
  'max-w-300px': width === 'sm',
122
131
  },
123
- !disableSheet && sheetClassName,
132
+ !disableSheet && sheetClassNames,
133
+ !disableSheet &&
134
+ virtualKeyboardBehavior === 'overlay' &&
135
+ sheetClassNameWithOverlayKeyboard,
136
+ !disableSheet &&
137
+ virtualKeyboardBehavior === 'displace' &&
138
+ sheetClassNameWithDisplaceKeyboard,
124
139
  outerClassName || className,
125
140
  )}
126
141
  >
@@ -1,10 +1,11 @@
1
1
  import classNames from 'clsx';
2
2
  import {
3
3
  ComponentProps,
4
- ComponentPropsWithRef,
5
4
  FocusEvent,
6
5
  forwardRef,
7
6
  useCallback,
7
+ useEffect,
8
+ useState,
8
9
  } from 'react';
9
10
  import { Slot } from '@radix-ui/react-slot';
10
11
 
@@ -20,10 +21,23 @@ export interface InputProps extends ComponentProps<'input'> {
20
21
  variant?: 'default' | 'primary';
21
22
  autoSelect?: boolean;
22
23
  asChild?: boolean;
24
+ /** Shuffle between random placeholders */
25
+ placeholders?: string[];
26
+ placeholdersIntervalMs?: number;
23
27
  }
24
28
 
25
29
  export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
26
- { className, autoSelect, onFocus, variant: _, asChild, ...props },
30
+ {
31
+ className,
32
+ autoSelect,
33
+ onFocus,
34
+ variant: _,
35
+ asChild,
36
+ placeholders,
37
+ placeholder,
38
+ placeholdersIntervalMs = 5000,
39
+ ...props
40
+ },
27
41
  ref,
28
42
  ) {
29
43
  const handleFocus = useCallback(
@@ -36,6 +50,24 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
36
50
  [onFocus, autoSelect],
37
51
  );
38
52
 
53
+ const [randomPlaceholder, setRandomPlaceholder] = useState<
54
+ string | undefined
55
+ >(
56
+ placeholders
57
+ ? placeholders[Math.floor(Math.random() * placeholders.length)]
58
+ : undefined,
59
+ );
60
+ useEffect(() => {
61
+ if (placeholders) {
62
+ const interval = setInterval(() => {
63
+ setRandomPlaceholder(
64
+ placeholders[Math.floor(Math.random() * placeholders.length)],
65
+ );
66
+ }, placeholdersIntervalMs);
67
+ return () => clearInterval(interval);
68
+ }
69
+ }, [placeholders, placeholdersIntervalMs]);
70
+
39
71
  const Component = asChild ? Slot : 'input';
40
72
 
41
73
  return (
@@ -44,6 +76,7 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(function Input(
44
76
  onFocus={handleFocus}
45
77
  className={classNames(inputClassName, className)}
46
78
  ref={ref}
79
+ placeholder={placeholder ?? randomPlaceholder}
47
80
  />
48
81
  );
49
82
  });
@@ -3,13 +3,14 @@
3
3
  import classNames from 'clsx';
4
4
  import { HTMLAttributes, useRef } from 'react';
5
5
  import { useBoundsCssVars } from '../../hooks.js';
6
+ import { HideWhileKeyboardOpen } from '../utility/HideWhileKeyboardOpen.js';
6
7
 
7
8
  export function PageNav({
8
9
  className,
9
10
  children,
10
11
  ...props
11
12
  }: HTMLAttributes<HTMLDivElement>) {
12
- const bodyRef = useRef(document.body);
13
+ const bodyRef = useRef(document.documentElement);
13
14
  const ref = useBoundsCssVars<HTMLDivElement>(undefined, bodyRef, {
14
15
  left: '--nav-left',
15
16
  top: '--nav-top',
@@ -17,8 +18,9 @@ export function PageNav({
17
18
  height: '--nav-height',
18
19
  ready: '--nav-ready',
19
20
  });
21
+
20
22
  return (
21
- <div
23
+ <HideWhileKeyboardOpen
22
24
  {...props}
23
25
  className={classNames(
24
26
  'layer-components:([grid-area:nav] relative z-nav pb-[calc(0.25rem+env(safe-area-inset-bottom,0px))])',
@@ -28,6 +30,6 @@ export function PageNav({
28
30
  ref={ref}
29
31
  >
30
32
  {children}
31
- </div>
33
+ </HideWhileKeyboardOpen>
32
34
  );
33
35
  }
@@ -2,6 +2,7 @@
2
2
 
3
3
  import classNames from 'clsx';
4
4
  import { HTMLAttributes } from 'react';
5
+ import { useConfig } from '../provider.js';
5
6
 
6
7
  export function PageNowPlaying({
7
8
  className,
@@ -12,6 +13,8 @@ export function PageNowPlaying({
12
13
  unstyled?: boolean;
13
14
  keepAboveKeyboard?: boolean;
14
15
  }) {
16
+ const { virtualKeyboardBehavior } = useConfig();
17
+
15
18
  return (
16
19
  <div
17
20
  {...props}
@@ -26,7 +29,11 @@ export function PageNowPlaying({
26
29
  ? 'layer-variants:p-2'
27
30
  : 'layer-components:(bg-wash p-2px rounded-xl border-light shadow-md min-w-32px items-center justify-center m-2 w-auto)',
28
31
  keepAboveKeyboard &&
32
+ virtualKeyboardBehavior === 'overlay' &&
29
33
  'layer-variants:lt-sm:bottom-[max(var(--mock-virtual-keyboard-height,env(keyboard-inset-height,0px),var(--nav-height,env(safe-area-inset-bottom,0px))))]',
34
+ keepAboveKeyboard &&
35
+ virtualKeyboardBehavior === 'displace' &&
36
+ 'layer-variants:lt-sm:bottom-[max(var(--viewport-bottom-offset,0px),var(--nav-height,env(safe-area-inset-bottom,0px)))]',
30
37
  className,
31
38
  )}
32
39
  />
@@ -14,6 +14,7 @@ import { Icon } from '../icon.js';
14
14
  import { PageNowPlaying } from './PageNowPlaying.js';
15
15
  import { useEffect, useState } from 'react';
16
16
  import { Button } from '../button.js';
17
+ import { Input } from '../input.js';
17
18
 
18
19
  const meta = {
19
20
  title: 'layouts',
@@ -99,8 +100,9 @@ export const WithVirtualKeyboard: Story = {
99
100
  </div>
100
101
  <PageNowPlaying keepAboveKeyboard>
101
102
  <Button onClick={() => setKeyboard((v) => !v)}>
102
- Toggle vkeyboard
103
+ Toggle fake kb
103
104
  </Button>
105
+ <Input />
104
106
  </PageNowPlaying>
105
107
  </PageContent>
106
108
  <PageNav>
@@ -1,4 +1,5 @@
1
1
  import { debounce } from '@a-type/utils';
2
+ import clsx from 'clsx';
2
3
  import { CSSProperties, ReactNode, useEffect, useRef, useState } from 'react';
3
4
 
4
5
  interface Layout {
@@ -240,7 +241,11 @@ export function Masonry({
240
241
  }, [layout, ref]);
241
242
 
242
243
  return (
243
- <div ref={ref} style={initialStyle} className={className}>
244
+ <div
245
+ ref={ref}
246
+ style={initialStyle}
247
+ className={clsx('layer-components:z-1', className)}
248
+ >
244
249
  {children}
245
250
  </div>
246
251
  );
@@ -19,15 +19,13 @@ export const Default: Story = {
19
19
  render() {
20
20
  return (
21
21
  <div className="w-full h-[100vh] relative flex flex-col items-center justify-center">
22
- <ParticleLayer noPortal>
23
- <DemoButton />
24
- </ParticleLayer>
22
+ <ExplodeButton />
25
23
  </div>
26
24
  );
27
25
  },
28
26
  };
29
27
 
30
- function DemoButton() {
28
+ function ExplodeButton() {
31
29
  const particles = useParticles();
32
30
  if (!particles) throw new Error('Must be used inside ParticleLayer');
33
31
  const burst = (ev: MouseEvent) => {
@@ -1,5 +1,3 @@
1
- import { assert } from '@a-type/utils';
2
-
3
1
  export class Particles {
4
2
  private canvas: HTMLCanvasElement | null = null;
5
3
  private ctx: CanvasRenderingContext2D | null = null;
@@ -96,7 +94,7 @@ export class Particles {
96
94
 
97
95
  this.ctx!.clearRect(0, 0, this.canvas!.width, this.canvas!.height);
98
96
 
99
- if (this.particles.length === 0) {
97
+ if (this.particles.length === this.freeParticles.length) {
100
98
  // skip drawing until we get particles again
101
99
  this.lastDrawLatch = true;
102
100
  } else {
@@ -121,6 +119,7 @@ export class Particles {
121
119
  addParticles = (spawn: ParticleSpawn) => {
122
120
  // wrap in RAF because initializers often use element dimensions
123
121
  requestAnimationFrame(() => {
122
+ const wasLatch = this.lastDrawLatch;
124
123
  this.lastDrawLatch = false;
125
124
  if (this.freeParticles.length < spawn.count) {
126
125
  this.extendPool(spawn.count - this.freeParticles.length);
@@ -141,6 +140,9 @@ export class Particles {
141
140
  spawn.behavior,
142
141
  );
143
142
  }
143
+ if (wasLatch) {
144
+ this.resume();
145
+ }
144
146
  });
145
147
  };
146
148
 
@@ -1,4 +1,4 @@
1
- import { ReactNode } from 'react';
1
+ import { createContext, ReactNode, useContext } from 'react';
2
2
  import { ParticleLayer } from '../particles.js';
3
3
  import { IconSpritesheet } from '../icon.js';
4
4
  import { Toaster, ToastPosition } from 'react-hot-toast';
@@ -23,11 +23,13 @@ export function Provider({
23
23
  disableParticles,
24
24
  toastPosition = 'top-center',
25
25
  toastContainerClassName,
26
- disableViewportOffset = true,
27
- virtualKeyboardBehavior = 'overlay',
26
+ disableViewportOffset,
27
+ virtualKeyboardBehavior = 'displace',
28
28
  }: ProviderProps) {
29
29
  useVisualViewportOffset(disableViewportOffset);
30
- useVirtualKeyboardBehavior(virtualKeyboardBehavior);
30
+ const supportedVirtualKeyboardBehavior =
31
+ 'virtualKeyboard' in navigator ? virtualKeyboardBehavior : 'displace';
32
+ useVirtualKeyboardBehavior(supportedVirtualKeyboardBehavior);
31
33
 
32
34
  const otherStuff = (
33
35
  <>
@@ -41,18 +43,36 @@ export function Provider({
41
43
 
42
44
  if (disableParticles)
43
45
  return (
44
- <TooltipProvider>
45
- {children}
46
- {otherStuff}
47
- </TooltipProvider>
46
+ <ConfigContext.Provider
47
+ value={{ virtualKeyboardBehavior: supportedVirtualKeyboardBehavior }}
48
+ >
49
+ <TooltipProvider>
50
+ {children}
51
+ {otherStuff}
52
+ </TooltipProvider>
53
+ </ConfigContext.Provider>
48
54
  );
49
55
 
50
56
  return (
51
- <TooltipProvider>
52
- <ParticleLayer>
53
- {children}
54
- {otherStuff}
55
- </ParticleLayer>
56
- </TooltipProvider>
57
+ <ConfigContext.Provider
58
+ value={{ virtualKeyboardBehavior: supportedVirtualKeyboardBehavior }}
59
+ >
60
+ <TooltipProvider>
61
+ <ParticleLayer>
62
+ {children}
63
+ {otherStuff}
64
+ </ParticleLayer>
65
+ </TooltipProvider>
66
+ </ConfigContext.Provider>
57
67
  );
58
68
  }
69
+
70
+ export const ConfigContext = createContext<{
71
+ virtualKeyboardBehavior: 'overlay' | 'displace';
72
+ }>({
73
+ virtualKeyboardBehavior: 'displace',
74
+ });
75
+
76
+ export function useConfig() {
77
+ return useContext(ConfigContext);
78
+ }
@@ -0,0 +1,26 @@
1
+ import { Slot } from '@radix-ui/react-slot';
2
+ import { useIsKeyboardOpen } from '../../hooks.js';
3
+ import clsx from 'clsx';
4
+ import { forwardRef, HTMLAttributes } from 'react';
5
+
6
+ export interface HideWhileKeyboardOpenProps
7
+ extends HTMLAttributes<HTMLDivElement> {
8
+ asChild?: boolean;
9
+ }
10
+
11
+ export const HideWhileKeyboardOpen = forwardRef<
12
+ HTMLDivElement,
13
+ HideWhileKeyboardOpenProps
14
+ >(function HideWhileKeyboardOpen({ asChild, className, ...rest }, ref) {
15
+ const isKeyboardOpen = useIsKeyboardOpen();
16
+
17
+ const Comp = asChild ? Slot : 'div';
18
+
19
+ return (
20
+ <Comp
21
+ ref={ref}
22
+ className={clsx(isKeyboardOpen && 'layer-responsive:hidden', className)}
23
+ {...rest}
24
+ />
25
+ );
26
+ });
@@ -74,13 +74,17 @@ export function useBounds<E extends HTMLElement>(
74
74
  top: number;
75
75
  width: number;
76
76
  height: number;
77
+ ref: RefObject<E>;
77
78
  }) => void,
79
+ disconnectCallback: () => void = () => {},
78
80
  ) {
79
81
  const ref = useRef<E>(null);
80
82
  const cb = useStableCallback(callback);
83
+ const disconnectCb = useStableCallback(disconnectCallback);
81
84
  useEffect(() => {
82
85
  const target = ref.current;
83
86
  if (!target) {
87
+ disconnectCb();
84
88
  return () => {
85
89
  //
86
90
  };
@@ -89,7 +93,7 @@ export function useBounds<E extends HTMLElement>(
89
93
  entries.forEach((entry) => {
90
94
  const { left, top, width, height } =
91
95
  entry.target.getBoundingClientRect();
92
- cb({ left, top, width, height });
96
+ cb({ left, top, width, height, ref });
93
97
  });
94
98
  });
95
99
  resizeObserver.observe(target);
@@ -97,7 +101,7 @@ export function useBounds<E extends HTMLElement>(
97
101
  resizeObserver.unobserve(target);
98
102
  resizeObserver.disconnect();
99
103
  };
100
- }, [ref, cb]);
104
+ }, [ref, cb, disconnectCb]);
101
105
  return ref;
102
106
  }
103
107
 
@@ -118,11 +122,13 @@ export function useBoundsCssVars<E extends HTMLElement>(
118
122
  top,
119
123
  width,
120
124
  height,
125
+ ref,
121
126
  }: {
122
127
  left: number;
123
128
  top: number;
124
129
  width: number;
125
130
  height: number;
131
+ ref: RefObject<E>;
126
132
  }) => {
127
133
  const usedRef = applyToRef || ref;
128
134
  usedRef.current?.style.setProperty(
@@ -159,6 +165,21 @@ export function useBoundsCssVars<E extends HTMLElement>(
159
165
  propertyNames?.width,
160
166
  propertyNames?.height,
161
167
  ]);
162
- const ref = useBounds<E>(update);
168
+ const disconnect = () => {
169
+ if (applyToRef?.current) {
170
+ applyToRef.current.style.removeProperty(
171
+ propertyNames?.ready ?? '--ready',
172
+ );
173
+ applyToRef.current.style.removeProperty(propertyNames?.left ?? '--left');
174
+ applyToRef.current.style.removeProperty(propertyNames?.top ?? '--top');
175
+ applyToRef.current.style.removeProperty(
176
+ propertyNames?.width ?? '--width',
177
+ );
178
+ applyToRef.current.style.removeProperty(
179
+ propertyNames?.height ?? '--height',
180
+ );
181
+ }
182
+ };
183
+ const ref = useBounds<E>(update, disconnect);
163
184
  return ref;
164
185
  }
@@ -1,8 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- export function useVirtualKeyboardBehavior(
4
- behavior: 'overlay' | 'displace' = 'overlay',
5
- ) {
3
+ export function useVirtualKeyboardBehavior(behavior: 'overlay' | 'displace') {
6
4
  useEffect(() => {
7
5
  if ('virtualKeyboard' in navigator) {
8
6
  // @ts-ignore