Skip to content

Commit 16a3900

Browse files
committed
feat: Added use-counter.
1 parent d3b0cae commit 16a3900

File tree

8 files changed

+131
-9
lines changed

8 files changed

+131
-9
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
5252
- [x] use-click-outside
5353
- [ ] use-clipboard
5454
- [ ] use-color-scheme
55-
- [ ] use-counter
55+
- [x] use-counter
5656
- [ ] use-debounced-callback
5757
- [ ] use-debounced-state
5858
- [ ] use-debounced-value
@@ -80,7 +80,7 @@ Based on the [@mantine/hooks](https://github.com/mantinedev/mantine/tree/master/
8080
- [ ] use-intersection
8181
- [ ] use-interval
8282
- [ ] use-is-first-render
83-
- [ ] use-isomorphic-effect
83+
- [x] ~~use-isomorphic-effect~~ (Solid's [`createEffect`](https://docs.solidjs.com/reference/basic-reactivity/create-effect) is actually isomorphic - it works in browser and server).
8484
- [ ] use-list-state
8585
- [x] use-local-storage
8686
- [ ] use-logger
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
```tsx
2+
const [count, { decrement, increment, reset, set }] = useCounter(5, { min: 1, max: 10 });
3+
4+
return (
5+
<div>
6+
{count()}
7+
<button onClick={decrement}>-</button>
8+
<button onClick={increment}>+</button>
9+
10+
<button onClick={reset}>
11+
Reset
12+
</button>
13+
14+
<button
15+
onClick={() => set(Math.floor(Math.random() * 10))}
16+
>
17+
Set (To Random)
18+
</button>
19+
</div>;
20+
)
21+
```
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useCounter } from 'src';
2+
import { ExampleBase } from '../example-base';
3+
import Code from './use-counter.code.mdx';
4+
5+
import { useMDXComponents } from 'solid-jsx';
6+
7+
export function UseCounterExample() {
8+
const [count, { decrement, increment, reset, set }] = useCounter(5, { min: 1, max: 10 });
9+
10+
// @ts-ignore
11+
const components: any = useMDXComponents();
12+
13+
return (
14+
<ExampleBase
15+
title="useCounter"
16+
description="A hook that returns a signal with a counter value and functions to increment, decrement, set and reset the counter."
17+
code={<Code components={components} />}
18+
>
19+
<div class="flex h-full w-full flex-col items-center justify-center gap-5 gap-x-1 rounded-md border p-3 py-10 text-center">
20+
<span>{count()}</span>
21+
22+
<div class="flex gap-x-2 text-sm">
23+
<button
24+
class="rounded-md border p-1 px-1.5 transition active:scale-90"
25+
onClick={decrement}
26+
>
27+
-
28+
</button>
29+
<button
30+
class="rounded-md border p-1 px-1.5 transition active:scale-90"
31+
onClick={increment}
32+
>
33+
+
34+
</button>
35+
<button class="rounded-md border p-1 px-1.5 transition active:scale-90" onClick={reset}>
36+
Reset
37+
</button>
38+
<button
39+
class="rounded-md border p-1 px-1.5 transition active:scale-90"
40+
onClick={() => set(Math.floor(Math.random() * 10))}
41+
>
42+
Set (To Random)
43+
</button>
44+
</div>
45+
</div>
46+
</ExampleBase>
47+
);
48+
}

dev/components/examples/use-local-storage/use-local-storage.example.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ export function UseLocalStorageExample() {
1919
title="useLocalStorage"
2020
description={
2121
<>
22-
A hook that allows using value from the localStorage as a signal The hook works the same
23-
way as createSignal, but also writes the value to the localStorage.
22+
A hook that allows using value from the localStorage as a signal. The hook works exactly
23+
the same way as createSignal, but also writes the value to the localStorage. It even works
24+
between tabs!
2425
<br />
2526
<br />
2627
To test, try changing the value with two tabs open.

dev/components/markdown/markdown.context.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@ export function MarkdownContextProvider(props: FlowProps) {
1717
return (
1818
<MDXProvider
1919
components={{
20-
code(props: any): JSX.Element {
20+
code(codeProps: any): JSX.Element {
2121
const [ref, setRef] = createSignal<HTMLPreElement | undefined>();
2222
const [copied, setCopied] = createSignal(false);
2323

24+
const originalContent = codeProps.children;
25+
2426
createEffect(async () => {
2527
const current = ref();
26-
const content = props.children;
28+
const content = originalContent;
2729

2830
if (current && content) {
29-
const language = props.className.slice(props.className.indexOf('language-') + 9); // 9 chars of 'langauge-'. Stripped
31+
const language = codeProps.className.slice(
32+
codeProps.className.indexOf('language-') + 9,
33+
); // 9 chars of 'langauge-'. Stripped
3034
console.log();
3135
const result = await codeToHtml(content, {
3236
lang: 'tsx',
@@ -41,7 +45,7 @@ export function MarkdownContextProvider(props: FlowProps) {
4145
<button
4246
class="absolute right-0 top-0 p-2 text-neutral-50 opacity-50 transition will-change-transform active:scale-95"
4347
onClick={() => {
44-
navigator.clipboard.writeText(props.children);
48+
navigator.clipboard.writeText(originalContent);
4549
setCopied(true);
4650
setTimeout(() => {
4751
setCopied(false);
@@ -56,7 +60,7 @@ export function MarkdownContextProvider(props: FlowProps) {
5660
</button>
5761
<div ref={setRef} class="text-sm">
5862
{/* This will be replaced with the code element, but render it at first paint with opacity 0 so it takes up space. */}
59-
<div class="opacity-0">{props.children}</div>
63+
<div class="opacity-0">{originalContent}</div>
6064
</div>
6165
</div>
6266
);

dev/pages/index/+Page.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import packageJSON from 'src/../package.json';
44

55
// Hooks
66
import { UseClickOutsideExample } from 'dev/components/examples/use-click-outside/use-click-outside.example';
7+
import { UseCounterExample } from 'dev/components/examples/use-counter/use-counter.example';
78
import { UseElementSizeExample } from 'dev/components/examples/use-element-size/use-element-size.example';
89
import { UseEyeDropperExample } from 'dev/components/examples/use-eye-dropper/use-eye-dropper.example';
910
import { UseFaviconExample } from 'dev/components/examples/use-favicon/use-favicon.example';
@@ -43,6 +44,10 @@ export default function HomePage() {
4344
const [searchInput, setSearchInput] = createSignal('');
4445

4546
const LIST = [
47+
{
48+
title: 'useCounter',
49+
example: <UseCounterExample />,
50+
},
4651
{
4752
title: 'useOS',
4853
example: <UseOsExample />,

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-counter/use-counter';
23
export * from './use-eye-dropper/use-eye-dropper';
34
export * from './use-favicon/use-favicon';
45
export * from './use-hotkeys/use-hotkeys';

src/use-counter/use-counter.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// ./clamp.ts
2+
3+
function clamp(value: number, min: number | undefined, max: number | undefined) {
4+
if (min === undefined && max === undefined) {
5+
return value;
6+
}
7+
8+
if (min !== undefined && max === undefined) {
9+
return Math.max(value, min);
10+
}
11+
12+
if (min === undefined && max !== undefined) {
13+
return Math.min(value, max);
14+
}
15+
16+
return Math.min(Math.max(value, min!), max!);
17+
}
18+
19+
// ./use-counter.ts
20+
import { createSignal } from 'solid-js';
21+
22+
const DEFAULT_OPTIONS = {
23+
min: -Infinity,
24+
max: Infinity,
25+
};
26+
27+
/**
28+
* A hook that returns a signal with a counter value and functions to increment, decrement, set and reset the counter.
29+
*/
30+
export function useCounter(
31+
initialValue = 0,
32+
options: Partial<{ min: number; max: number }> = DEFAULT_OPTIONS,
33+
) {
34+
const [count, setCount] = createSignal<number>(clamp(initialValue, options?.min, options?.max));
35+
36+
const increment = () => setCount(current => clamp(current + 1, options?.min, options?.max));
37+
const decrement = () => setCount(current => clamp(current - 1, options?.min, options?.max));
38+
const set = (value: number) => setCount(clamp(value, options?.min, options?.max));
39+
const reset = () => setCount(clamp(initialValue, options?.min, options?.max));
40+
41+
return [count, { increment, decrement, set, reset }] as const;
42+
}

0 commit comments

Comments
 (0)