@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.
- package/dist/cjs/colors.stories.js +1 -8
- package/dist/cjs/colors.stories.js.map +1 -1
- package/dist/cjs/components/button/Button.js +3 -3
- package/dist/cjs/components/button/Button.js.map +1 -1
- package/dist/cjs/components/dialog/Dialog.js +30 -5
- package/dist/cjs/components/dialog/Dialog.js.map +1 -1
- package/dist/cjs/components/dialog/Dialog.stories.js +1 -1
- package/dist/cjs/components/dialog/Dialog.stories.js.map +1 -1
- package/dist/cjs/components/icon/Icon.d.ts +2 -1
- package/dist/cjs/components/icon/Icon.js +4 -3
- package/dist/cjs/components/icon/Icon.js.map +1 -1
- package/dist/cjs/components/index.d.ts +2 -0
- package/dist/cjs/components/index.js +2 -0
- package/dist/cjs/components/index.js.map +1 -1
- package/dist/cjs/components/lightbox/Lightbox.d.ts +25 -0
- package/dist/cjs/components/lightbox/Lightbox.js +71 -0
- package/dist/cjs/components/lightbox/Lightbox.js.map +1 -0
- package/dist/cjs/components/lightbox/Lightbox.stories.d.ts +24 -0
- package/dist/cjs/components/lightbox/Lightbox.stories.js +21 -0
- package/dist/cjs/components/lightbox/Lightbox.stories.js.map +1 -0
- package/dist/cjs/components/lists/lists.d.ts +6 -0
- package/dist/cjs/components/lists/lists.js +16 -0
- package/dist/cjs/components/lists/lists.js.map +1 -0
- package/dist/cjs/components/lists/lists.stories.d.ts +14 -0
- package/dist/cjs/components/lists/lists.stories.js +25 -0
- package/dist/cjs/components/lists/lists.stories.js.map +1 -0
- package/dist/cjs/components/provider/Provider.d.ts +1 -4
- package/dist/cjs/components/provider/Provider.js +3 -3
- package/dist/cjs/components/provider/Provider.js.map +1 -1
- package/dist/cjs/components/pwaInstall/PlatformIcons.d.ts +5 -0
- package/dist/cjs/components/pwaInstall/PlatformIcons.js +25 -0
- package/dist/cjs/components/pwaInstall/PlatformIcons.js.map +1 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.d.ts +5 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.js +74 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.js.map +1 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.d.ts +15 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.js +23 -0
- package/dist/cjs/components/pwaInstall/PwaInstallTrigger.stories.js.map +1 -0
- package/dist/cjs/components/pwaInstall/index.d.ts +3 -0
- package/dist/cjs/components/pwaInstall/index.js +21 -0
- package/dist/cjs/components/pwaInstall/index.js.map +1 -0
- package/dist/cjs/components/pwaInstall/useIsInstallReady.d.ts +3 -0
- package/dist/cjs/components/pwaInstall/useIsInstallReady.js +58 -0
- package/dist/cjs/components/pwaInstall/useIsInstallReady.js.map +1 -0
- package/dist/cjs/components/pwaInstall/useWebManifest.d.ts +2 -0
- package/dist/cjs/components/pwaInstall/useWebManifest.js +45 -0
- package/dist/cjs/components/pwaInstall/useWebManifest.js.map +1 -0
- package/dist/cjs/components/toasts/toasts.d.ts +4 -1
- package/dist/cjs/components/toasts/toasts.js +35 -1
- package/dist/cjs/components/toasts/toasts.js.map +1 -1
- package/dist/cjs/components/toasts/toasts.stories.d.ts +13 -0
- package/dist/cjs/components/toasts/toasts.stories.js +47 -0
- package/dist/cjs/components/toasts/toasts.stories.js.map +1 -0
- package/dist/cjs/platform.d.ts +11 -0
- package/dist/cjs/platform.js +95 -0
- package/dist/cjs/platform.js.map +1 -0
- package/dist/cjs/uno/logic/color.d.ts +0 -12
- package/dist/cjs/uno/logic/color.js +0 -35
- package/dist/cjs/uno/logic/color.js.map +1 -1
- package/dist/cjs/uno/logic/color.test.js +0 -12
- package/dist/cjs/uno/logic/color.test.js.map +1 -1
- package/dist/cjs/uno/theme/index.js +8 -0
- package/dist/cjs/uno/theme/index.js.map +1 -1
- package/dist/css/main.css +6 -5
- package/dist/esm/colors.stories.js +1 -8
- package/dist/esm/colors.stories.js.map +1 -1
- package/dist/esm/components/button/Button.js +3 -3
- package/dist/esm/components/button/Button.js.map +1 -1
- package/dist/esm/components/dialog/Dialog.js +30 -5
- package/dist/esm/components/dialog/Dialog.js.map +1 -1
- package/dist/esm/components/dialog/Dialog.stories.js +1 -1
- package/dist/esm/components/dialog/Dialog.stories.js.map +1 -1
- package/dist/esm/components/icon/Icon.d.ts +2 -1
- package/dist/esm/components/icon/Icon.js +4 -3
- package/dist/esm/components/icon/Icon.js.map +1 -1
- package/dist/esm/components/index.d.ts +2 -0
- package/dist/esm/components/index.js +2 -0
- package/dist/esm/components/index.js.map +1 -1
- package/dist/esm/components/lightbox/Lightbox.d.ts +25 -0
- package/dist/esm/components/lightbox/Lightbox.js +32 -0
- package/dist/esm/components/lightbox/Lightbox.js.map +1 -0
- package/dist/esm/components/lightbox/Lightbox.stories.d.ts +24 -0
- package/dist/esm/components/lightbox/Lightbox.stories.js +18 -0
- package/dist/esm/components/lightbox/Lightbox.stories.js.map +1 -0
- package/dist/esm/components/lists/lists.d.ts +6 -0
- package/dist/esm/components/lists/lists.js +13 -0
- package/dist/esm/components/lists/lists.js.map +1 -0
- package/dist/esm/components/lists/lists.stories.d.ts +14 -0
- package/dist/esm/components/lists/lists.stories.js +22 -0
- package/dist/esm/components/lists/lists.stories.js.map +1 -0
- package/dist/esm/components/provider/Provider.d.ts +1 -4
- package/dist/esm/components/provider/Provider.js +3 -3
- package/dist/esm/components/provider/Provider.js.map +1 -1
- package/dist/esm/components/pwaInstall/PlatformIcons.d.ts +5 -0
- package/dist/esm/components/pwaInstall/PlatformIcons.js +18 -0
- package/dist/esm/components/pwaInstall/PlatformIcons.js.map +1 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.d.ts +5 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.js +71 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.js.map +1 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.d.ts +15 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.js +20 -0
- package/dist/esm/components/pwaInstall/PwaInstallTrigger.stories.js.map +1 -0
- package/dist/esm/components/pwaInstall/index.d.ts +3 -0
- package/dist/esm/components/pwaInstall/index.js +5 -0
- package/dist/esm/components/pwaInstall/index.js.map +1 -0
- package/dist/esm/components/pwaInstall/useIsInstallReady.d.ts +3 -0
- package/dist/esm/components/pwaInstall/useIsInstallReady.js +53 -0
- package/dist/esm/components/pwaInstall/useIsInstallReady.js.map +1 -0
- package/dist/esm/components/pwaInstall/useWebManifest.d.ts +2 -0
- package/dist/esm/components/pwaInstall/useWebManifest.js +42 -0
- package/dist/esm/components/pwaInstall/useWebManifest.js.map +1 -0
- package/dist/esm/components/toasts/toasts.d.ts +4 -1
- package/dist/esm/components/toasts/toasts.js +32 -1
- package/dist/esm/components/toasts/toasts.js.map +1 -1
- package/dist/esm/components/toasts/toasts.stories.d.ts +13 -0
- package/dist/esm/components/toasts/toasts.stories.js +44 -0
- package/dist/esm/components/toasts/toasts.stories.js.map +1 -0
- package/dist/esm/platform.d.ts +11 -0
- package/dist/esm/platform.js +84 -0
- package/dist/esm/platform.js.map +1 -0
- package/dist/esm/uno/logic/color.d.ts +0 -12
- package/dist/esm/uno/logic/color.js +0 -35
- package/dist/esm/uno/logic/color.js.map +1 -1
- package/dist/esm/uno/logic/color.test.js +0 -12
- package/dist/esm/uno/logic/color.test.js.map +1 -1
- package/dist/esm/uno/theme/index.js +8 -0
- package/dist/esm/uno/theme/index.js.map +1 -1
- package/package.json +5 -7
- package/src/colors.stories.tsx +0 -12
- package/src/components/button/Button.tsx +7 -4
- package/src/components/dialog/Dialog.stories.tsx +20 -6
- package/src/components/dialog/Dialog.tsx +49 -4
- package/src/components/icon/Icon.tsx +10 -2
- package/src/components/index.ts +2 -0
- package/src/components/lightbox/Lightbox.stories.tsx +28 -0
- package/src/components/lightbox/Lightbox.tsx +87 -0
- package/src/components/lists/lists.stories.tsx +38 -0
- package/src/components/lists/lists.tsx +21 -0
- package/src/components/provider/Provider.tsx +2 -9
- package/src/components/pwaInstall/PlatformIcons.tsx +75 -0
- package/src/components/pwaInstall/PwaInstallTrigger.stories.tsx +29 -0
- package/src/components/pwaInstall/PwaInstallTrigger.tsx +227 -0
- package/src/components/pwaInstall/index.ts +3 -0
- package/src/components/pwaInstall/useIsInstallReady.ts +56 -0
- package/src/components/pwaInstall/useWebManifest.ts +55 -0
- package/src/components/toasts/toasts.stories.tsx +83 -0
- package/src/components/toasts/toasts.tsx +72 -1
- package/src/platform.ts +103 -0
- package/src/uno/logic/color.test.ts +0 -27
- package/src/uno/logic/color.ts +0 -56
- package/src/uno/theme/index.ts +8 -0
- package/dist/cjs/uno/logic/oklch.d.ts +0 -3
- package/dist/cjs/uno/logic/oklch.js +0 -96
- package/dist/cjs/uno/logic/oklch.js.map +0 -1
- package/dist/esm/uno/logic/oklch.d.ts +0 -3
- package/dist/esm/uno/logic/oklch.js +0 -90
- package/dist/esm/uno/logic/oklch.js.map +0 -1
- 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,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
|
|
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
|
+
};
|