@a-type/ui 3.0.25 → 3.0.28

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 (158) hide show
  1. package/dist/cjs/colors.stories.js +1 -8
  2. package/dist/cjs/colors.stories.js.map +1 -1
  3. package/dist/cjs/components/button/Button.js +3 -3
  4. package/dist/cjs/components/button/Button.js.map +1 -1
  5. package/dist/cjs/components/dialog/Dialog.js +30 -5
  6. package/dist/cjs/components/dialog/Dialog.js.map +1 -1
  7. package/dist/cjs/components/dialog/Dialog.stories.js +1 -1
  8. package/dist/cjs/components/dialog/Dialog.stories.js.map +1 -1
  9. package/dist/cjs/components/icon/Icon.d.ts +2 -1
  10. package/dist/cjs/components/icon/Icon.js +4 -3
  11. package/dist/cjs/components/icon/Icon.js.map +1 -1
  12. package/dist/cjs/components/index.d.ts +2 -0
  13. package/dist/cjs/components/index.js +2 -0
  14. package/dist/cjs/components/index.js.map +1 -1
  15. package/dist/cjs/components/lightbox/Lightbox.d.ts +25 -0
  16. package/dist/cjs/components/lightbox/Lightbox.js +71 -0
  17. package/dist/cjs/components/lightbox/Lightbox.js.map +1 -0
  18. package/dist/cjs/components/lightbox/Lightbox.stories.d.ts +24 -0
  19. package/dist/cjs/components/lightbox/Lightbox.stories.js +21 -0
  20. package/dist/cjs/components/lightbox/Lightbox.stories.js.map +1 -0
  21. package/dist/cjs/components/lists/lists.d.ts +6 -0
  22. package/dist/cjs/components/lists/lists.js +16 -0
  23. package/dist/cjs/components/lists/lists.js.map +1 -0
  24. package/dist/cjs/components/lists/lists.stories.d.ts +14 -0
  25. package/dist/cjs/components/lists/lists.stories.js +25 -0
  26. package/dist/cjs/components/lists/lists.stories.js.map +1 -0
  27. package/dist/cjs/components/provider/Provider.d.ts +1 -4
  28. package/dist/cjs/components/provider/Provider.js +3 -3
  29. package/dist/cjs/components/provider/Provider.js.map +1 -1
  30. package/dist/cjs/components/pwaInstall/PlatformIcons.d.ts +5 -0
  31. package/dist/cjs/components/pwaInstall/PlatformIcons.js +25 -0
  32. package/dist/cjs/components/pwaInstall/PlatformIcons.js.map +1 -0
  33. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.d.ts +5 -0
  34. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.js +74 -0
  35. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.js.map +1 -0
  36. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.d.ts +15 -0
  37. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.js +23 -0
  38. package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.js.map +1 -0
  39. package/dist/cjs/components/pwaInstall/index.d.ts +3 -0
  40. package/dist/cjs/components/pwaInstall/index.js +21 -0
  41. package/dist/cjs/components/pwaInstall/index.js.map +1 -0
  42. package/dist/cjs/components/pwaInstall/useIsInstallReady.d.ts +3 -0
  43. package/dist/cjs/components/pwaInstall/useIsInstallReady.js +58 -0
  44. package/dist/cjs/components/pwaInstall/useIsInstallReady.js.map +1 -0
  45. package/dist/cjs/components/pwaInstall/useWebManifest.d.ts +2 -0
  46. package/dist/cjs/components/pwaInstall/useWebManifest.js +45 -0
  47. package/dist/cjs/components/pwaInstall/useWebManifest.js.map +1 -0
  48. package/dist/cjs/components/toasts/toasts.d.ts +4 -1
  49. package/dist/cjs/components/toasts/toasts.js +35 -1
  50. package/dist/cjs/components/toasts/toasts.js.map +1 -1
  51. package/dist/cjs/components/toasts/toasts.stories.d.ts +13 -0
  52. package/dist/cjs/components/toasts/toasts.stories.js +47 -0
  53. package/dist/cjs/components/toasts/toasts.stories.js.map +1 -0
  54. package/dist/cjs/platform.d.ts +11 -0
  55. package/dist/cjs/platform.js +95 -0
  56. package/dist/cjs/platform.js.map +1 -0
  57. package/dist/cjs/uno/logic/color.d.ts +0 -12
  58. package/dist/cjs/uno/logic/color.js +0 -35
  59. package/dist/cjs/uno/logic/color.js.map +1 -1
  60. package/dist/cjs/uno/logic/color.test.js +0 -12
  61. package/dist/cjs/uno/logic/color.test.js.map +1 -1
  62. package/dist/cjs/uno/theme/index.js +8 -0
  63. package/dist/cjs/uno/theme/index.js.map +1 -1
  64. package/dist/css/main.css +6 -5
  65. package/dist/esm/colors.stories.js +1 -8
  66. package/dist/esm/colors.stories.js.map +1 -1
  67. package/dist/esm/components/button/Button.js +3 -3
  68. package/dist/esm/components/button/Button.js.map +1 -1
  69. package/dist/esm/components/dialog/Dialog.js +30 -5
  70. package/dist/esm/components/dialog/Dialog.js.map +1 -1
  71. package/dist/esm/components/dialog/Dialog.stories.js +1 -1
  72. package/dist/esm/components/dialog/Dialog.stories.js.map +1 -1
  73. package/dist/esm/components/icon/Icon.d.ts +2 -1
  74. package/dist/esm/components/icon/Icon.js +4 -3
  75. package/dist/esm/components/icon/Icon.js.map +1 -1
  76. package/dist/esm/components/index.d.ts +2 -0
  77. package/dist/esm/components/index.js +2 -0
  78. package/dist/esm/components/index.js.map +1 -1
  79. package/dist/esm/components/lightbox/Lightbox.d.ts +25 -0
  80. package/dist/esm/components/lightbox/Lightbox.js +32 -0
  81. package/dist/esm/components/lightbox/Lightbox.js.map +1 -0
  82. package/dist/esm/components/lightbox/Lightbox.stories.d.ts +24 -0
  83. package/dist/esm/components/lightbox/Lightbox.stories.js +18 -0
  84. package/dist/esm/components/lightbox/Lightbox.stories.js.map +1 -0
  85. package/dist/esm/components/lists/lists.d.ts +6 -0
  86. package/dist/esm/components/lists/lists.js +13 -0
  87. package/dist/esm/components/lists/lists.js.map +1 -0
  88. package/dist/esm/components/lists/lists.stories.d.ts +14 -0
  89. package/dist/esm/components/lists/lists.stories.js +22 -0
  90. package/dist/esm/components/lists/lists.stories.js.map +1 -0
  91. package/dist/esm/components/provider/Provider.d.ts +1 -4
  92. package/dist/esm/components/provider/Provider.js +3 -3
  93. package/dist/esm/components/provider/Provider.js.map +1 -1
  94. package/dist/esm/components/pwaInstall/PlatformIcons.d.ts +5 -0
  95. package/dist/esm/components/pwaInstall/PlatformIcons.js +18 -0
  96. package/dist/esm/components/pwaInstall/PlatformIcons.js.map +1 -0
  97. package/dist/esm/components/pwaInstall/PwaInstallTrigger.d.ts +5 -0
  98. package/dist/esm/components/pwaInstall/PwaInstallTrigger.js +71 -0
  99. package/dist/esm/components/pwaInstall/PwaInstallTrigger.js.map +1 -0
  100. package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.d.ts +15 -0
  101. package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.js +20 -0
  102. package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.js.map +1 -0
  103. package/dist/esm/components/pwaInstall/index.d.ts +3 -0
  104. package/dist/esm/components/pwaInstall/index.js +5 -0
  105. package/dist/esm/components/pwaInstall/index.js.map +1 -0
  106. package/dist/esm/components/pwaInstall/useIsInstallReady.d.ts +3 -0
  107. package/dist/esm/components/pwaInstall/useIsInstallReady.js +53 -0
  108. package/dist/esm/components/pwaInstall/useIsInstallReady.js.map +1 -0
  109. package/dist/esm/components/pwaInstall/useWebManifest.d.ts +2 -0
  110. package/dist/esm/components/pwaInstall/useWebManifest.js +42 -0
  111. package/dist/esm/components/pwaInstall/useWebManifest.js.map +1 -0
  112. package/dist/esm/components/toasts/toasts.d.ts +4 -1
  113. package/dist/esm/components/toasts/toasts.js +32 -1
  114. package/dist/esm/components/toasts/toasts.js.map +1 -1
  115. package/dist/esm/components/toasts/toasts.stories.d.ts +13 -0
  116. package/dist/esm/components/toasts/toasts.stories.js +44 -0
  117. package/dist/esm/components/toasts/toasts.stories.js.map +1 -0
  118. package/dist/esm/platform.d.ts +11 -0
  119. package/dist/esm/platform.js +84 -0
  120. package/dist/esm/platform.js.map +1 -0
  121. package/dist/esm/uno/logic/color.d.ts +0 -12
  122. package/dist/esm/uno/logic/color.js +0 -35
  123. package/dist/esm/uno/logic/color.js.map +1 -1
  124. package/dist/esm/uno/logic/color.test.js +0 -12
  125. package/dist/esm/uno/logic/color.test.js.map +1 -1
  126. package/dist/esm/uno/theme/index.js +8 -0
  127. package/dist/esm/uno/theme/index.js.map +1 -1
  128. package/package.json +5 -7
  129. package/src/colors.stories.tsx +0 -12
  130. package/src/components/button/Button.tsx +7 -4
  131. package/src/components/dialog/Dialog.stories.tsx +20 -6
  132. package/src/components/dialog/Dialog.tsx +49 -4
  133. package/src/components/icon/Icon.tsx +10 -2
  134. package/src/components/index.ts +2 -0
  135. package/src/components/lightbox/Lightbox.stories.tsx +28 -0
  136. package/src/components/lightbox/Lightbox.tsx +87 -0
  137. package/src/components/lists/lists.stories.tsx +38 -0
  138. package/src/components/lists/lists.tsx +21 -0
  139. package/src/components/provider/Provider.tsx +2 -9
  140. package/src/components/pwaInstall/PlatformIcons.tsx +75 -0
  141. package/src/components/pwaInstall/PwaInstallTrigger.stories.tsx +29 -0
  142. package/src/components/pwaInstall/PwaInstallTrigger.tsx +227 -0
  143. package/src/components/pwaInstall/index.ts +3 -0
  144. package/src/components/pwaInstall/useIsInstallReady.ts +56 -0
  145. package/src/components/pwaInstall/useWebManifest.ts +55 -0
  146. package/src/components/toasts/toasts.stories.tsx +83 -0
  147. package/src/components/toasts/toasts.tsx +72 -1
  148. package/src/platform.ts +103 -0
  149. package/src/uno/logic/color.test.ts +0 -27
  150. package/src/uno/logic/color.ts +0 -56
  151. package/src/uno/theme/index.ts +8 -0
  152. package/dist/cjs/uno/logic/oklch.d.ts +0 -3
  153. package/dist/cjs/uno/logic/oklch.js +0 -96
  154. package/dist/cjs/uno/logic/oklch.js.map +0 -1
  155. package/dist/esm/uno/logic/oklch.d.ts +0 -3
  156. package/dist/esm/uno/logic/oklch.js +0 -90
  157. package/dist/esm/uno/logic/oklch.js.map +0 -1
  158. package/src/uno/logic/oklch.ts +0 -120
@@ -0,0 +1,75 @@
1
+ export function SafariShareIcon() {
2
+ return (
3
+ <svg
4
+ id="pwa-share"
5
+ width="24"
6
+ height="24"
7
+ viewBox="0 0 17.695 26.475"
8
+ className="v-middle"
9
+ >
10
+ <g fill="currentColor">
11
+ <path d="M17.334 10.762v9.746c0 2.012-1.025 3.027-3.066 3.027H3.066C1.026 23.535 0 22.52 0 20.508v-9.746C0 8.75 1.025 7.734 3.066 7.734h2.94v1.573h-2.92c-.977 0-1.514.527-1.514 1.543v9.57c0 1.015.537 1.543 1.514 1.543h11.152c.967 0 1.524-.527 1.524-1.543v-9.57c0-1.016-.557-1.543-1.524-1.543h-2.91V7.734h2.94c2.04 0 3.066 1.016 3.066 3.028Z" />
12
+ <path d="M8.662 15.889c.42 0 .781-.352.781-.762V5.097l-.058-1.464.654.693 1.484 1.582a.698.698 0 0 0 .528.235c.4 0 .713-.293.713-.694 0-.205-.088-.361-.235-.508l-3.3-3.183c-.196-.196-.362-.264-.567-.264-.195 0-.361.069-.566.264L4.795 4.94a.681.681 0 0 0-.225.508c0 .4.293.694.703.694.186 0 .4-.079.538-.235l1.474-1.582.664-.693-.058 1.465v10.029c0 .41.351.762.771.762Z" />
13
+ </g>
14
+ </svg>
15
+ );
16
+ }
17
+
18
+ export function SafariIcon() {
19
+ return (
20
+ <svg
21
+ id="pwa-safari"
22
+ viewBox="0 0 20.283 19.932"
23
+ width="25"
24
+ height="25"
25
+ className="v-middle"
26
+ >
27
+ <g fill="currentColor">
28
+ <path d="M9.96 19.922c5.45 0 9.962-4.522 9.962-9.961C19.922 4.51 15.4 0 9.952 0 4.511 0 0 4.512 0 9.96c0 5.44 4.521 9.962 9.96 9.962Zm0-1.66A8.26 8.26 0 0 1 1.67 9.96c0-4.61 3.672-8.3 8.281-8.3 4.61 0 8.31 3.69 8.31 8.3 0 4.61-3.69 8.3-8.3 8.3Z" />
29
+ <path d="m5.87 14.883 5.605-2.735a1.47 1.47 0 0 0 .683-.673l2.725-5.596c.312-.664-.166-1.182-.85-.84L8.447 7.764c-.302.136-.508.341-.674.673L5.03 14.043c-.312.645.196 1.152.84.84Zm4.09-3.72A1.19 1.19 0 0 1 8.77 9.97c0-.664.527-1.201 1.19-1.201a1.2 1.2 0 0 1 1.202 1.2c0 .655-.537 1.192-1.201 1.192Z" />
30
+ </g>
31
+ </svg>
32
+ );
33
+ }
34
+
35
+ export function SafariPlusSquareIcon() {
36
+ return (
37
+ <svg
38
+ id="pwa-add"
39
+ width="24"
40
+ height="24"
41
+ viewBox="-4 -4 33 33"
42
+ fill="currentColor"
43
+ className="v-middle"
44
+ >
45
+ <g>
46
+ <path d="m23.40492,1.60784c-1.32504,-1.32504 -3.19052,-1.56912 -5.59644,-1.56912l-10.65243,0c-2.33622,0 -4.2017,0.24408 -5.5267,1.56912c-1.32504,1.34243 -1.56911,3.17306 -1.56911,5.50924l0,10.5827c0,2.40596 0.22665,4.254 1.55165,5.57902c1.34246,1.32501 3.19052,1.5691 5.59647,1.5691l10.60013,0c2.40592,0 4.2714,-0.24408 5.59644,-1.5691c1.325,-1.34245 1.55166,-3.17306 1.55166,-5.57902l0,-10.51293c0,-2.40596 -0.22666,-4.25401 -1.55166,-5.57901zm-0.38355,5.21289l0,11.24518c0,1.51681 -0.20924,2.94643 -1.02865,3.78327c-0.83683,0.83685 -2.30134,1.0635 -3.81815,1.0635l-11.33234,0c-1.51681,0 -2.96386,-0.22665 -3.80073,-1.0635c-0.83683,-0.83684 -1.04607,-2.26646 -1.04607,-3.78327l0,-11.19288c0,-1.5517 0.20924,-3.01617 1.02865,-3.85304c0.83687,-0.83683 2.31876,-1.04607 3.87042,-1.04607l11.28007,0c1.51681,0 2.98132,0.22666 3.81815,1.06353c0.81941,0.81941 1.02865,2.26645 1.02865,3.78327zm-10.53039,12.08205c0.64506,0 1.02861,-0.43586 1.02861,-1.13326l0,-4.34117l4.53294,0c0.66252,0 1.13326,-0.36613 1.13326,-0.99376c0,-0.64506 -0.43586,-1.02861 -1.13326,-1.02861l-4.53294,0l0,-4.53294c0,-0.6974 -0.38355,-1.13326 -1.02861,-1.13326c-0.62763,0 -0.99376,0.45332 -0.99376,1.13326l0,4.53294l-4.51552,0c-0.69737,0 -1.15069,0.38355 -1.15069,1.02861c0,0.62763 0.48817,0.99376 1.15069,0.99376l4.51552,0l0,4.34117c0,0.66252 0.36613,1.13326 0.99376,1.13326z" />
47
+ </g>
48
+ </svg>
49
+ );
50
+ }
51
+
52
+ export function SafariAddToDockIcon() {
53
+ return (
54
+ <svg
55
+ id="pwa-add"
56
+ viewBox="0 0 23.389 17.979"
57
+ width="24"
58
+ height="24"
59
+ className="v-middle"
60
+ >
61
+ <g fill="currentColor">
62
+ <path d="M1.045 3.291v1.377h20.937V3.291Zm2.021 14.688h16.895c2.05 0 3.066-1.006 3.066-3.018V3.027C23.027 1.016 22.012 0 19.961 0H3.066C1.026 0 0 1.016 0 3.027v11.934c0 2.012 1.025 3.018 3.066 3.018Zm.02-1.573c-.977 0-1.514-.517-1.514-1.533V3.115c0-1.015.537-1.543 1.514-1.543H19.94c.967 0 1.514.528 1.514 1.543v11.758c0 1.016-.547 1.533-1.514 1.533Z" />
63
+ <path d="M4.2 14.014c0 .508.35.85.868.85h12.92c.518 0 .87-.343.87-.85v-1.465c0-.508-.352-.85-.87-.85H5.068c-.517 0-.869.342-.869.85Z" />
64
+ </g>
65
+ </svg>
66
+ );
67
+ }
68
+
69
+ export function AndroidAddToHomeIcon() {
70
+ return (
71
+ <svg height="24" viewBox="0 -960 960 960" width="24" className="v-middle">
72
+ <path d="M320-40q-33 0-56.5-23.5T240-120v-160h80v40h400v-480H320v40h-80v-160q0-33 23.5-56.5T320-920h400q33 0 56.5 23.5T800-840v720q0 33-23.5 56.5T720-40H320Zm0-120v40h400v-40H320ZM176-280l-56-56 224-224H200v-80h280v280h-80v-144L176-280Zm144-520h400v-40H320v40Zm0 0v-40 40Zm0 640v40-40Z" />
73
+ </svg>
74
+ );
75
+ }
@@ -0,0 +1,29 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { Button } from '../button/Button.js';
3
+ import { Icon } from '../icon/Icon.js';
4
+ import { PwaInstallTrigger } from './PwaInstallTrigger.js';
5
+
6
+ const meta = {
7
+ title: 'Components/PwaInstallTrigger',
8
+ component: PwaInstallTrigger,
9
+ argTypes: {},
10
+ parameters: {
11
+ controls: { expanded: true },
12
+ },
13
+ } satisfies Meta<typeof PwaInstallTrigger>;
14
+
15
+ export default meta;
16
+
17
+ type Story = StoryObj<typeof PwaInstallTrigger>;
18
+
19
+ export const Default: Story = {
20
+ render(args) {
21
+ return (
22
+ <PwaInstallTrigger {...args} asChild>
23
+ <Button color="primary" emphasis="light">
24
+ <Icon name="star" /> Install
25
+ </Button>
26
+ </PwaInstallTrigger>
27
+ );
28
+ },
29
+ };
@@ -0,0 +1,227 @@
1
+ import { DialogTriggerProps } from '@radix-ui/react-dialog';
2
+ import { useState } from 'react';
3
+ import {
4
+ getIsSafari,
5
+ getOS,
6
+ getSupportsPWAInstallPrompt,
7
+ } from '../../platform.js';
8
+ import { Box } from '../box/Box.js';
9
+ import { Button } from '../button/Button.js';
10
+ import { Dialog } from '../dialog/Dialog.js';
11
+ import { Icon } from '../icon/Icon.js';
12
+ import { Lightbox } from '../lightbox/Lightbox.js';
13
+ import { Ol } from '../lists/lists.js';
14
+ import { P } from '../typography/typography.js';
15
+ import {
16
+ AndroidAddToHomeIcon,
17
+ SafariAddToDockIcon,
18
+ SafariIcon,
19
+ SafariPlusSquareIcon,
20
+ SafariShareIcon,
21
+ } from './PlatformIcons.js';
22
+ import {
23
+ triggerDeferredInstall,
24
+ useIsInstalled,
25
+ useIsInstallReady,
26
+ } from './useIsInstallReady.js';
27
+ import { useWebManifest } from './useWebManifest.js';
28
+
29
+ export interface PwaInstallTriggerProps extends DialogTriggerProps {
30
+ manifestPath?: string;
31
+ }
32
+
33
+ export function PwaInstallTrigger({
34
+ children,
35
+ manifestPath,
36
+ asChild: _,
37
+ ...rest
38
+ }: PwaInstallTriggerProps) {
39
+ const installed = useIsInstalled();
40
+ const manifest = useWebManifest(manifestPath);
41
+
42
+ const [showInstructions, setShowInstructions] = useState(false);
43
+
44
+ if (installed) {
45
+ return null;
46
+ }
47
+
48
+ const primaryIcon = manifest?.icons?.[0];
49
+
50
+ return (
51
+ <Dialog>
52
+ <Dialog.Trigger {...rest} asChild>
53
+ {children ?? (
54
+ <Button color="primary" emphasis="light">
55
+ <Icon name="star" /> Install
56
+ </Button>
57
+ )}
58
+ </Dialog.Trigger>
59
+ <Dialog.Content className="flex flex-col gap-xs">
60
+ <Dialog.Title className="flex flex-row gap-md items-center">
61
+ {primaryIcon && (
62
+ <img
63
+ src={primaryIcon.src}
64
+ alt={
65
+ primaryIcon.label ??
66
+ manifest?.short_name ??
67
+ manifest?.name ??
68
+ 'App Icon'
69
+ }
70
+ className="inline-block w-1em h-1em rounded"
71
+ />
72
+ )}
73
+ Install {manifest?.short_name ?? manifest?.name ?? 'App'}
74
+ </Dialog.Title>
75
+ {showInstructions ? (
76
+ <>
77
+ <Dialog.Description>
78
+ Follow the instructions below to install this app on your device.
79
+ </Dialog.Description>
80
+ <InstallInstructions />
81
+ <Dialog.Actions>
82
+ <Dialog.Close asChild>
83
+ <Button emphasis="ghost">Close</Button>
84
+ </Dialog.Close>
85
+ <Button onClick={() => setShowInstructions(false)}>
86
+ <Icon name="arrowLeft" /> Back
87
+ </Button>
88
+ </Dialog.Actions>
89
+ </>
90
+ ) : (
91
+ <>
92
+ <Dialog.Description>
93
+ This site is also an app. You can install it right now for easier
94
+ access and more features.
95
+ </Dialog.Description>
96
+ {manifest?.description && (
97
+ <P className="mb-sm">{manifest.description}</P>
98
+ )}
99
+ <ManifestImageGallery manifestPath={manifestPath} />
100
+ <Dialog.Actions>
101
+ <Dialog.Close asChild>
102
+ <Button emphasis="ghost">Close</Button>
103
+ </Dialog.Close>
104
+ <InstallDeviceActions
105
+ showInstructions={() => setShowInstructions(true)}
106
+ />
107
+ </Dialog.Actions>
108
+ </>
109
+ )}
110
+ </Dialog.Content>
111
+ </Dialog>
112
+ );
113
+ }
114
+
115
+ const supportsDirectInstall = getSupportsPWAInstallPrompt();
116
+
117
+ function InstallInstructions() {
118
+ const os = getOS();
119
+ if (os === 'iOS' || os === 'Mac OS') {
120
+ if (getIsSafari()) {
121
+ if (os === 'iOS') {
122
+ return (
123
+ <Ol>
124
+ <Ol.Item>
125
+ <SafariShareIcon /> Tap the Share button in the toolbar.
126
+ </Ol.Item>
127
+ <Ol.Item>
128
+ <SafariPlusSquareIcon /> Scroll down and tap "Add to Home Screen".
129
+ </Ol.Item>
130
+ </Ol>
131
+ );
132
+ } else {
133
+ return (
134
+ <Ol>
135
+ <Ol.Item>
136
+ <SafariShareIcon /> Tap the Share button in the toolbar.
137
+ </Ol.Item>
138
+ <Ol.Item>
139
+ <SafariAddToDockIcon /> Tap "Add to Dock".
140
+ </Ol.Item>
141
+ </Ol>
142
+ );
143
+ }
144
+ } else {
145
+ return (
146
+ <>
147
+ <Ol>
148
+ <Ol.Item>
149
+ <SafariIcon /> Open this site in Safari to continue.
150
+ </Ol.Item>
151
+ </Ol>
152
+ <Box surface p>
153
+ Apple does not allow non-Safari browsers to install web apps.
154
+ </Box>
155
+ </>
156
+ );
157
+ }
158
+ }
159
+
160
+ return (
161
+ <Ol>
162
+ <Ol.Item>
163
+ <Icon name="dots" className="rotate-90" /> Tap the menu button in the
164
+ toolbar.
165
+ </Ol.Item>
166
+ <Ol.Item>
167
+ <AndroidAddToHomeIcon /> Tap "Add to Home Screen."
168
+ </Ol.Item>
169
+ </Ol>
170
+ );
171
+ }
172
+
173
+ function ManifestImageGallery({ manifestPath }: { manifestPath?: string }) {
174
+ const manifest = useWebManifest(manifestPath);
175
+
176
+ if (!manifest?.screenshots?.length) {
177
+ return null;
178
+ }
179
+
180
+ return (
181
+ <Box overflow="auto-x" p="sm" gap className="h-240px">
182
+ {manifest.screenshots?.map((screenshot, index) => (
183
+ <Lightbox.Root key={screenshot.src}>
184
+ <Lightbox.Trigger asChild>
185
+ <Lightbox.Image
186
+ tabIndex={0}
187
+ key={index}
188
+ src={screenshot.src}
189
+ alt={screenshot.label || `Screenshot ${index + 1}`}
190
+ className="border border-default rounded-xs"
191
+ />
192
+ </Lightbox.Trigger>
193
+ <Lightbox.Portal>
194
+ <Lightbox.Overlay className="z-10000" />
195
+ <Lightbox.Content className="z-10001">
196
+ <Lightbox.Image
197
+ src={screenshot.src}
198
+ alt={screenshot.label || `Screenshot ${index + 1}`}
199
+ />
200
+ </Lightbox.Content>
201
+ </Lightbox.Portal>
202
+ </Lightbox.Root>
203
+ ))}
204
+ </Box>
205
+ );
206
+ }
207
+
208
+ function InstallDeviceActions({
209
+ showInstructions,
210
+ }: {
211
+ showInstructions: () => void;
212
+ }) {
213
+ const ready = useIsInstallReady();
214
+ if (supportsDirectInstall && ready) {
215
+ return (
216
+ <Button onClick={() => triggerDeferredInstall()} emphasis="primary">
217
+ <Icon name="download" /> Install now
218
+ </Button>
219
+ );
220
+ }
221
+
222
+ return (
223
+ <Button onClick={() => showInstructions()} emphasis="primary">
224
+ <Icon name="arrowRight" /> Add to Home
225
+ </Button>
226
+ );
227
+ }
@@ -0,0 +1,3 @@
1
+ export * from './PwaInstallTrigger.js';
2
+ export * from './useIsInstallReady.js';
3
+ export * from './useWebManifest.js';
@@ -0,0 +1,56 @@
1
+ import { proxy, useSnapshot } from 'valtio';
2
+ import { getIsPWAInstalled, PRETEND_INSTALLABLE } from '../../platform.js';
3
+
4
+ let deferredPrompt: BeforeInstallPromptEvent | null = null;
5
+ const installState = proxy({
6
+ installReady: false,
7
+ installed: getIsPWAInstalled(),
8
+ });
9
+
10
+ if (typeof window !== 'undefined') {
11
+ window.addEventListener('beforeinstallprompt', (e) => {
12
+ // Prevent the mini-infobar from appearing on mobile
13
+ e.preventDefault();
14
+ e.stopImmediatePropagation();
15
+ e.stopPropagation();
16
+ // Stash the event so it can be triggered later.
17
+ deferredPrompt = e;
18
+ // Update UI notify the user they can install the PWA
19
+ installState.installReady = true;
20
+ // Optionally, send analytics event that PWA install promo was shown.
21
+ console.log(`Ready to show custom install prompt`);
22
+ });
23
+ }
24
+
25
+ if (PRETEND_INSTALLABLE) {
26
+ installState.installReady = true;
27
+ }
28
+
29
+ export function useIsInstallReady() {
30
+ return useSnapshot(installState).installReady;
31
+ }
32
+
33
+ export function useIsInstalled() {
34
+ return useSnapshot(installState).installed;
35
+ }
36
+
37
+ export function triggerDeferredInstall() {
38
+ if (!deferredPrompt) {
39
+ return false;
40
+ }
41
+ // Show the install prompt
42
+ deferredPrompt.prompt();
43
+ // Wait for the user to respond to the prompt
44
+ deferredPrompt.userChoice.then((choiceResult) => {
45
+ if (choiceResult.outcome === 'accepted') {
46
+ console.log('User accepted the install prompt');
47
+ installState.installed = true;
48
+ } else {
49
+ console.log('User dismissed the install prompt');
50
+ }
51
+ // Clear the deferredPrompt so it can only be used once.
52
+ deferredPrompt = null;
53
+ installState.installReady = false;
54
+ });
55
+ return true;
56
+ }
@@ -0,0 +1,55 @@
1
+ import { useEffect } from 'react';
2
+ import { proxy, useSnapshot } from 'valtio';
3
+ import { WebAppManifest } from 'web-app-manifest';
4
+
5
+ async function fetchManifest(
6
+ manifestPath: string = '/manifest.json',
7
+ controller?: AbortController,
8
+ ): Promise<WebAppManifest | undefined> {
9
+ const response = await fetch(manifestPath, {
10
+ signal: controller?.signal,
11
+ });
12
+ if (!response.ok) {
13
+ console.error(
14
+ `Failed to fetch web manifest: ${response.status} ${response.statusText}`,
15
+ );
16
+ return {};
17
+ }
18
+
19
+ const manifest = await response.json();
20
+ return manifest;
21
+ }
22
+
23
+ function abortableManifestSync(manifestPath: string) {
24
+ const controller = new AbortController();
25
+ fetchManifest(manifestPath, controller).then((manifest) => {
26
+ if (manifest) {
27
+ manifestState.value = manifest;
28
+ }
29
+ });
30
+ return controller;
31
+ }
32
+
33
+ const manifestState = proxy({
34
+ value: {} as WebAppManifest,
35
+ });
36
+ let abortController: AbortController | null = null;
37
+
38
+ export function useWebManifest(
39
+ manifestPath = '/manifest.json',
40
+ ): WebAppManifest {
41
+ useEffect(() => {
42
+ if (abortController) {
43
+ abortController.abort();
44
+ }
45
+ abortController = abortableManifestSync(manifestPath);
46
+
47
+ return () => {
48
+ if (abortController) {
49
+ abortController.abort();
50
+ }
51
+ };
52
+ }, [manifestPath]);
53
+
54
+ return useSnapshot(manifestState).value as WebAppManifest;
55
+ }
@@ -0,0 +1,83 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { toast } from 'react-hot-toast';
3
+ import { Box } from '../box/Box.js';
4
+ import { Button } from '../button/Button.js';
5
+
6
+ const meta = {
7
+ title: 'Components/toasts',
8
+ argTypes: {},
9
+ parameters: {
10
+ controls: { expanded: true },
11
+ },
12
+ } satisfies Meta;
13
+
14
+ export default meta;
15
+
16
+ type Story = StoryObj;
17
+
18
+ export const Default: Story = {
19
+ render(args) {
20
+ return (
21
+ <Box col gap>
22
+ <Button
23
+ onClick={() => {
24
+ toast(
25
+ 'This is a default toast! With a lot of text. Enough to wrap around.',
26
+ {
27
+ duration: 10_000,
28
+ },
29
+ );
30
+ }}
31
+ >
32
+ Show Default Toast
33
+ </Button>
34
+ <Button
35
+ color="success"
36
+ onClick={() => {
37
+ toast.success('This is a success toast!', {
38
+ duration: 10_000,
39
+ });
40
+ }}
41
+ >
42
+ Show Success Toast
43
+ </Button>
44
+ <Button
45
+ color="attention"
46
+ onClick={() => {
47
+ toast.error('This is an error toast!', {
48
+ duration: 10_000,
49
+ });
50
+ }}
51
+ >
52
+ Show Error Toast
53
+ </Button>
54
+ <Button
55
+ onClick={() => {
56
+ const id = toast.loading('This is a loading toast!');
57
+ setTimeout(() => {
58
+ toast.success('Loading complete!', { id, duration: 5000 });
59
+ }, 3000);
60
+ }}
61
+ >
62
+ Show Loading Toast
63
+ </Button>
64
+ <Button
65
+ onClick={() => {
66
+ toast.promise(
67
+ (async () => {
68
+ await new Promise((resolve) => setTimeout(resolve, 3000));
69
+ })(),
70
+ {
71
+ loading: 'Promise is loading...',
72
+ success: 'Promise resolved!',
73
+ error: 'Promise rejected.',
74
+ },
75
+ );
76
+ }}
77
+ >
78
+ Show Promise Toast
79
+ </Button>
80
+ </Box>
81
+ );
82
+ },
83
+ };
@@ -1,2 +1,73 @@
1
1
  export type * from 'react-hot-toast';
2
- export { toast, Toaster } from 'react-hot-toast';
2
+ export { toast } from 'react-hot-toast';
3
+ import clsx from 'clsx';
4
+ import { AnimatePresence, motion } from 'motion/react';
5
+ import { DefaultToastOptions, useToaster } from 'react-hot-toast';
6
+ import { useResolvedColorMode } from '../../colorMode.js';
7
+ import { Icon } from '../icon/Icon.js';
8
+
9
+ const toastOptions: DefaultToastOptions = {};
10
+
11
+ export const Toaster = (props: { className?: string }) => {
12
+ const mode = useResolvedColorMode();
13
+ const { toasts, handlers } = useToaster(toastOptions);
14
+ const { startPause, endPause } = handlers;
15
+ const visibleToasts = toasts.filter((t) => t.visible);
16
+
17
+ return (
18
+ <div
19
+ className={clsx(
20
+ 'fixed z-toast flex flex-col items-center gap-xs left-1/2 center-x top-sm max-w-400px',
21
+ mode === 'dark' ? 'override-light' : 'override-dark',
22
+ props.className,
23
+ )}
24
+ onMouseEnter={startPause}
25
+ onMouseLeave={endPause}
26
+ >
27
+ <AnimatePresence>
28
+ {visibleToasts.map((toast) => {
29
+ const message =
30
+ typeof toast.message === 'function'
31
+ ? toast.message(toast)
32
+ : toast.message;
33
+ return (
34
+ <motion.div
35
+ key={toast.id}
36
+ className={clsx(
37
+ {
38
+ 'palette-success': toast.type === 'success',
39
+ 'palette-attention': toast.type === 'error',
40
+ 'palette-info': toast.type === 'blank',
41
+ },
42
+ 'bg-main-wash color-black rounded-md shadow-md px-md py-sm',
43
+ 'flex flex-row gap-sm',
44
+ )}
45
+ {...toast.ariaProps}
46
+ initial={{ scale: 0.8, opacity: 0, y: -20 }}
47
+ exit={{ scale: 0.8, opacity: 0, y: -20 }}
48
+ animate={{
49
+ scale: 1,
50
+ opacity: 1,
51
+ y: 0,
52
+ }}
53
+ layout
54
+ >
55
+ <Icon
56
+ className="mt-2px"
57
+ loading={toast.type === 'loading'}
58
+ name={
59
+ toast.type === 'success'
60
+ ? 'check'
61
+ : toast.type === 'error'
62
+ ? 'warning'
63
+ : 'info'
64
+ }
65
+ />
66
+ {message}
67
+ </motion.div>
68
+ );
69
+ })}
70
+ </AnimatePresence>
71
+ </div>
72
+ );
73
+ };