Signed-off-by: Avior <github@avior.me>
This commit is contained in:
Florian Bouillon 2021-09-27 15:48:04 +02:00
parent 9ab56544d9
commit b403a94a2e
16 changed files with 358 additions and 233 deletions

View File

@ -18,6 +18,19 @@ module.exports = {
});
newConfig.resolve.extensions.push('.ts', '.tsx');
// JavaScript
newConfig.module.rules.push({
test: /\.(js|jsx)$/,
// include: [path.resolve(__dirname, '../src/client/components')],
use: [{
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}]
});
newConfig.resolve.extensions.push('.js', '.jsx');
// Stylus
newConfig.module.rules.push({
test: /\.styl$/,

11
package-lock.json generated
View File

@ -10,6 +10,7 @@
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"@dzeio/object-util": "^1.3.0",
"lucide-react": "^0.16.0",
"rollup": "^2.44.0",
"rollup-plugin-styles": "^3.14.1",
@ -1871,6 +1872,11 @@
"node": ">=0.1.95"
}
},
"node_modules/@dzeio/object-util": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.3.0.tgz",
"integrity": "sha512-9pI7BdaS88NIM1RVP3pQ+oJoAPBtYJyInnIzFw3wE+j8W5eyTfudSQvkNoQW6SYrjxND7+IQRCMFwTor+lN5lA=="
},
"node_modules/@emotion/cache": {
"version": "10.0.29",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
@ -24161,6 +24167,11 @@
"minimist": "^1.2.0"
}
},
"@dzeio/object-util": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@dzeio/object-util/-/object-util-1.3.0.tgz",
"integrity": "sha512-9pI7BdaS88NIM1RVP3pQ+oJoAPBtYJyInnIzFw3wE+j8W5eyTfudSQvkNoQW6SYrjxND7+IQRCMFwTor+lN5lA=="
},
"@emotion/cache": {
"version": "10.0.29",
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",

View File

@ -38,6 +38,7 @@
"postinstall": "rollup --config"
},
"dependencies": {
"@dzeio/object-util": "^1.3.0",
"lucide-react": "^0.16.0",
"rollup": "^2.44.0",
"rollup-plugin-styles": "^3.14.1",

View File

@ -1,5 +1,7 @@
.container
margin 48px auto
padding 0 16px
padding 0 32px
width 100%
max-width 1312px
max-width 1280px + @padding[1] * 2
margin auto auto
&.main
margin 48px auto

View File

@ -6,14 +6,11 @@ import css from './Container.module.styl'
interface Props {
children: React.ReactNode
className?: string
mainContainer?: boolean
}
export default class Container extends React.Component<Props> {
public render = () => (
<main className={buildClassName(css.container, this.props.className)}>
{this.props.children}
</main>
)
public render = () => React.createElement(this.props.mainContainer ? 'main' : 'div', {className: buildClassName(css.container, this.props.className, [css.main, this.props.mainContainer]), children: this.props.children})
}

View File

@ -1,7 +1,7 @@
@import '../config.styl'
.footer
padding 16px
padding 24px 0
background $foregroundLight
@media (prefers-color-scheme dark)
background $foregroundDark
@ -9,14 +9,16 @@
ul
padding 0
margin 0
margin-top 12px
display flex
justify-content center
justify-content right
li
display inline-block
padding-left 24px
+ .icon
padding-left 16px
&:not(.icon) + .icon
padding-left 48px
&.socials a
padding 0 8px
.animation
animation grow 1s linear infinite

View File

@ -5,6 +5,9 @@ import Text from '../Text'
import css from './Footer.module.styl'
import Image from 'next/image'
import { Icon } from '../interfaces'
import Container from '../Container'
import Row from '../Row'
import Col from '../Col'
interface Props {
text?: string
@ -22,27 +25,34 @@ interface Props {
export default class Footer extends React.Component<Props> {
public render = () => (
<footer className={css.footer}>
{this.props.text ? (
<Text align="center">{this.props.text}</Text>
) : (
<Text align="center">Made with <span className={css.animation}><Heart color={'#E6808A'} fill={'#E6808A'} size={16} fillOpacity={0.5} /></span> by {this.props.company || 'Dzeio'}</Text>
)}
{this.props.links && (
<ul>{this.props.links.map((l, index) => (
<li key={l.path}><Text>{index !== 0 && (<>&nbsp;- </>)}<Link href={l.path}>{l.name}</Link></Text></li>
))}</ul>
)}
{this.props.socials && (
<ul className={css.socials}>{this.props.socials.map((l, index) => (
<li key={l.href}><Text><Link hideIcon noStyle href={l.href}>
{typeof l.icon === 'string' ? (
<Image width={24} height={24} src={l.icon} />
<Container>
<Row nomargin>
<Col>
{this.props.text ? (
<Text>{this.props.text}</Text>
) : (
<l.icon size={24} />
)}
</Link></Text></li>
))}</ul>
)}
<Text>Made with <span className={css.animation}><Heart color={'#E6808A'} fill={'#E6808A'} size={16} fillOpacity={0.5} /></span> by {this.props.company || 'Dzeio'}</Text>
)}
</Col>
<Col>
<ul>
{this.props.links && this.props.links.map((l, index) => (
<li key={l.path + index}><Text><Link href={l.path} hideIcon>{l.name}</Link></Text></li>
))}
{this.props.socials && this.props.socials.map((l, index) => (
<li key={l.href + index} className={css.icon}><Text><Link hideIcon noStyle href={l.href}>
{typeof l.icon === 'string' ? (
<Image width={24} height={24} src={l.icon} />
) : (
<l.icon size={24} />
)}
</Link></Text></li>
))}
</ul>
</Col>
</Row>
</Container>
</footer>
)
}

View File

@ -15,6 +15,9 @@
color $darkGrayDark
transition color $transition
pointer-events none
&.iconClickable
pointer-events all
cursor pointer
top 14px
&.left
left 16px // input padding-left
@ -51,52 +54,25 @@
/* End */
.autocomplete
display flex
opacity 0
transition all $transition
overflow-x hidden
pointer-events none
// display flex
flex-direction column
list-style none
position absolute
top calc(100% - 4px)
top calc(100% + 16px)
left 0
width 100%
z-index 100
box-shadow 0 2px 4px rgba(black, .3)
margin 0
padding 0
background $foregroundLight
@media (prefers-color-scheme dark)
background $foregroundDark
border-radius 8px
max-height 25vh
overflow-y auto
@media (max-width $mobile)
max-height 50vh
&.reverse
flex-direction column-reverse
top initial
bottom 100%
box-shadow 0 -4px 8px rgba(black, .3)
border-radius 0
border-top-left-radius 4px
border-top-right-radius 4px
li
transition all $transition
padding 8px
@media (max-width $mobile)
padding 24px
cursor pointer
&:hover
background darken(@background, 20%)
@media (prefers-color-scheme dark)
background lighten(lighten($foregroundDark, 5%), 20%)
bottom calc(100% + 16px)
div + .autocomplete
top calc(100% - 4px - .9em)
top 100%
input:focus ~ .autocomplete
select:focus ~ .autocomplete
@ -127,9 +103,13 @@
font-size rem(16)
transition color $transition
opacity 1
color $darkGrayLight
// color $darkGrayLight
// @media (prefers-color-scheme dark)
// color $darkGrayDark
color black
@media (prefers-color-scheme dark)
color $darkGrayDark
color white
&:disabled
@ -162,14 +142,14 @@
~ svg
color @border-color
&::placeholder
color black
// &::placeholder
// color black
@media (prefers-color-scheme dark)
color white
// @media (prefers-color-scheme dark)
// color white
&:invalid
border-color $errorDark
border-color $errorLight
~ label
color @border-color
@ -177,7 +157,7 @@
~ svg
color @border-color
@media (prefers-color-scheme dark)
border-color $errorLight
border-color $errorDark
~ label
color @border-color
@ -190,53 +170,7 @@
padding-left 16px + 24px + 10px
&.iconRight
padding-right 16 + 24 + 10px
&.filled
border none
background rgba(gray, .1)
border-radius @border-radius @border-radius 0 0
border-bottom 2px solid rgba(black,.4)
@media (prefers-color-scheme dark)
background rgba(white, .1)
border-bottom 2px solid rgba(white,.4)
&.opaque
background white
@media (prefers-color-scheme dark)
background #202020
&:hover:not(:disabled)
background rgba(gray, .2)
@media (prefers-color-scheme dark)
background rgba(white, .2)
&.opaque
background darken(white, 5%)
@media (prefers-color-scheme dark)
background #1c1c1c
&:focus
background rgba(gray, .3)
border-bottom 2px solid $main
&.opaque
background white
@media (prefers-color-scheme dark)
background #202020
&:not(:placeholder-shown)
&:focus
&:not([placeholder=" "])
~ label
top 3px
left 16px - 4px // .input/padding-left label/padding-left
background transparent
padding 0
font-size .75rem
~ svg.left ~ label
left 16px + 24px + 10px // .input/padding-left label/padding-left
~ svg.rotate
transform rotateX(0)
transition $transition
@ -249,6 +183,9 @@
padding 0 8px
font-size rem(14)
&.block, &.block input, &.block select, &.block textarea
&.block
&.block input
&.block select
&.block textarea
width 100%
display block

View File

@ -1,29 +1,37 @@
import { Meta } from '@storybook/react/types-6-0'
import { Story } from "@storybook/react"
import React from 'react'
import Component from '.'
import { X } from 'lucide-react'
export default {
title: 'DZEIO/Input',
component: Component
} as Meta
export const Basic: Story<any> = (args: any) => <Component {...args} />
let tmp = Basic.bind({})
tmp.args = {label: 'Label', helper: 'Helper', maxLength: 6, characterCount: true, icon: X}
export const Normal = tmp
tmp = Basic.bind({})
tmp.args = {label: 'Label', filled:true, helper: 'Helper', autocomplete: ['a', 'b', 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'], characterCount: true, icon: X}
export const AutoComplete = tmp
export const Select: Story<any> = (args: any) => <Component type="select" {...args}>
<option>a</option>
<option>b</option>
<option>c</option>
<option>d</option>
</Component>
import { Meta } from '@storybook/react/types-6-0'
import { Story } from "@storybook/react"
import React from 'react'
import Component from '.'
import { X } from 'lucide-react'
export default {
title: 'DZEIO/Input',
component: Component
} as Meta
export const Basic: Story<any> = (args: any) => <Component {...args} />
let tmp = Basic.bind({})
tmp.args = {label: 'Label', helper: 'Helper', maxLength: 6, iconLeft: {
icon: X,
transformer: (v: string) => v + 1
}}
export const Normal = tmp
tmp = Basic.bind({})
tmp.args = {label: 'Label', filled:true, helper: 'Helper', choices: [
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'b',
{value: 'd', display: 'D'},
'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
], characterCount: true, iconLeft: X}
export const AutoComplete = tmp

View File

@ -5,22 +5,28 @@ import Text from '../Text'
import { Icon } from '../interfaces'
import { buildClassName } from '../Util'
import css from './Input.module.styl'
import Menu from '../Menu'
import { objectClone } from '@dzeio/object-util'
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
id?: string
label?: string
icon?: Icon
iconRight?: Icon
iconLeft?: Icon | {
icon: Icon
transformer: (value: string) => string
}
iconRight?: Icon | {
icon: Icon
transformer: (value: string) => string
}
helper?: string
inputRef?: React.RefObject<HTMLInputElement>
selectRef?: React.RefObject<HTMLSelectElement>
type?: 'color' | 'text' | 'date' | 'datetime-local' |
'email' | 'file' | 'month' | 'number' | 'password' |
'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
// Custom Types
'select' | 'textarea'
autocomplete?: Array<string>
children?: React.ReactNode
'textarea'
choices?: Array<string | {display: string, value: string}>
}
interface States {
@ -29,100 +35,78 @@ interface States {
isInFirstPartOfScreen?: boolean
}
export default class Input extends React.Component<Props, States> {
export default class Input extends React.PureComponent<Props, States> {
public state: States = {}
// any because f*ck types
private inputRef: React.RefObject<any> = React.createRef()
private inputRef: React.RefObject<HTMLInputElement> = React.createRef()
private parentRef: React.RefObject<HTMLDivElement> = React.createRef()
public componentDidMount() {
if (this.props.type === 'textarea') {
this.textareaHandler()
}
if (this.props.autocomplete) {
if (this.props.choices) {
window.addEventListener('scroll', this.parentScroll)
this.parentScroll()
}
}
public componentDidUpdate() {
console.log(this.state)
}
public componentWillUnmount() {
if (this.props.autocomplete) {
if (this.props.choices) {
window.removeEventListener('scroll', this.parentScroll)
}
}
public render() {
const props: Props = Object.assign({}, this.props)
const props: Props = objectClone(this.props)
delete props.label
delete props.children
delete props.icon
delete props.helper
delete props.autocomplete
delete props.iconLeft
delete props.iconRight
delete props.inputRef
delete props.selectRef
delete props.color
delete props.helper
delete props.choices
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
placeholder: this.props.label || this.props.placeholder || ' ',
ref: this.props.inputRef || this.inputRef,
className: buildClassName(
[css.iconLeft, this.props.icon],
[css.iconRight, this.props.iconRight || this.props.autocomplete]
[css.iconLeft, this.props.iconLeft],
[css.iconRight, this.props.iconRight || this.props.choices]
),
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
}
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
if (this.props.type === 'number') {
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
}
if (this.props.type === 'select' && !this.props.readOnly) {
input = (
<select
ref={this.props.selectRef || this.inputRef}
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>}
className={buildClassName(
[css.iconLeft, this.props.icon],
[css.iconRight, !this.props.disabled || this.props.iconRight],
[css[this.props.color as string], this.props.color]
)}
>
{this.props.children}
</select>
)
// select is readonly
} else if (this.props.type === 'select') {
input = (
<input
{...props}
{...baseProps}
type="text"
/>
)
} else if (this.props.type === 'textarea') {
delete baseProps.ref
input = (
<textarea
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>}
{...baseProps as any}
ref={this.inputRef}
style={{minHeight: this.state?.textAreaHeight}}
onKeyDown={this.textareaHandler}
onKeyUp={this.textareaHandler}
onFocus={this.textareaHandler}
/>
)
} else {
input = (
<input
{...props}
{...baseProps}
/>
switch (this.props.type) {
case 'textarea':
delete baseProps.ref
input = (
<textarea
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>}
{...baseProps as any}
ref={this.inputRef}
style={{minHeight: this.state?.textAreaHeight}}
onKeyDown={this.textareaHandler}
onKeyUp={this.textareaHandler}
onFocus={this.textareaHandler}
/>
)
break
case 'number':
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
default:
input = (
<input
{...props}
{...baseProps}
/>
)
}
@ -134,17 +118,15 @@ export default class Input extends React.Component<Props, States> {
onChangeCapture={this.onChange}
ref={this.parentRef}
>
{input}
{input as any}
{/* Left Icon */}
{this.props.icon && (
<this.props.icon size="18" className={css.left} />
)}
{this.getIcon('left')}
{/* Right Icon */}
{this.props.iconRight ? (
<this.props.iconRight size="18" className={css.right} />
) : ((this.props.type === 'select' || this.props.autocomplete) && !this.props.disabled) && (
{this.props.iconRight ?
this.getIcon('right') :
(this.props.choices && !this.props.disabled) && (
<ChevronDown size="18" className={buildClassName(css.right, css.rotate)} />
)}
@ -154,10 +136,23 @@ export default class Input extends React.Component<Props, States> {
)}
{/* List when this is an autocomplete */}
{this.props.autocomplete && this.props.autocomplete.indexOf(this.state?.value ?? this.props.value?.toString() ?? '') === -1 && (
<ul className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}>
{this.props.autocomplete.filter((item) => item.toLowerCase().includes(this.state?.value?.toLowerCase() ?? this.props.value?.toString().toLowerCase() ?? '')).map((item) => (<li key={item} onClick={this.onAutoCompleteClick(item)}><Text>{item}</Text></li>))}
</ul>
{this.props.choices && (
// <ul className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}>
// {this.props.choices
// .map((item, index) => typeof item === 'string' ? ({item: {display: item, value: item}, index}) : {item, index})
// .filter(
// (item) => !this.getValue() || [item.item.display.toLowerCase(), item.item.value.toLowerCase()]
// .includes(this.getValue())
// )
// .map((item) => (<li key={item.index} onClick={this.onAutoCompleteClick(item.index)}><Text>{item.item.display}</Text></li>))}
// </ul>
<Menu
outline
hideWhenEmpty
className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}
items={this.buildList()}
onClick={this.listSelection}
/>
)}
</div>
)
@ -176,8 +171,56 @@ export default class Input extends React.Component<Props, States> {
}
}
private buildList(): Menu['props']['items'] {
if (!this.props.choices) {
return []
}
const v = this.getValue().toLowerCase()
return this.props.choices
.map((item, index) => typeof item === 'string' ? ({item: {display: item, value: item}, index}) : {item, index})
.filter(
(item) => !v || item.item.display.toLowerCase().includes(v) || item.item.display.toLowerCase().toLowerCase().includes(v)
)
.map((item) => ({display: item.item.display, value: item.index}))
}
private listSelection: Menu['props']['onClick'] = async (value: number, key) => {
const newValue = this.props.choices?.[value]
if (!newValue) {
return
}
if (typeof newValue === 'string') {
return this.setValue(newValue)
}
await this.setValue(newValue.display)
this.setState({value: newValue.value})
}
private getIcon(icon: 'left' | 'right') {
const Icon = icon === 'left' ? this.props.iconLeft : this.props.iconRight
if (!Icon) {
return undefined
}
if ('icon' in Icon) {
return <Icon.icon size="18" className={buildClassName(css[icon], css.iconClickable)} onClick={() => {
const el = this.getElement()
console.log(el, 'pouet')
if (!el) {
return
}
el.value = Icon.transformer(el.value)
}} />
}
return <Icon size="18" className={css[icon]} />
}
private getValue(): string {
return this.state?.value?.toLowerCase() ?? this.props.value?.toString().toLowerCase() ?? ''
}
private getElement(): undefined | HTMLInputElement {
const item = this.props.inputRef || this.props.selectRef || this.inputRef
const item = this.props.inputRef || this.inputRef
if (!item || !item.current) {return}
return item.current
}
@ -188,8 +231,7 @@ export default class Input extends React.Component<Props, States> {
this.setState({textAreaHeight: this.inputRef.current.scrollHeight})
})
private onAutoCompleteClick = (value: string) => async () => {
// console.log('test')
private async setValue(value: string) {
const item = this.getElement()
if (!item) {return}
const valueSetter = Object.getOwnPropertyDescriptor(item, 'value')?.set

View File

@ -0,0 +1,38 @@
@import "../config"
.menu
&.outline
border 2px solid $main
&.hidden
display none
ul
padding 0
margin 0
display flex
flex-direction column
li
svg
margin-right 8px
&:last-child
margin 0
display inline-flex
padding 8px
overflow-x hidden
text-overflow: ellipsis;
margin-bottom 8px
color $darkGrayLight
cursor pointer
font-weight bold
border-radius 8px
&:hover
color black
background $lightGrayLight
@media (prefers-color-scheme dark)
color $lightGrayDark
&:hover
color white
background $darkGrayDark
&.selected
&:active
background $main
color $textOnMain

View File

@ -0,0 +1,26 @@
import { Meta } from '@storybook/react/types-6-0'
import { XOctagon } from 'lucide-react'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Menu',
component: Component
} as Meta
const list: Component['props']['items'] = [
{value: 'Menu item 1'},
{value: 'Menu item 2', icon: XOctagon},
{value: 'Menu item 3', icon: XOctagon},
{value: 'Menu item 4', icon: XOctagon},
{value: 'Menu item 5', selected: true, icon: XOctagon},
{value: 'Menu item 6', icon: XOctagon},
{value: 'Menu item 7', icon: XOctagon},
{value: 'Menu item 8', icon: XOctagon},
{value: 'Menu item 9', icon: XOctagon},
{value: 'Menu item 10', icon: XOctagon}
]
export const Basic = (args: any) => (
<Component outline {...args} items={list} onClick={(_, index) => list[index].selected = !list[index].selected} />
)

31
src/dzeio/Menu/index.tsx Normal file
View File

@ -0,0 +1,31 @@
import React from 'react'
import Box from '../Box'
import { Icon } from '../interfaces'
import { buildClassName } from '../Util'
import css from './Menu.module.styl'
interface Props {
items: Array<{display?: string, value: any, selected?: boolean, icon?: Icon}>
outline?: boolean
onClick?: (value: any, key: number) => void
className?: string
hideWhenEmpty?: boolean
}
export default class Menu extends React.Component<Props> {
public render = () => (
<Box className={buildClassName(css.menu, this.props.className, [css.outline, this.props.outline], [css.hidden, this.props.hideWhenEmpty, this.props.items.length === 0])}>
<ul>
{this.props.items.map((item, key) => (
<li key={key} className={buildClassName([css.selected, item.selected])} onClick={() => this.props.onClick?.(item.value, key)}>
{item.icon && (
<item.icon size="24" />
)}
{item.display ?? item.value}
</li>
))}
</ul>
</Box>
)
}

View File

@ -1,4 +1,4 @@
export function buildClassName(...classes: Array<Array<any> | string | undefined>): string|undefined {
export function buildClassName(...classes: Array<Array<any> | string | undefined>): string | undefined {
const classesFinal: Array<string> = []
root: for (const classe of classes) {
if (typeof classe === 'undefined') {

View File

@ -1,8 +1,15 @@
//@import "_aileron"
@import "config"
body
*
*::before
*::after
box-sizing border-box
scrollbar-width 16px
scrollbar-color darken($foregroundLight, 16%) $foregroundLight
@media (prefers-color-scheme dark)
scrollbar-color lighten($foregroundDark, 16%) $foregroundDark
font-family: ui-system, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', Helvetica, Arial, sans-serif
// Georgia,Cambria,"Times New Roman",Times,serif
@ -25,9 +32,9 @@ a
text-decoration none
/* width */
::-webkit-scrollbar {
width: 16px;
}
::-webkit-scrollbar
width 16px
/* Track */
::-webkit-scrollbar-corner

View File

@ -1,6 +1,4 @@
/**
* Copyright (c) 2021
*
* @summary DZEIO Component Library
*/
@ -18,6 +16,7 @@ import Image from './dzeio/Image'
import Input from './dzeio/Input'
import Link from './dzeio/Link'
import Loader from './dzeio/Loader'
import Menu from './dzeio/Menu'
import Navbar from './dzeio/Navbar'
import NotificationManager from './dzeio/NotificationManager'
import Popup from './dzeio/Popup'
@ -39,6 +38,7 @@ export {
Input,
Link,
Loader,
Menu,
Navbar,
NotificationManager,
Popup,