Skip to content

Commit 153b457

Browse files
committed
chore: add toStore and classGet$ utils
1 parent e317ee6 commit 153b457

File tree

11 files changed

+164
-31
lines changed

11 files changed

+164
-31
lines changed

.clean-publish

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"withoutPublish": true,
33
"tempDir": "package",
4-
"fields": ["scripts"]
4+
"fields": ["scripts", "publishConfig"]
55
}

README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,16 @@ button({
254254
`classList$` is an effect attribute that manages class names of the element.
255255

256256
```js
257-
import { button, classList$, classIf } from 'nanoviews'
257+
import { button, classList$, classIf$, classGet$ } from 'nanoviews'
258+
import * as styles from './styles.css'
258259

259260
function MyButton({
260261
class: className,
261-
primary
262+
theme = 'primary',
263+
rounded = false
262264
}) {
263265
return button({
264-
[classList$]: [className, 'myButton', classIf('primary', primary)]
266+
[classList$]: [className, 'myButton', classIf$(styles.rounded, rounded), classGet$(styles, theme)]
265267
})
266268
}
267269
```

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
"@trigen/eslint-config": "8.0.0-alpha.32",
3737
"@trigen/scripts": "8.0.0-alpha.30",
3838
"@types/node": "^20.0.0",
39-
"clean-publish": "^4.0.1",
39+
"clean-publish": "^4.4.0",
4040
"commitizen": "^4.2.4",
4141
"del-cli": "^5.0.0",
4242
"eslint": "^8.28.0",

packages/nanoviews/.size-limit.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "All publics",
44
"path": "dist/index.production.js",
55
"import": "*",
6-
"limit": "4.77 kB"
6+
"limit": "4.81 kB"
77
},
88
{
99
"name": "All internals",
@@ -14,7 +14,7 @@
1414
{
1515
"name": "Average usage",
1616
"path": "dist/index.production.js",
17-
"import": "{ div, input, button, label, fragment, decide$, for$, classList$, classIf, value$, children$, effect$ }",
18-
"limit": "2.78 kB"
17+
"import": "{ div, input, button, label, fragment, decide$, for$, classList$, classIf$, classGet$, value$, children$, effect$ }",
18+
"limit": "2.81 kB"
1919
}
2020
]

packages/nanoviews/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -254,14 +254,16 @@ button({
254254
`classList$` is an effect attribute that manages class names of the element.
255255

256256
```js
257-
import { button, classList$, classIf } from 'nanoviews'
257+
import { button, classList$, classIf$, classGet$ } from 'nanoviews'
258+
import * as styles from './styles.css'
258259

259260
function MyButton({
260261
class: className,
261-
primary
262+
theme = 'primary',
263+
rounded = false
262264
}) {
263265
return button({
264-
[classList$]: [className, 'myButton', classIf('primary', primary)]
266+
[classList$]: [className, 'myButton', classIf$(styles.rounded, rounded), classGet$(styles, theme)]
265267
})
266268
}
267269
```

packages/nanoviews/src/elements/classList.spec.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ const {
1616
ReactiveStrings,
1717
NestedStrings,
1818
EmptyValues,
19-
ClassIf
19+
ClassIf,
20+
ClassGet
2021
} = composeStories(Stories)
2122

2223
describe('nanoviews', () => {
@@ -112,7 +113,7 @@ describe('nanoviews', () => {
112113
expect(container.innerHTML).toBe('<div><div class="class-c">Hello, world!</div></div>')
113114
})
114115

115-
it('should hanlde classIf utility', () => {
116+
it('should hanlde classIf$ utility', () => {
116117
const className = atom('class-a')
117118
const enabled = atom(true)
118119
const { container } = render(ClassIf({
@@ -126,6 +127,19 @@ describe('nanoviews', () => {
126127

127128
expect(container.innerHTML).toBe('<div><div class="">Hello, world!</div></div>')
128129
})
130+
131+
it('should handle classGet$ utility', () => {
132+
const theme = atom('primary')
133+
const { container } = render(ClassGet({
134+
theme
135+
}))
136+
137+
expect(container.innerHTML).toBe('<div><div class="theme-primary">Hello, world!</div></div>')
138+
139+
theme.set('secondary')
140+
141+
expect(container.innerHTML).toBe('<div><div class="theme-secondary">Hello, world!</div></div>')
142+
})
129143
})
130144
})
131145
})

packages/nanoviews/src/elements/classList.stories.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ import { nanoStory } from '@nanoviews/storybook'
33
import { div } from './elements.js'
44
import {
55
classList$,
6-
classIf
6+
classIf$,
7+
classGet$
78
} from './classList.js'
89

910
const meta: Meta<{
1011
className?: string
1112
classList?: string[]
1213
enabled?: boolean
14+
theme?: string
1315
}> = {
1416
title: 'Elements/Effect Attributes/Class List'
1517
}
@@ -89,6 +91,24 @@ export const ClassIf: Story = {
8991
enabled: true
9092
},
9193
render: nanoStory(({ className, enabled }) => div({
92-
[classList$]: classIf(className, enabled)
94+
[classList$]: classIf$(className, enabled)
95+
})('Hello, world!'))
96+
}
97+
98+
export const ClassGet: Story = {
99+
argTypes: {
100+
theme: {
101+
control: 'radio',
102+
options: ['primary', 'secondary']
103+
}
104+
},
105+
args: {
106+
theme: 'primary'
107+
},
108+
render: nanoStory(({ theme }) => div({
109+
[classList$]: classGet$({
110+
primary: 'theme-primary',
111+
secondary: 'theme-secondary'
112+
}, theme)
93113
})('Hello, world!'))
94114
}

packages/nanoviews/src/elements/classList.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ import {
1717

1818
export type ClassListPrimitive = string | boolean | EmptyValue
1919

20-
export type ClassIf = (set: (classList: ClassList) => StrictEffect<void>) => StrictEffect<void>
20+
export type ClassListEffect = (set: (classList: ClassList) => StrictEffect<void>) => StrictEffect<void>
2121

22-
export type ClassList = ValueOrStore<ClassListPrimitive | ClassIf | readonly ClassList[]>
22+
export type ClassList = ValueOrStore<ClassListPrimitive | ClassListEffect | readonly ClassList[]>
2323

24-
export type ClassListStore = Store<ClassListPrimitive | ClassIf | readonly ClassList[]>
24+
export type ClassListStore = Store<ClassListPrimitive | ClassListEffect | readonly ClassList[]>
2525

2626
function isClassName(cls: string) {
2727
return isString(cls) && cls.includes(' ')
@@ -100,7 +100,7 @@ function createClassListEffect(
100100
}
101101
}
102102

103-
export function classIf(
103+
export function classIf$(
104104
classList: ClassList,
105105
$condition: ValueOrStore<boolean | EmptyValue>
106106
): ClassList {
@@ -111,6 +111,17 @@ export function classIf(
111111
return $condition && classList
112112
}
113113

114+
export function classGet$(
115+
classMap: Record<string, string>,
116+
$key: ValueOrStore<string | EmptyValue>
117+
): ClassList {
118+
if (isStore($key)) {
119+
return set => createDynamicEffect($key, key => set(classMap[key!]))
120+
}
121+
122+
return classMap[$key!]
123+
}
124+
114125
/**
115126
* Effect attribute to control class list of element
116127
*/
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
describe,
3+
it,
4+
expect
5+
} from 'vitest'
6+
import { atom } from 'nanostores'
7+
import { toStore } from './utils.js'
8+
9+
describe('nanoviews', () => {
10+
describe('utils', () => {
11+
describe('toStore', () => {
12+
it('should create store from value', () => {
13+
const value = 'value'
14+
const store = toStore(value, atom)
15+
16+
expect(store.get()).toBe(value)
17+
})
18+
19+
it('should return store', () => {
20+
const store = atom('value')
21+
const result = toStore(store, atom)
22+
23+
expect(result).toBe(store)
24+
})
25+
})
26+
})
27+
})

packages/nanoviews/src/utils.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { Store } from './internals/index.js'
2+
import { isStore } from './internals/index.js'
3+
14
export * from './internals/utils.js'
25

36
/**
@@ -7,3 +10,17 @@ export * from './internals/utils.js'
710
export function throw$(error: Error) {
811
throw error
912
}
13+
14+
/**
15+
* Create store from value or return store
16+
* @param valueOrStore - Value or store
17+
* @param creator - Store creator
18+
* @returns Store
19+
*/
20+
export function toStore<T, S extends Store<T>>(valueOrStore: T | Store<T>, creator: (value: T) => S): S {
21+
if (isStore(valueOrStore)) {
22+
return valueOrStore as S
23+
}
24+
25+
return creator(valueOrStore)
26+
}

0 commit comments

Comments
 (0)