@kalink-ui/seedly 0.15.0 → 0.17.0
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/CHANGELOG.md +13 -0
- package/package.json +33 -37
- package/src/components/card/card.tsx +3 -1
- package/src/components/command/command-group.css.ts +4 -0
- package/src/components/command/command-input.css.ts +22 -17
- package/src/components/command/command-item.css.ts +15 -10
- package/src/components/command/command-list.css.ts +46 -10
- package/src/components/command/command-list.tsx +10 -2
- package/src/components/command/command-separator.tsx +2 -3
- package/src/components/command/command.tsx +3 -1
- package/src/components/loader-overlay/loader-overlay.css.ts +1 -1
- package/src/components/loader-overlay/loader-overlay.tsx +6 -2
- package/src/components/menu/menu-separator.css.ts +1 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.css.ts +127 -0
- package/src/components/skeleton/skeleton.tsx +32 -0
- package/src/index.ts +0 -1
- package/src/hooks/index.ts +0 -1
- package/src/hooks/use-local-storage.ts +0 -99
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# @kalink-ui/seedly
|
|
2
2
|
|
|
3
|
+
## 0.17.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 6d46df7: Fix styling issues after Stack component update
|
|
8
|
+
- 141fc36: [Skeleton] Add new Skeleton component
|
|
9
|
+
|
|
10
|
+
## 0.16.0
|
|
11
|
+
|
|
12
|
+
### Minor Changes
|
|
13
|
+
|
|
14
|
+
- 5af6427: [useLocalStorage] Move hook in the dibbly package
|
|
15
|
+
|
|
3
16
|
## 0.15.0
|
|
4
17
|
|
|
5
18
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kalink-ui/seedly",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "A set of components for building UIs with React and TypeScript",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"license": "MIT",
|
|
@@ -16,38 +16,34 @@
|
|
|
16
16
|
"./styles/layers": "./src/styles/layers.css.ts"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@chromatic-com/storybook": "^
|
|
20
|
-
"@storybook/addon-docs": "^
|
|
21
|
-
"@storybook/addon-
|
|
22
|
-
"@storybook/
|
|
23
|
-
"@
|
|
24
|
-
"@
|
|
25
|
-
"@
|
|
26
|
-
"@
|
|
27
|
-
"@
|
|
28
|
-
"@
|
|
29
|
-
"@
|
|
30
|
-
"@
|
|
31
|
-
"@
|
|
32
|
-
"@vanilla-extract/
|
|
33
|
-
"@
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"storybook": "^8.6.9",
|
|
45
|
-
"type-fest": "^4.37.0",
|
|
46
|
-
"typescript": "^5.8.2",
|
|
47
|
-
"vite": "^6.2.1",
|
|
19
|
+
"@chromatic-com/storybook": "^4.0.0",
|
|
20
|
+
"@storybook/addon-docs": "^9.0.8",
|
|
21
|
+
"@storybook/addon-onboarding": "^9.0.8",
|
|
22
|
+
"@storybook/react-vite": "^9.0.8",
|
|
23
|
+
"@turbo/gen": "^2.5.4",
|
|
24
|
+
"@types/node": "^24.0.0",
|
|
25
|
+
"@types/react": "19.1.7",
|
|
26
|
+
"@types/react-dom": "19.1.6",
|
|
27
|
+
"@vanilla-extract/css": "^1.17.3",
|
|
28
|
+
"@vanilla-extract/css-utils": "^0.1.5",
|
|
29
|
+
"@vanilla-extract/dynamic": "^2.1.4",
|
|
30
|
+
"@vanilla-extract/recipes": "^0.5.6",
|
|
31
|
+
"@vanilla-extract/sprinkles": "^1.6.4",
|
|
32
|
+
"@vanilla-extract/vite-plugin": "^5.0.5",
|
|
33
|
+
"@vitejs/plugin-react": "^4.5.2",
|
|
34
|
+
"eslint": "^9.28.0",
|
|
35
|
+
"eslint-plugin-storybook": "9.0.8",
|
|
36
|
+
"lucide-react": "^0.514.0",
|
|
37
|
+
"react": "^19.1.0",
|
|
38
|
+
"react-docgen-typescript": "^2.4.0",
|
|
39
|
+
"react-dom": "^19.1.0",
|
|
40
|
+
"storybook": "^9.0.8",
|
|
41
|
+
"type-fest": "^4.41.0",
|
|
42
|
+
"typescript": "^5.8.3",
|
|
43
|
+
"vite": "^6.3.5",
|
|
48
44
|
"vite-tsconfig-paths": "^5.1.4",
|
|
49
|
-
"vitest": "^3.
|
|
50
|
-
"@kalink-ui/dibbly": "0.
|
|
45
|
+
"vitest": "^3.2.3",
|
|
46
|
+
"@kalink-ui/dibbly": "0.5.0",
|
|
51
47
|
"@kalink-ui/eslint-config": "0.9.0",
|
|
52
48
|
"@kalink-ui/typescript-config": "0.4.0"
|
|
53
49
|
},
|
|
@@ -62,11 +58,11 @@
|
|
|
62
58
|
"typescript": "^5.8.2"
|
|
63
59
|
},
|
|
64
60
|
"dependencies": {
|
|
65
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
66
|
-
"@radix-ui/react-popover": "^1.1.
|
|
67
|
-
"@radix-ui/react-scroll-area": "^1.2.
|
|
68
|
-
"@radix-ui/react-select": "^2.2.
|
|
69
|
-
"@radix-ui/react-slot": "^1.2.
|
|
61
|
+
"@radix-ui/react-dialog": "^1.1.14",
|
|
62
|
+
"@radix-ui/react-popover": "^1.1.14",
|
|
63
|
+
"@radix-ui/react-scroll-area": "^1.2.9",
|
|
64
|
+
"@radix-ui/react-select": "^2.2.5",
|
|
65
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
70
66
|
"clsx": "^2.1.1",
|
|
71
67
|
"cmdk": "^1.1.1"
|
|
72
68
|
},
|
|
@@ -38,7 +38,9 @@ export function Card<TUse extends CardRootElement = 'article'>(
|
|
|
38
38
|
className={clsx(card, className)}
|
|
39
39
|
{...rest}
|
|
40
40
|
>
|
|
41
|
-
<Stack spacing={verticalSpacing ?? spacing}>
|
|
41
|
+
<Stack align="stretch" spacing={verticalSpacing ?? spacing}>
|
|
42
|
+
{children}
|
|
43
|
+
</Stack>
|
|
42
44
|
</Box>
|
|
43
45
|
);
|
|
44
46
|
}
|
|
@@ -1,31 +1,36 @@
|
|
|
1
1
|
import { createVar, style } from '@vanilla-extract/css';
|
|
2
2
|
|
|
3
3
|
import { sys, transition } from '../../styles';
|
|
4
|
+
import { components } from '../../styles/layers.css';
|
|
4
5
|
|
|
5
6
|
const outlineColor = createVar();
|
|
6
7
|
|
|
7
8
|
export const commandInputWrapper = style({
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
'@layer': {
|
|
10
|
+
[components]: {
|
|
11
|
+
padding: sys.spacing[2],
|
|
12
|
+
|
|
13
|
+
borderBottomWidth: 1,
|
|
14
|
+
borderBottomStyle: 'solid',
|
|
15
|
+
borderBottomColor: outlineColor,
|
|
16
|
+
|
|
17
|
+
transition: transition(['border-color', 'box-shadow'], {
|
|
18
|
+
duration: 'short.2',
|
|
19
|
+
}),
|
|
20
|
+
|
|
21
|
+
selectors: {
|
|
22
|
+
'&:focus, &:focus-within, &:focus-visible': {
|
|
23
|
+
boxShadow: 'unset',
|
|
24
|
+
|
|
25
|
+
vars: {
|
|
26
|
+
[outlineColor]: sys.color.foreground,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
21
30
|
|
|
22
31
|
vars: {
|
|
23
32
|
[outlineColor]: sys.color.foreground,
|
|
24
33
|
},
|
|
25
34
|
},
|
|
26
35
|
},
|
|
27
|
-
|
|
28
|
-
vars: {
|
|
29
|
-
[outlineColor]: sys.color.foreground,
|
|
30
|
-
},
|
|
31
36
|
});
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
|
|
2
2
|
|
|
3
3
|
import { sys } from '../../styles';
|
|
4
|
+
import { components } from '../../styles/layers.css';
|
|
4
5
|
|
|
5
6
|
export const commandItem = recipe({
|
|
6
7
|
base: {
|
|
7
|
-
|
|
8
|
+
'@layer': {
|
|
9
|
+
[components]: {
|
|
10
|
+
cursor: 'pointer',
|
|
8
11
|
|
|
9
|
-
|
|
12
|
+
color: sys.color.foreground,
|
|
10
13
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
selectors: {
|
|
15
|
+
'&[data-selected=true]': {
|
|
16
|
+
backgroundColor: `color-mix(in srgb, ${sys.color.foreground} calc(${sys.state.muted.dark} * 100%), transparent)`,
|
|
17
|
+
outline: 'none',
|
|
18
|
+
},
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
'&:active': {
|
|
21
|
+
backgroundColor: `color-mix(in srgb, ${sys.color.foreground} calc(${sys.state.focused} * 100%), transparent)`,
|
|
22
|
+
outline: 'none',
|
|
23
|
+
},
|
|
24
|
+
},
|
|
20
25
|
},
|
|
21
26
|
},
|
|
22
27
|
},
|
|
@@ -1,14 +1,50 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createVar, globalStyle } from '@vanilla-extract/css';
|
|
2
|
+
import { recipe } from '@vanilla-extract/recipes';
|
|
2
3
|
|
|
3
|
-
import { sys, transition } from '../../styles';
|
|
4
|
+
import { mapContractVars, sys, transition } from '../../styles';
|
|
5
|
+
import { components } from '../../styles/layers.css';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
width: '100%',
|
|
7
|
-
maxHeight: 350,
|
|
8
|
-
overflow: 'auto',
|
|
9
|
-
paddingInline: sys.spacing[2],
|
|
10
|
-
paddingBlockEnd: sys.spacing[2],
|
|
7
|
+
const spacingVar = createVar();
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
export const commandList = recipe({
|
|
10
|
+
base: {
|
|
11
|
+
'@layer': {
|
|
12
|
+
[components]: {
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexDirection: 'column',
|
|
15
|
+
gap: spacingVar,
|
|
16
|
+
|
|
17
|
+
width: '100%',
|
|
18
|
+
maxHeight: 350,
|
|
19
|
+
overflow: 'auto',
|
|
20
|
+
paddingInline: sys.spacing[2],
|
|
21
|
+
paddingBlockEnd: sys.spacing[2],
|
|
22
|
+
|
|
23
|
+
overscrollBehavior: 'contain',
|
|
24
|
+
transition: transition(['height']),
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
variants: {
|
|
30
|
+
spacing: mapContractVars(sys.spacing, (key) => ({
|
|
31
|
+
'@layer': {
|
|
32
|
+
[components]: {
|
|
33
|
+
vars: {
|
|
34
|
+
[spacingVar]: sys.spacing[key],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
})),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
globalStyle(`${commandList.classNames.base} [cmdk-list-sizer]`, {
|
|
43
|
+
'@layer': {
|
|
44
|
+
[components]: {
|
|
45
|
+
display: 'flex',
|
|
46
|
+
flexDirection: 'column',
|
|
47
|
+
gap: spacingVar,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
14
50
|
});
|
|
@@ -4,15 +4,23 @@ import { clsx } from 'clsx';
|
|
|
4
4
|
import { Command as CommandPrimitive } from 'cmdk';
|
|
5
5
|
import { ComponentPropsWithRef } from 'react';
|
|
6
6
|
|
|
7
|
+
import { StackProps } from '../stack';
|
|
8
|
+
|
|
7
9
|
import { commandList } from './command-list.css';
|
|
8
10
|
|
|
11
|
+
type CommandListProps = ComponentPropsWithRef<typeof CommandPrimitive.List> & {
|
|
12
|
+
spacing?: StackProps<'div'>['spacing'];
|
|
13
|
+
};
|
|
14
|
+
|
|
9
15
|
export function CommandList({
|
|
10
16
|
className,
|
|
17
|
+
spacing = 4,
|
|
11
18
|
...props
|
|
12
|
-
}:
|
|
19
|
+
}: CommandListProps) {
|
|
13
20
|
return (
|
|
14
21
|
<CommandPrimitive.List
|
|
15
|
-
className={clsx(commandList, className)}
|
|
22
|
+
className={clsx(commandList({ spacing }), className)}
|
|
23
|
+
asChild
|
|
16
24
|
{...props}
|
|
17
25
|
/>
|
|
18
26
|
);
|
|
@@ -12,17 +12,16 @@ import {
|
|
|
12
12
|
export type CommandSeparatorProps = ComponentPropsWithRef<
|
|
13
13
|
typeof CommandPrimitive.Separator
|
|
14
14
|
> &
|
|
15
|
-
MenuSeparatorVariants
|
|
15
|
+
Omit<MenuSeparatorVariants, 'spacing'>;
|
|
16
16
|
|
|
17
17
|
export function CommandSeparator({
|
|
18
18
|
className,
|
|
19
|
-
spacing = 4,
|
|
20
19
|
offset = true,
|
|
21
20
|
...props
|
|
22
21
|
}: CommandSeparatorProps) {
|
|
23
22
|
return (
|
|
24
23
|
<CommandPrimitive.Separator
|
|
25
|
-
className={clsx(menuSeparator({ offset
|
|
24
|
+
className={clsx(menuSeparator({ offset }), className)}
|
|
26
25
|
{...props}
|
|
27
26
|
/>
|
|
28
27
|
);
|
|
@@ -18,7 +18,9 @@ export function Command({
|
|
|
18
18
|
}: CommandProps) {
|
|
19
19
|
return (
|
|
20
20
|
<CommandPrimitive className={clsx(className)} {...props}>
|
|
21
|
-
<Stack spacing={spacing}>
|
|
21
|
+
<Stack spacing={spacing} align="stretch">
|
|
22
|
+
{children}
|
|
23
|
+
</Stack>
|
|
22
24
|
</CommandPrimitive>
|
|
23
25
|
);
|
|
24
26
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { clsx } from 'clsx';
|
|
2
2
|
|
|
3
|
+
import { Box } from '../box';
|
|
3
4
|
import { Center } from '../center';
|
|
4
5
|
import { MoonLoader } from '../loader';
|
|
5
6
|
import { Stack, StackProps } from '../stack';
|
|
@@ -20,11 +21,14 @@ export function LoaderOverlay({
|
|
|
20
21
|
spacing = 2,
|
|
21
22
|
}: LoaderOverlayProps) {
|
|
22
23
|
return (
|
|
23
|
-
<
|
|
24
|
+
<Box
|
|
25
|
+
spacing={spacing}
|
|
26
|
+
className={clsx(loaderOverlay({ position }), className)}
|
|
27
|
+
>
|
|
24
28
|
<Stack use={Center} align="center" spacing={spacing} intrinsic andText>
|
|
25
29
|
<MoonLoader active forceMount />
|
|
26
30
|
{text && <Text>{text}</Text>}
|
|
27
31
|
</Stack>
|
|
28
|
-
</
|
|
32
|
+
</Box>
|
|
29
33
|
);
|
|
30
34
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Skeleton } from './skeleton';
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { globalStyle, keyframes } from '@vanilla-extract/css';
|
|
2
|
+
import { recipe, RecipeVariants } from '@vanilla-extract/recipes';
|
|
3
|
+
|
|
4
|
+
import { sys, typography } from '../../styles';
|
|
5
|
+
import { components } from '../../styles/layers.css';
|
|
6
|
+
|
|
7
|
+
const pulseKeyframe = keyframes({
|
|
8
|
+
'0%': {
|
|
9
|
+
opacity: 1,
|
|
10
|
+
},
|
|
11
|
+
|
|
12
|
+
'50%': {
|
|
13
|
+
opacity: 0.4,
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
'100%': {
|
|
17
|
+
opacity: 1,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export const skeleton = recipe({
|
|
22
|
+
base: {
|
|
23
|
+
'@layer': {
|
|
24
|
+
[components]: {
|
|
25
|
+
display: 'block',
|
|
26
|
+
|
|
27
|
+
backgroundColor: `color-mix(in srgb, ${sys.color.foreground} 10%, transparent)`,
|
|
28
|
+
|
|
29
|
+
animationName: pulseKeyframe,
|
|
30
|
+
animationDuration: '1500ms',
|
|
31
|
+
animationIterationCount: 'infinite',
|
|
32
|
+
animationTimingFunction: sys.motion.easing.standard,
|
|
33
|
+
animationDelay: '500ms',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
variants: {
|
|
39
|
+
type: {
|
|
40
|
+
text: [
|
|
41
|
+
typography.body.medium,
|
|
42
|
+
{
|
|
43
|
+
'@layer': {
|
|
44
|
+
[components]: {
|
|
45
|
+
selectors: {
|
|
46
|
+
'&:empty::before': {
|
|
47
|
+
content: '"\\00a0"',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
|
|
55
|
+
field: {
|
|
56
|
+
'@layer': {
|
|
57
|
+
[components]: {
|
|
58
|
+
display: 'block',
|
|
59
|
+
width: '100%',
|
|
60
|
+
height: sys.spacing[6],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
paragraph: {
|
|
66
|
+
'@layer': {
|
|
67
|
+
[components]: {
|
|
68
|
+
display: 'block',
|
|
69
|
+
width: '100%',
|
|
70
|
+
height: sys.spacing[16],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
block: {
|
|
76
|
+
'@layer': {
|
|
77
|
+
[components]: {
|
|
78
|
+
aspectRatio: '1',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
withChildren: {
|
|
85
|
+
true: {
|
|
86
|
+
'@layer': {
|
|
87
|
+
[components]: {
|
|
88
|
+
maxWidth: 'fit-content',
|
|
89
|
+
height: 'auto',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
corner: {
|
|
96
|
+
square: {
|
|
97
|
+
'@layer': {
|
|
98
|
+
[components]: {
|
|
99
|
+
borderRadius: sys.shape.corner.none,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
|
|
104
|
+
rounded: {
|
|
105
|
+
'@layer': {
|
|
106
|
+
[components]: {
|
|
107
|
+
borderRadius: sys.shape.corner.small,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
|
|
112
|
+
circle: {
|
|
113
|
+
'@layer': {
|
|
114
|
+
[components]: {
|
|
115
|
+
borderRadius: sys.shape.corner.circle,
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
globalStyle(`${skeleton.classNames.variants.withChildren.true} *`, {
|
|
124
|
+
visibility: 'hidden',
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
export type SkeletonVariants = NonNullable<RecipeVariants<typeof skeleton>>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { clsx } from 'clsx';
|
|
2
|
+
import { Children, ElementType } from 'react';
|
|
3
|
+
|
|
4
|
+
import { Box, BoxProps } from '../box';
|
|
5
|
+
|
|
6
|
+
import { skeleton, SkeletonVariants } from './skeleton.css';
|
|
7
|
+
|
|
8
|
+
export type SkeletonProps<TUse extends ElementType> = Omit<
|
|
9
|
+
SkeletonVariants,
|
|
10
|
+
'withChildren'
|
|
11
|
+
> &
|
|
12
|
+
BoxProps<TUse>;
|
|
13
|
+
|
|
14
|
+
export const Skeleton = <TUse extends ElementType>({
|
|
15
|
+
children,
|
|
16
|
+
className,
|
|
17
|
+
type = 'text',
|
|
18
|
+
radius = 'small',
|
|
19
|
+
...props
|
|
20
|
+
}: SkeletonProps<TUse>) => {
|
|
21
|
+
const withChildren = Children.count(children) > 0;
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Box
|
|
25
|
+
className={clsx(skeleton({ withChildren, type }), className)}
|
|
26
|
+
radius={radius}
|
|
27
|
+
{...props}
|
|
28
|
+
>
|
|
29
|
+
{children}
|
|
30
|
+
</Box>
|
|
31
|
+
);
|
|
32
|
+
};
|
package/src/index.ts
CHANGED
package/src/hooks/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { useLocalStorage } from './use-local-storage';
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import { useCallback, useEffect, useSyncExternalStore } from 'react';
|
|
4
|
-
|
|
5
|
-
type Serializable =
|
|
6
|
-
| string
|
|
7
|
-
| number
|
|
8
|
-
| boolean
|
|
9
|
-
| null
|
|
10
|
-
| Serializable[]
|
|
11
|
-
| { [key: string]: Serializable };
|
|
12
|
-
|
|
13
|
-
type StateUpdater<T> = (oldValue: T) => T;
|
|
14
|
-
|
|
15
|
-
function dispatchStorageEvent(key: string, newValue: string | null) {
|
|
16
|
-
window.dispatchEvent(new StorageEvent('storage', { key, newValue }));
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function subscribeToStorageEvent(callback: () => void) {
|
|
20
|
-
window.addEventListener('storage', callback);
|
|
21
|
-
|
|
22
|
-
return () => window.removeEventListener('storage', callback);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function setLocalStorage<T>(key: string, value: T) {
|
|
26
|
-
const stringifiedValue = JSON.stringify(value);
|
|
27
|
-
|
|
28
|
-
window.localStorage.setItem(key, stringifiedValue);
|
|
29
|
-
|
|
30
|
-
dispatchStorageEvent(key, stringifiedValue);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function removeLocalStorage(key: string) {
|
|
34
|
-
window.localStorage.removeItem(key);
|
|
35
|
-
|
|
36
|
-
dispatchStorageEvent(key, null);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function getLocalStorage(key: string) {
|
|
40
|
-
return window.localStorage.getItem(key);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getLocalStorageServerSnapshot<T>(initialValue: T) {
|
|
44
|
-
const initialSnapshot = JSON.stringify(initialValue);
|
|
45
|
-
|
|
46
|
-
return () => initialSnapshot;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
const cachedStore = new Map<string, Serializable>();
|
|
50
|
-
|
|
51
|
-
export function useLocalStorage<T extends Serializable>(
|
|
52
|
-
key: string,
|
|
53
|
-
initialValue: T,
|
|
54
|
-
): [T, (value: T | StateUpdater<T>) => void] {
|
|
55
|
-
const getSnapshot = () => getLocalStorage(key);
|
|
56
|
-
|
|
57
|
-
const store = useSyncExternalStore(
|
|
58
|
-
subscribeToStorageEvent,
|
|
59
|
-
getSnapshot,
|
|
60
|
-
getLocalStorageServerSnapshot(initialValue),
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
const setValue = useCallback(
|
|
64
|
-
(value: T | StateUpdater<T>) => {
|
|
65
|
-
try {
|
|
66
|
-
const newValue =
|
|
67
|
-
typeof value === 'function'
|
|
68
|
-
? value(store ? JSON.parse(store) : null)
|
|
69
|
-
: value;
|
|
70
|
-
|
|
71
|
-
if (newValue === undefined || newValue === null) {
|
|
72
|
-
removeLocalStorage(key);
|
|
73
|
-
} else {
|
|
74
|
-
setLocalStorage(key, newValue);
|
|
75
|
-
}
|
|
76
|
-
} catch (error) {
|
|
77
|
-
console.error(error);
|
|
78
|
-
}
|
|
79
|
-
},
|
|
80
|
-
[key, store],
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (getLocalStorage(key) === null && typeof initialValue !== 'undefined') {
|
|
85
|
-
setLocalStorage(key, initialValue);
|
|
86
|
-
}
|
|
87
|
-
}, [key, initialValue]);
|
|
88
|
-
|
|
89
|
-
if (!store) {
|
|
90
|
-
return [initialValue, setValue];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (!cachedStore.has(store)) {
|
|
94
|
-
cachedStore.clear();
|
|
95
|
-
cachedStore.set(store, JSON.parse(store));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return [cachedStore.get(store) as T, setValue];
|
|
99
|
-
}
|