Skip to content
This repository was archived by the owner on May 11, 2024. It is now read-only.

Commit 49accaa

Browse files
committed
feat: support react
1 parent d56e613 commit 49accaa

File tree

12 files changed

+196
-27
lines changed

12 files changed

+196
-27
lines changed

Diff for: .storybook/main.js

+10
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,16 @@ module.exports = {
2727
},
2828
},
2929
],
30+
babel: async (options) => {
31+
options.presets.push([
32+
'@babel/preset-react',
33+
{
34+
runtime: 'automatic',
35+
},
36+
])
37+
38+
return options
39+
},
3040
webpackFinal: (config) => {
3141
const rules = config.module.rules
3242

Diff for: index.json

+1
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
"install": false
169169
},
170170
"modal": {
171+
"react": true,
171172
"icon": true,
172173
"version": "1.0.0",
173174
"style": true,

Diff for: lib/build.js

+10
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,16 @@ module.exports = wrap(async function (component) {
5555
if (!isEmpty(peerDependencies)) {
5656
pkg.peerDependencies = peerDependencies
5757
}
58+
pkg.exports = {
59+
'.': `esm/${component}/index.js`,
60+
'./css': `./luna-${component}.css`,
61+
[`./luna-${component}.css`]: `./luna-${component}.css`,
62+
[`./luna-${component}.js`]: `./luna-${component}.js`,
63+
'./package.json': './package.json',
64+
}
65+
if (config.react) {
66+
pkg.exports['./react'] = `./esm/${component}/react.js`
67+
}
5868
}
5969

6070
await fs.writeFile(

Diff for: lib/util.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ exports.readComponentConfig = function (component) {
7979
icon: false,
8080
test: true,
8181
install: false,
82+
react: false,
8283
dependencies: [],
8384
})
8485

@@ -97,6 +98,7 @@ const config = defaults(pkg.luna || {}, {
9798
icon: false,
9899
test: true,
99100
install: false,
101+
react: false,
100102
dependencies: []
101103
})`
102104

@@ -129,7 +131,7 @@ exports.createKarmaConf = async function (component) {
129131

130132
exports.createTsConfig = async function (component, esm) {
131133
let files = await fs.readdir(resolve(`../src/${component}`))
132-
files = filter(files, (file) => endWith(file, '.ts'))
134+
files = filter(files, (file) => endWith(file, '.ts') || endWith(file, '.tsx'))
133135

134136
const tsConfig = {
135137
extends: '../../tsconfig.json',

Diff for: package.json

+5
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@
3535
},
3636
"devDependencies": {
3737
"@babel/core": "^7.10.5",
38+
"@babel/preset-react": "^7.22.5",
3839
"@metahub/karma-postcss-preprocessor": "^4.0.1",
3940
"@storybook/addon-knobs": "^6.2.9",
4041
"@storybook/addon-storysource": "^6.4.14",
4142
"@storybook/addons": "^6.4.14",
4243
"@storybook/html": "^6.4.14",
4344
"@storybook/theming": "^6.4.14",
4445
"@types/node": "^17.0.21",
46+
"@types/react": "^17.0.2",
47+
"@types/react-dom": "^17.0.2",
4548
"@typescript-eslint/eslint-plugin": "^5.13.0",
4649
"@typescript-eslint/parser": "^5.13.0",
4750
"autoprefixer": "^9.7.4",
@@ -68,6 +71,8 @@
6871
"postcss-clean": "^1.1.0",
6972
"postcss-loader": "^3.0.0",
7073
"postcss-prefixer": "^2.1.2",
74+
"react": "^17.0.2",
75+
"react-dom": "^17.0.2",
7176
"sass": "^1.62.1",
7277
"sass-loader": "^10.2.0",
7378
"shelljs": "^0.8.3",

Diff for: src/modal/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export default class Modal extends Component<IOptions> {
6666
this.$container.rmClass(this.c('hidden'))
6767
}
6868
/** Hide the modal. */
69-
hide = () => {
69+
hide() {
7070
this.$container.addClass(this.c('hidden'))
7171
}
7272
destroy() {
@@ -189,7 +189,7 @@ export default class Modal extends Component<IOptions> {
189189
globalContainer = container
190190
}
191191
private bindEvent() {
192-
this.$body.on('click', this.c('.icon-close'), this.hide)
192+
this.$body.on('click', this.c('.icon-close'), () => this.hide())
193193
this.on('optionChange', this.render)
194194
}
195195
private render = () => {

Diff for: src/modal/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"version": "1.0.0",
44
"description": "Create modal dialogs",
55
"luna": {
6+
"react": true,
67
"icon": true
78
}
89
}

Diff for: src/modal/react.tsx

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { FC, PropsWithChildren, useEffect, useRef } from 'react'
2+
import { createPortal } from 'react-dom'
3+
import h from 'licia/h'
4+
import types from 'licia/types'
5+
import Modal from './index'
6+
7+
interface IModalProps {
8+
title: string
9+
visible: boolean
10+
width?: number
11+
onClose?: () => void
12+
}
13+
14+
const LunaModal: FC<PropsWithChildren<IModalProps>> = (props) => {
15+
const modalRef = useRef<HTMLDivElement>(null)
16+
const modal = useRef<Modal>()
17+
const content = useRef<HTMLDivElement>(h('div') as HTMLDivElement)
18+
const doHide = useRef<types.AnyFn>()
19+
20+
useEffect(() => {
21+
modal.current = new Modal(modalRef.current!, {
22+
title: props.title,
23+
content: content.current,
24+
})
25+
doHide.current = modal.current.hide
26+
modal.current.hide = function () {
27+
props.onClose && props.onClose()
28+
}
29+
if (props.visible) {
30+
modal.current.show()
31+
}
32+
if (props.width) {
33+
modal.current.setOption('width', props.width)
34+
}
35+
36+
return () => modal.current?.destroy()
37+
}, [])
38+
39+
useEffect(() => {
40+
if (modal.current) {
41+
modal.current.setOption('title', props.title)
42+
}
43+
}, [props.title])
44+
45+
useEffect(() => {
46+
if (modal.current) {
47+
if (props.visible) {
48+
modal.current.show()
49+
} else {
50+
doHide.current && doHide.current.call(modal.current)
51+
}
52+
}
53+
}, [props.visible])
54+
55+
useEffect(() => {
56+
if (modal.current) {
57+
modal.current.setOption('width', props.width)
58+
}
59+
}, [props.width])
60+
61+
return <div ref={modalRef}>
62+
{createPortal(<>{props.children}</>, content.current)}
63+
</div>
64+
}
65+
66+
export default LunaModal

Diff for: src/modal/story.js

+52-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,21 @@
11
import 'luna-modal.css'
22
import Modal from 'luna-modal.js'
3+
import LunaModal from './react'
34
import readme from './README.md'
45
import changelog from './CHANGELOG.md'
56
import story from '../share/story'
6-
import { text, button } from '@storybook/addon-knobs'
7+
import { text, button, number } from '@storybook/addon-knobs'
8+
import { useState } from 'react'
79

810
const def = story(
911
'modal',
1012
(container) => {
11-
const title = text('Modal Title', 'This is the Title')
12-
const content = text('Modal Content', 'This is the modal content.')
13+
const { title, content, width } = createKnobs()
1314

1415
const modal = new Modal(container, {
1516
title,
1617
content,
18+
width,
1719
})
1820
modal.show()
1921

@@ -22,13 +24,13 @@ const def = story(
2224
return false
2325
})
2426

25-
button('hide', () => {
27+
button('Hide', () => {
2628
modal.hide()
2729
return false
2830
})
2931

3032
const alertContent = text('Alert Content', 'This is the alert content.')
31-
button('alert', () => {
33+
button('Alert', () => {
3234
modal.hide()
3335
Modal.alert(alertContent)
3436

@@ -39,7 +41,7 @@ const def = story(
3941
'Confirm Content',
4042
'This is the confirm content.'
4143
)
42-
button('confirm', () => {
44+
button('Confirm', () => {
4345
modal.hide()
4446
Modal.confirm(confirmContent).then((result) => {
4547
console.log('Confirm result:', result)
@@ -49,7 +51,7 @@ const def = story(
4951

5052
const promptTitle = text('Prompt Title', 'This is the prompt title.')
5153
const promptDefault = text('Prompt Default', 'This is the default text.')
52-
button('prompt', () => {
54+
button('Prompt', () => {
5355
modal.hide()
5456
Modal.prompt(promptTitle, promptDefault).then((result) => {
5557
console.log('Prompt result:', result)
@@ -63,9 +65,51 @@ const def = story(
6365
readme,
6466
changelog,
6567
source: __STORY__,
68+
ReactComponent() {
69+
const { title, content, width } = createKnobs()
70+
const [visible, setVisible] = useState(true)
71+
72+
button('Show', () => {
73+
setVisible(true)
74+
return false
75+
})
76+
77+
button('Hide', () => {
78+
setVisible(false)
79+
return false
80+
})
81+
82+
return (
83+
<LunaModal
84+
title={title}
85+
visible={visible}
86+
width={width}
87+
onClose={() => setVisible(false)}
88+
>
89+
{content}
90+
</LunaModal>
91+
)
92+
},
6693
}
6794
)
6895

96+
function createKnobs() {
97+
const title = text('Modal Title', 'This is the Title')
98+
const content = text('Modal Content', 'This is the modal content.')
99+
const width = number('Modal Width', 500, {
100+
range: true,
101+
min: 250,
102+
max: 1000,
103+
stp: 1,
104+
})
105+
106+
return {
107+
title,
108+
content,
109+
width,
110+
}
111+
}
112+
69113
export default def
70114

71-
export const { modal } = def
115+
export const { modal: html, react } = def

Diff for: src/share/story.js

+45-15
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,29 @@ import { addReadme } from 'storybook-readme/html'
1212
import each from 'licia/each'
1313
import addons from '@storybook/addons'
1414
import now from 'licia/now'
15+
import ReactDOM from 'react-dom'
1516
import * as registerKnobs from '@storybook/addon-knobs/dist/registerKnobs'
1617
import { optionsKnob } from '@storybook/addon-knobs'
1718

1819
export default function story(
1920
name,
2021
storyFn,
21-
{ readme, changelog = '', source, layout = 'padded', themes = {} } = {}
22+
{
23+
readme,
24+
changelog = '',
25+
source,
26+
layout = 'padded',
27+
themes = {},
28+
ReactComponent = false,
29+
} = {}
2230
) {
2331
const container = h('div')
2432

2533
if (changelog) {
2634
readme += `\n## Changelog\n${changelog.replace(/## /g, '### ')}`
2735
}
2836

29-
return {
37+
const ret = {
3038
title: map(spaceCase(name).split(' '), upperFirst).join(' '),
3139
decorators: [withKnobs, addReadme],
3240
parameters: {
@@ -42,19 +50,7 @@ export default function story(
4250
layout,
4351
},
4452
[camelCase(name)]: () => {
45-
if (window.components) {
46-
const lastComponentName = window.componentName
47-
if (upperFirst(camelCase(name)) !== lastComponentName) {
48-
// Fix knobs not reset when story changed.
49-
const knobStore = registerKnobs.manager.knobStore
50-
knobStore.reset()
51-
addons.getChannel().emit('storybookjs/knobs/set', {
52-
knobs: knobStore.getAll(),
53-
timestamp: now(),
54-
})
55-
}
56-
each(window.components, (component) => component.destroy())
57-
}
53+
fixKnobs(name)
5854

5955
waitUntil(() => container.parentElement).then(() => {
6056
const theme = optionsKnob(
@@ -92,4 +88,38 @@ export default function story(
9288
return container
9389
},
9490
}
91+
92+
if (ReactComponent) {
93+
const container = h('div')
94+
95+
ret.react = function () {
96+
fixKnobs(`react-${name}`)
97+
98+
window.components = []
99+
delete window.component
100+
window.componentName = upperFirst(camelCase(`react-${name}`))
101+
102+
ReactDOM.render(<ReactComponent />, container)
103+
104+
return container
105+
}
106+
}
107+
108+
return ret
109+
}
110+
111+
function fixKnobs(name) {
112+
if (window.components) {
113+
const lastComponentName = window.componentName
114+
if (upperFirst(camelCase(name)) !== lastComponentName) {
115+
// Fix knobs not reset when story changed.
116+
const knobStore = registerKnobs.manager.knobStore
117+
knobStore.reset()
118+
addons.getChannel().emit('storybookjs/knobs/set', {
119+
knobs: knobStore.getAll(),
120+
timestamp: now(),
121+
})
122+
}
123+
each(window.components, (component) => component.destroy())
124+
}
95125
}

Diff for: src/share/webpack.config.js

-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ module.exports = function (
3131
if (hasStyle) {
3232
entry.unshift(`./src/${name}/style.scss`)
3333
}
34-
3534
if (useIcon) {
3635
entry.unshift(`./src/${name}/icon.css`)
3736
}

0 commit comments

Comments
 (0)