@a-type/ui 2.0.11 → 2.1.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.
Files changed (47) hide show
  1. package/dist/cjs/colors.stories.d.ts +20 -1
  2. package/dist/cjs/colors.stories.js +30 -5
  3. package/dist/cjs/colors.stories.js.map +1 -1
  4. package/dist/cjs/components/emojiPicker/EmojiPicker.d.ts +28 -0
  5. package/dist/cjs/components/emojiPicker/EmojiPicker.js +79 -0
  6. package/dist/cjs/components/emojiPicker/EmojiPicker.js.map +1 -0
  7. package/dist/cjs/components/emojiPicker/EmojiPicker.stories.d.ts +25 -0
  8. package/dist/cjs/components/emojiPicker/EmojiPicker.stories.js +21 -0
  9. package/dist/cjs/components/emojiPicker/EmojiPicker.stories.js.map +1 -0
  10. package/dist/cjs/components/index.d.ts +1 -0
  11. package/dist/cjs/components/index.js +1 -0
  12. package/dist/cjs/components/index.js.map +1 -1
  13. package/dist/cjs/hooks/useStorage.d.ts +2 -0
  14. package/dist/cjs/hooks/useStorage.js +80 -0
  15. package/dist/cjs/hooks/useStorage.js.map +1 -0
  16. package/dist/cjs/hooks/withProps.d.ts +5 -0
  17. package/dist/cjs/hooks/withProps.js +7 -1
  18. package/dist/cjs/hooks/withProps.js.map +1 -1
  19. package/dist/cjs/uno/colors.js +1 -1
  20. package/dist/css/main.css +34 -34
  21. package/dist/esm/colors.stories.d.ts +20 -1
  22. package/dist/esm/colors.stories.js +30 -5
  23. package/dist/esm/colors.stories.js.map +1 -1
  24. package/dist/esm/components/emojiPicker/EmojiPicker.d.ts +28 -0
  25. package/dist/esm/components/emojiPicker/EmojiPicker.js +68 -0
  26. package/dist/esm/components/emojiPicker/EmojiPicker.js.map +1 -0
  27. package/dist/esm/components/emojiPicker/EmojiPicker.stories.d.ts +25 -0
  28. package/dist/esm/components/emojiPicker/EmojiPicker.stories.js +18 -0
  29. package/dist/esm/components/emojiPicker/EmojiPicker.stories.js.map +1 -0
  30. package/dist/esm/components/index.d.ts +1 -0
  31. package/dist/esm/components/index.js +1 -0
  32. package/dist/esm/components/index.js.map +1 -1
  33. package/dist/esm/hooks/useStorage.d.ts +2 -0
  34. package/dist/esm/hooks/useStorage.js +77 -0
  35. package/dist/esm/hooks/useStorage.js.map +1 -0
  36. package/dist/esm/hooks/withProps.d.ts +5 -0
  37. package/dist/esm/hooks/withProps.js +5 -0
  38. package/dist/esm/hooks/withProps.js.map +1 -1
  39. package/dist/esm/uno/colors.js +1 -1
  40. package/package.json +4 -2
  41. package/src/colors.stories.tsx +30 -4
  42. package/src/components/emojiPicker/EmojiPicker.stories.tsx +21 -0
  43. package/src/components/emojiPicker/EmojiPicker.tsx +170 -0
  44. package/src/components/index.ts +1 -0
  45. package/src/hooks/useStorage.ts +107 -0
  46. package/src/hooks/withProps.tsx +12 -0
  47. package/src/uno/colors.ts +1 -1
@@ -0,0 +1,170 @@
1
+ import clsx from 'clsx';
2
+ import {
3
+ EmojiPicker as Core,
4
+ EmojiPickerListCategoryHeaderProps,
5
+ EmojiPickerListEmojiProps,
6
+ EmojiPickerListProps,
7
+ EmojiPickerRootProps,
8
+ EmojiPickerViewportProps,
9
+ useSkinTone,
10
+ } from 'frimousse';
11
+ import { withClassName, withProps } from '../../hooks.js';
12
+ import { useLocalStorage } from '../../hooks/useStorage.js';
13
+ import { Box, BoxProps } from '../box/Box.js';
14
+ import { Button } from '../button/Button.js';
15
+ import { inputClassName } from '../input/Input.js';
16
+ import { Spinner } from '../spinner/Spinner.js';
17
+
18
+ export const EmojiPickerRoot = withClassName(
19
+ Core.Root,
20
+ 'layer-components:(isolate flex flex-col w-fit h-368px bg-white gap-sm)',
21
+ );
22
+ export const EmojiPickerSearch = withClassName(
23
+ Core.Search,
24
+ 'layer-components:(z-10)',
25
+ inputClassName,
26
+ );
27
+ export const EmojiPickerViewport = ({
28
+ className,
29
+ ...props
30
+ }: EmojiPickerViewportProps) => (
31
+ <Box border className="flex-1 min-h-0 overflow-hidden">
32
+ <Core.Viewport
33
+ className="layer-components:(relative outline-hidden)"
34
+ {...props}
35
+ />
36
+ </Box>
37
+ );
38
+ export const EmojiPickerLoading = withClassName(
39
+ withProps(Core.Loading, {
40
+ children: <Spinner />,
41
+ }),
42
+ 'layer-compoennts:(absolute inset-0 flex items-center justify-center bg-inherit)',
43
+ );
44
+ export const EmojiPickerEmpty = withClassName(
45
+ withProps(Core.Empty, {
46
+ children: <>No emoji found</>,
47
+ }),
48
+ 'layer-components:(absolute inset-0 flex items-center justify-center bg-inherit color-gray-dark text-xs)',
49
+ );
50
+
51
+ export const EmojiPickerCategoryHeader = (
52
+ props: EmojiPickerListCategoryHeaderProps,
53
+ ) => (
54
+ <div
55
+ className={clsx(
56
+ 'layer-components:(bg-inherit px-md py-sm text-xs font-semibold text-gray-dark sticky top-0)',
57
+ props.className,
58
+ )}
59
+ >
60
+ {props.category.label}
61
+ </div>
62
+ );
63
+ export const EmojiPickerRow = withClassName(
64
+ 'div',
65
+ 'layer-components:(scroll-my-xs px-xs)',
66
+ );
67
+ export const EmojiPickerEmoji = withClassName(
68
+ (p: EmojiPickerListEmojiProps) => (
69
+ <Button
70
+ {...p}
71
+ color="ghost"
72
+ toggled={p.emoji.isActive}
73
+ toggleMode="color"
74
+ size="icon-small"
75
+ aria-label={p.emoji.label}
76
+ className="text-lg p-xs"
77
+ >
78
+ {p.emoji.emoji}
79
+ </Button>
80
+ ),
81
+ '',
82
+ );
83
+
84
+ const defaultListComponents = {
85
+ CategoryHeader: EmojiPickerCategoryHeader,
86
+ Row: EmojiPickerRow,
87
+ Emoji: EmojiPickerEmoji,
88
+ };
89
+
90
+ export const EmojiPickerList = ({
91
+ className,
92
+ components,
93
+ }: EmojiPickerListProps) => {
94
+ return (
95
+ <Core.List
96
+ className={clsx('layer-components:(select-none pb-md)', className)}
97
+ components={
98
+ components
99
+ ? {
100
+ ...defaultListComponents,
101
+ ...components,
102
+ }
103
+ : defaultListComponents
104
+ }
105
+ />
106
+ );
107
+ };
108
+
109
+ export const useEmojiSkinTone = () =>
110
+ useLocalStorage<SkinTone | undefined>('emoji-skin-tone', undefined, false);
111
+
112
+ export type SkinTone = ReturnType<typeof useSkinTone>[0];
113
+ export const EmojiPickerSkinToneSelector = (props: BoxProps) => {
114
+ const [_, __, options] = useSkinTone();
115
+ const [skinTone, setSkinTone] = useEmojiSkinTone();
116
+
117
+ return (
118
+ <Box d="row" gap border {...props}>
119
+ {options.map((option) => (
120
+ <Button
121
+ key={option.skinTone}
122
+ color="ghost"
123
+ toggled={option.skinTone === skinTone}
124
+ toggleMode="color"
125
+ size="icon-small"
126
+ aria-label={`Skin tone ${option}`}
127
+ className="text-md p-xs"
128
+ onClick={() => setSkinTone(option.skinTone)}
129
+ >
130
+ {option.emoji}
131
+ </Button>
132
+ ))}
133
+ </Box>
134
+ );
135
+ };
136
+
137
+ export interface EmojiPickerProps
138
+ extends Omit<EmojiPickerRootProps, 'emoji' | 'onEmojiSelect'> {
139
+ onValueChange: (value: string, label: string) => void;
140
+ }
141
+ const EmojiPickerPrefab = ({ onValueChange, ...props }: EmojiPickerProps) => {
142
+ const [skinTone] = useEmojiSkinTone();
143
+ return (
144
+ <EmojiPickerRoot
145
+ {...props}
146
+ onEmojiSelect={(emoji) => onValueChange(emoji.emoji, emoji.label)}
147
+ skinTone={skinTone}
148
+ >
149
+ <EmojiPickerSearch />
150
+ <EmojiPickerViewport>
151
+ <EmojiPickerList />
152
+ <EmojiPickerLoading />
153
+ <EmojiPickerEmpty />
154
+ </EmojiPickerViewport>
155
+ <EmojiPickerSkinToneSelector className="mr-auto" />
156
+ </EmojiPickerRoot>
157
+ );
158
+ };
159
+
160
+ export const EmojiPicker = Object.assign(EmojiPickerPrefab, {
161
+ Root: EmojiPickerRoot,
162
+ Search: EmojiPickerSearch,
163
+ Viewport: EmojiPickerViewport,
164
+ List: EmojiPickerList,
165
+ Loading: EmojiPickerLoading,
166
+ Empty: EmojiPickerEmpty,
167
+ CategoryHeader: EmojiPickerCategoryHeader,
168
+ Row: EmojiPickerRow,
169
+ Emoji: EmojiPickerEmoji,
170
+ });
@@ -15,6 +15,7 @@ export * from './dialog/index.js';
15
15
  export * from './divider/index.js';
16
16
  export * from './dropdownMenu/index.js';
17
17
  export * from './editableText/EditableText.js';
18
+ export * from './emojiPicker/EmojiPicker.js';
18
19
  export * from './errorBoundary/index.js';
19
20
  export * from './forms/index.js';
20
21
  export * from './horizontalList/HorizontalList.js';
@@ -0,0 +1,107 @@
1
+ import { useEffect, useMemo } from 'react';
2
+ import { proxy, useSnapshot } from 'valtio';
3
+
4
+ function makeUseStorage(
5
+ storage: Storage,
6
+ cache: Record<string, any>,
7
+ name: string = storage.constructor.name,
8
+ ) {
9
+ return function useStorage<T>(
10
+ key: string,
11
+ initialValue: T,
12
+ writeInitialValue = false,
13
+ ) {
14
+ // using useMemo to execute synchronous code in render just once.
15
+ // this hook comes before useLocalStorageCache because we want to load
16
+ // values into the cache before accessing them.
17
+ useMemo(() => {
18
+ if (typeof window === 'undefined') return;
19
+
20
+ try {
21
+ const stored = storage.getItem(key);
22
+ if (stored) {
23
+ cache[key] = JSON.parse(stored);
24
+ }
25
+ } catch (err) {
26
+ console.error(`Error loading use-${name} value for ${key}: ${err}`);
27
+ storage.removeItem(key);
28
+ }
29
+ }, [key]);
30
+ const snapshot = useSnapshot(cache);
31
+ const storedValue = (snapshot[key] ?? initialValue) as T;
32
+
33
+ const hasValue = snapshot[key] !== undefined;
34
+ useEffect(() => {
35
+ if (!hasValue && writeInitialValue) {
36
+ storage.setItem(key, JSON.stringify(initialValue));
37
+ }
38
+ }, [hasValue, initialValue, writeInitialValue, key]);
39
+
40
+ // Return a wrapped version of useState's setter function that
41
+ // persists the new value to localStorage. It's throttled to prevent
42
+ // frequent writes to localStorage, which can be costly.
43
+ const setValue = useMemo(
44
+ () =>
45
+ throttle(
46
+ (value: T | ((current: T) => T)) => {
47
+ if (typeof window === 'undefined') return;
48
+
49
+ try {
50
+ // Allow value to be a function so we have same API as useState
51
+ const valueToStore =
52
+ value instanceof Function ? value(storedValue) : value;
53
+ // Save to local storage
54
+ storage.setItem(key, JSON.stringify(valueToStore));
55
+ // sync it to other instances of the hook via the global cache
56
+ cache[key] = valueToStore;
57
+ } catch (error) {
58
+ console.error(
59
+ `Error setting use-${name} value for ${key}: ${value}: ${error}`,
60
+ );
61
+ throw new Error('Error setting value');
62
+ }
63
+ },
64
+ 300,
65
+ { trailing: true, leading: true },
66
+ ),
67
+ [key, storedValue],
68
+ ) as (value: T | ((current: T) => T)) => void;
69
+
70
+ return [storedValue, setValue] as const;
71
+ };
72
+ }
73
+
74
+ export const useLocalStorage = makeUseStorage(
75
+ localStorage,
76
+ proxy({}),
77
+ 'LocalStorage',
78
+ );
79
+ export const useSessionStorage = makeUseStorage(
80
+ sessionStorage,
81
+ proxy({}),
82
+ 'SessionStorage',
83
+ );
84
+
85
+ function throttle(
86
+ func: (...args: any[]) => any,
87
+ wait: number,
88
+ options?: { trailing?: boolean; leading?: boolean },
89
+ ): (...args: any[]) => any {
90
+ let previous = 0;
91
+ return function (this: any, ...args: any[]) {
92
+ const now = Date.now();
93
+ if (!previous && options?.leading === false) previous = now;
94
+ const remaining = wait - (now - previous);
95
+ if (remaining <= 0) {
96
+ if (options?.trailing === false) previous = now;
97
+ return func(...args);
98
+ }
99
+ if (options?.trailing === false) {
100
+ return func(...args);
101
+ }
102
+ return setTimeout(() => {
103
+ previous = options?.leading === false ? 0 : Date.now();
104
+ func(...args);
105
+ }, remaining);
106
+ };
107
+ }
@@ -8,3 +8,15 @@ export const withProps = <T extends {}>(
8
8
  return <Component {...props} {...extras} />;
9
9
  };
10
10
  };
11
+
12
+ type OptionalKeys<T> = {
13
+ [K in keyof T]-?: undefined extends T[K] ? K : never;
14
+ }[keyof T];
15
+ export const withoutProps = <T extends {}, P extends OptionalKeys<T>>(
16
+ Component: React.ComponentType<T>,
17
+ remove: P[],
18
+ ) => {
19
+ return (props: Omit<T, P>) => {
20
+ return <Component {...(props as any)} />;
21
+ };
22
+ };
package/src/uno/colors.ts CHANGED
@@ -7,7 +7,7 @@ export const colorConstants = `
7
7
 
8
8
  export const dynamicThemeComputedColors = (name: string) => `
9
9
  --color-${name}: oklch(calc(90% - 35% * var(--dyn-source-mode-adjust, 0) - (var(--dyn-mode-sign, 1) * var(--dyn-${name}-base-dim, 0%))) calc(var(--dyn-${name}-sat-mult,1) * (35% - 2% * var(--dyn-source-mode-adjust, 0))) var(--dyn-${name}-source, 0));
10
- --color-${name}-wash: oklch(from var(--color-${name}) calc(min(0.999,max(0.15, l + 0.15 * var(--dyn-mode-mult, 1)))) calc(var(--dyn-${name}-sat-mult) * (c * var(--dyn-saturation-x-wash, 1) - 0.03)) calc(h - 5 * var(--dyn-${name}-hue-rotate, 0) * var(--dyn-${name}-hue-rotate-mult, 1)));
10
+ --color-${name}-wash: oklch(from var(--color-${name}) calc(min(0.999,max(0.15, l + 0.15 * var(--dyn-mode-mult, 1)))) calc(var(--dyn-${name}-sat-mult) * (c * var(--dyn-saturation-x-wash, 1) - 0.06)) calc(h - 5 * var(--dyn-${name}-hue-rotate, 0) * var(--dyn-${name}-hue-rotate-mult, 1)));
11
11
  --color-${name}-light: oklch(from var(--color-${name}) calc(l + 0.08 * var(--dyn-mode-mult, 1)) calc(var(--dyn-${name}-sat-mult) * (c * var(--dyn-saturation-x-light, 1) - 0.03)) calc(h - 0.5 * var(--dyn-${name}-hue-rotate, 0) * var(--dyn-${name}-hue-rotate-mult, 1)));
12
12
  --color-${name}-dark: oklch(from var(--color-${name}) calc(l - 0.26 * var(--dyn-mode-mult, 1)) calc(var(--dyn-${name}-sat-mult) * (c * var(--dyn-saturation-x-dark, 1) + 0.01)) calc(h + 0.2 * var(--dyn-${name}-hue-rotate, 0) * var(--dyn-${name}-hue-rotate-mult, 1)));
13
13
  --color-${name}-ink: oklch(from var(--color-${name}) calc(l - 0.45 * var(--dyn-mode-mult, 1)) calc(var(--dyn-${name}-sat-mult) * (c * var(--dyn-saturation-x-ink, 1) + 0.01)) calc(h + 1 * var(--dyn-${name}-hue-rotate, 0) * var(--dyn-${name}-hue-rotate-mult, 1)));