Skip to content

Commit 4b095ac

Browse files
committed
feat: Added eye-dropper hook (improved).
1 parent a52b108 commit 4b095ac

File tree

7 files changed

+147
-2
lines changed

7 files changed

+147
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
6161
- [ ] use-document-title
6262
- [ ] use-document-visibility
6363
- [ ] use-event-listener
64-
- [ ] use-eye-dropper
64+
- [x] use-eye-dropper (improved, state management is inside the hook)
6565
- [x] use-favicon (improved, more flexible)
6666
- [ ] use-fetch
6767
- [ ] use-focus-return
@@ -74,7 +74,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
7474
- [x] use-hotkeys
7575
- [x] use-hover
7676
- [x] ~~use-id~~ (Solid has [`createUniqueId`](https://docs.solidjs.com/reference/component-apis/create-unique-id))
77-
- [x] use-idle (There is [`createIdleTimer`](https://primitives.solidjs.community/package/idle/) solid-primitives as well)
77+
- [x] use-idle (Added, but note that there is [`createIdleTimer`](https://primitives.solidjs.community/package/idle/) solid-primitives as well)
7878
- [ ] use-in-viewport
7979
- [ ] use-input-state
8080
- [ ] use-intersection
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
```tsx
2+
const { color, supported, pickColor } = useEyeDropper();
3+
4+
return (
5+
<div>
6+
<button onClick={pickColor} disabled={!supported()}>
7+
Pick Color
8+
</button>
9+
10+
<div>
11+
Current Color: <span style={{ height: 32, width: 32, 'background-color': color() }} />
12+
</div>
13+
14+
<Show when={supported() !== undefined && !supported()}>
15+
<span>Your browser does not support EyeDropper.</span>
16+
</Show>
17+
</div>
18+
);
19+
```
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useEyeDropper } from 'src';
2+
import { ExampleBase } from '../example-base';
3+
import Code from './use-eye-dropper.code.mdx';
4+
5+
import { IconEyeDropper } from 'dev/icons';
6+
import { Show } from 'solid-js';
7+
import { useMDXComponents } from 'solid-jsx';
8+
9+
export function UseEyeDropperExample() {
10+
const { color, supported, pickColor } = useEyeDropper();
11+
12+
// @ts-ignore
13+
const components: any = useMDXComponents();
14+
15+
return (
16+
<ExampleBase
17+
title="useEyeDropper"
18+
description="A hook that gives you the ability to open the browser's EyeDropper API and save it to a signal."
19+
code={<Code components={components} />}
20+
>
21+
<div class="flex h-full w-full flex-col items-center justify-center gap-5 rounded-md border p-3 py-10 text-center">
22+
<div class="flex items-center gap-x-2">
23+
<button class="transition active:scale-95" onClick={pickColor} disabled={!supported()}>
24+
<IconEyeDropper />
25+
</button>
26+
27+
<div class="flex items-center gap-x-2 text-sm">
28+
Picked Color: {color()}
29+
<div class="h-8 w-8 rounded-full border" style={{ 'background-color': color() }} />
30+
</div>
31+
</div>
32+
33+
<Show when={supported() !== undefined && !supported()}>
34+
<span class="text-xs text-red-500">Your browser does not support EyeDropper.</span>
35+
</Show>
36+
</div>
37+
</ExampleBase>
38+
);
39+
}

dev/icons/eye-dropper.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { JSX, VoidProps } from 'solid-js';
2+
3+
export default function EyeDropper(props: VoidProps<JSX.SvgSVGAttributes<SVGSVGElement>>) {
4+
return (
5+
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 256 256" {...props}>
6+
<g fill="currentColor">
7+
<path
8+
d="m207.8 87.6l-25.37 25.53l4.89 4.88a16 16 0 0 1 0 22.64l-9 9a8 8 0 0 1-11.32 0l-60.68-60.7a8 8 0 0 1 0-11.32l9-9a16 16 0 0 1 22.63 0l4.88 4.89l25-25.11c10.79-10.79 28.37-11.45 39.45-1a28 28 0 0 1 .52 40.19"
9+
opacity=".2"
10+
/>
11+
<path d="M224 67.3a35.8 35.8 0 0 0-11.26-25.66c-14-13.28-36.72-12.78-50.62 1.13L142.8 62.2a24 24 0 0 0-33.14.77l-9 9a16 16 0 0 0 0 22.64l2 2.06l-51 51a39.75 39.75 0 0 0-10.53 38l-8 18.41A13.68 13.68 0 0 0 36 219.3a15.92 15.92 0 0 0 17.71 3.35L71.23 215a39.89 39.89 0 0 0 37.06-10.75l51-51l2.06 2.06a16 16 0 0 0 22.62 0l9-9a24 24 0 0 0 .74-33.18l19.75-19.87A35.75 35.75 0 0 0 224 67.3M97 193a24 24 0 0 1-24 6a8 8 0 0 0-5.55.31l-18.1 7.91L57 189.41a8 8 0 0 0 .25-5.75A23.88 23.88 0 0 1 63 159l51-51l33.94 34ZM202.13 82l-25.37 25.52a8 8 0 0 0 0 11.3l4.89 4.89a8 8 0 0 1 0 11.32l-9 9L112 83.26l9-9a8 8 0 0 1 11.31 0l4.89 4.89a8 8 0 0 0 5.65 2.34a8 8 0 0 0 5.66-2.36l24.94-25.09c7.81-7.82 20.5-8.18 28.29-.81a20 20 0 0 1 .39 28.7Z" />
12+
</g>
13+
</svg>
14+
);
15+
}

dev/pages/index/+Page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import packageJSON from 'src/../package.json';
55
// Hooks
66
import { UseClickOutsideExample } from 'dev/components/examples/use-click-outside/use-click-outside.example';
77
import { UseElementSizeExample } from 'dev/components/examples/use-element-size/use-element-size.example';
8+
import { UseEyeDropperExample } from 'dev/components/examples/use-eye-dropper/use-eye-dropper.example';
89
import { UseFaviconExample } from 'dev/components/examples/use-favicon/use-favicon.example';
910
import { UseHotkeysExample } from 'dev/components/examples/use-hotkeys/use-hotkeys.example';
1011
import { UseHoverExample } from 'dev/components/examples/use-hover/use-hover.example';
@@ -90,6 +91,10 @@ export default function HomePage() {
9091
title: 'useLocalStorage',
9192
example: <UseLocalStorageExample />,
9293
},
94+
{
95+
title: 'useEyeDropper',
96+
example: <UseEyeDropperExample />,
97+
},
9398
];
9499

95100
const filteredList = createMemo(() => {

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from './use-click-outside/use-click-outside';
2+
export * from './use-eye-dropper/use-eye-dropper';
23
export * from './use-favicon/use-favicon';
34
export * from './use-hotkeys/use-hotkeys';
45
export * from './use-hover/use-hover';
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { createSignal, onMount } from 'solid-js';
2+
3+
interface EyeDropperOpenOptions {
4+
signal?: AbortSignal;
5+
}
6+
7+
export interface EyeDropperOpenReturnType {
8+
sRGBHex: string;
9+
}
10+
11+
function isOpera() {
12+
return navigator.userAgent.includes('OPR');
13+
}
14+
15+
/**
16+
* A hook that opens the browser's EyeDropper API.
17+
*
18+
* This has an improvement over Mantine's API. Mantine's is more low-level. Bagon
19+
* prepares states for you by default.
20+
*/
21+
export function useEyeDropper() {
22+
const [color, setColor] = createSignal<string>();
23+
const [error, setError] = createSignal<Error | null>(null);
24+
const [supported, setSupported] = createSignal<boolean>();
25+
26+
onMount(() => {
27+
setSupported(typeof window !== 'undefined' && !isOpera() && 'EyeDropper' in window);
28+
});
29+
30+
const open = (
31+
options: EyeDropperOpenOptions = {},
32+
): Promise<EyeDropperOpenReturnType | undefined> => {
33+
if (supported()) {
34+
const eyeDropper = new (window as any).EyeDropper();
35+
return eyeDropper.open(options);
36+
}
37+
38+
return Promise.resolve(undefined);
39+
};
40+
41+
const pickColor = async () => {
42+
try {
43+
const { sRGBHex } = (await open())!;
44+
setColor(sRGBHex);
45+
} catch (e) {
46+
setError(e as Error);
47+
}
48+
};
49+
50+
return {
51+
/**
52+
* Improved in bagon.
53+
* - true - supported.
54+
* - false - not supported.
55+
* - undefined - support is not checked yet (initial state so you can check this if you don't want a flicker before support is determined).
56+
*/
57+
supported,
58+
open,
59+
/** Added in bagon. */
60+
error,
61+
/** Added in bagon. */
62+
color,
63+
/** Added in bagon. */
64+
pickColor,
65+
};
66+
}

0 commit comments

Comments
 (0)