mirror of
https://github.com/dzeiocom/components.git
synced 2025-04-23 19:32:14 +00:00
Update
Signed-off-by: Avior <github@avior.me>
This commit is contained in:
parent
9ab56544d9
commit
b403a94a2e
@ -18,6 +18,19 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
newConfig.resolve.extensions.push('.ts', '.tsx');
|
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
|
// Stylus
|
||||||
newConfig.module.rules.push({
|
newConfig.module.rules.push({
|
||||||
test: /\.styl$/,
|
test: /\.styl$/,
|
||||||
|
11
package-lock.json
generated
11
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dzeio/object-util": "^1.3.0",
|
||||||
"lucide-react": "^0.16.0",
|
"lucide-react": "^0.16.0",
|
||||||
"rollup": "^2.44.0",
|
"rollup": "^2.44.0",
|
||||||
"rollup-plugin-styles": "^3.14.1",
|
"rollup-plugin-styles": "^3.14.1",
|
||||||
@ -1871,6 +1872,11 @@
|
|||||||
"node": ">=0.1.95"
|
"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": {
|
"node_modules/@emotion/cache": {
|
||||||
"version": "10.0.29",
|
"version": "10.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
|
||||||
@ -24161,6 +24167,11 @@
|
|||||||
"minimist": "^1.2.0"
|
"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": {
|
"@emotion/cache": {
|
||||||
"version": "10.0.29",
|
"version": "10.0.29",
|
||||||
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz",
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
"postinstall": "rollup --config"
|
"postinstall": "rollup --config"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dzeio/object-util": "^1.3.0",
|
||||||
"lucide-react": "^0.16.0",
|
"lucide-react": "^0.16.0",
|
||||||
"rollup": "^2.44.0",
|
"rollup": "^2.44.0",
|
||||||
"rollup-plugin-styles": "^3.14.1",
|
"rollup-plugin-styles": "^3.14.1",
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
.container
|
.container
|
||||||
margin 48px auto
|
padding 0 32px
|
||||||
padding 0 16px
|
|
||||||
width 100%
|
width 100%
|
||||||
max-width 1312px
|
max-width 1280px + @padding[1] * 2
|
||||||
|
margin auto auto
|
||||||
|
&.main
|
||||||
|
margin 48px auto
|
||||||
|
@ -6,14 +6,11 @@ import css from './Container.module.styl'
|
|||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
className?: string
|
className?: string
|
||||||
|
mainContainer?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Container extends React.Component<Props> {
|
export default class Container extends React.Component<Props> {
|
||||||
|
|
||||||
public render = () => (
|
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})
|
||||||
<main className={buildClassName(css.container, this.props.className)}>
|
|
||||||
{this.props.children}
|
|
||||||
</main>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
@import '../config.styl'
|
@import '../config.styl'
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
padding 16px
|
padding 24px 0
|
||||||
background $foregroundLight
|
background $foregroundLight
|
||||||
@media (prefers-color-scheme dark)
|
@media (prefers-color-scheme dark)
|
||||||
background $foregroundDark
|
background $foregroundDark
|
||||||
@ -9,14 +9,16 @@
|
|||||||
ul
|
ul
|
||||||
padding 0
|
padding 0
|
||||||
margin 0
|
margin 0
|
||||||
margin-top 12px
|
|
||||||
display flex
|
display flex
|
||||||
justify-content center
|
justify-content right
|
||||||
li
|
li
|
||||||
display inline-block
|
display inline-block
|
||||||
|
padding-left 24px
|
||||||
|
+ .icon
|
||||||
|
padding-left 16px
|
||||||
|
&:not(.icon) + .icon
|
||||||
|
padding-left 48px
|
||||||
|
|
||||||
&.socials a
|
|
||||||
padding 0 8px
|
|
||||||
|
|
||||||
.animation
|
.animation
|
||||||
animation grow 1s linear infinite
|
animation grow 1s linear infinite
|
||||||
|
@ -5,6 +5,9 @@ import Text from '../Text'
|
|||||||
import css from './Footer.module.styl'
|
import css from './Footer.module.styl'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
import { Icon } from '../interfaces'
|
import { Icon } from '../interfaces'
|
||||||
|
import Container from '../Container'
|
||||||
|
import Row from '../Row'
|
||||||
|
import Col from '../Col'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
text?: string
|
text?: string
|
||||||
@ -22,27 +25,34 @@ interface Props {
|
|||||||
export default class Footer extends React.Component<Props> {
|
export default class Footer extends React.Component<Props> {
|
||||||
public render = () => (
|
public render = () => (
|
||||||
<footer className={css.footer}>
|
<footer className={css.footer}>
|
||||||
{this.props.text ? (
|
<Container>
|
||||||
<Text align="center">{this.props.text}</Text>
|
<Row nomargin>
|
||||||
) : (
|
<Col>
|
||||||
<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.text ? (
|
||||||
)}
|
<Text>{this.props.text}</Text>
|
||||||
{this.props.links && (
|
|
||||||
<ul>{this.props.links.map((l, index) => (
|
|
||||||
<li key={l.path}><Text>{index !== 0 && (<> - </>)}<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} />
|
|
||||||
) : (
|
) : (
|
||||||
<l.icon size={24} />
|
<Text>Made with <span className={css.animation}><Heart color={'#E6808A'} fill={'#E6808A'} size={16} fillOpacity={0.5} /></span> by {this.props.company || 'Dzeio'}</Text>
|
||||||
)}
|
)}
|
||||||
</Link></Text></li>
|
</Col>
|
||||||
))}</ul>
|
<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>
|
</footer>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
color $darkGrayDark
|
color $darkGrayDark
|
||||||
transition color $transition
|
transition color $transition
|
||||||
pointer-events none
|
pointer-events none
|
||||||
|
&.iconClickable
|
||||||
|
pointer-events all
|
||||||
|
cursor pointer
|
||||||
top 14px
|
top 14px
|
||||||
&.left
|
&.left
|
||||||
left 16px // input padding-left
|
left 16px // input padding-left
|
||||||
@ -51,52 +54,25 @@
|
|||||||
/* End */
|
/* End */
|
||||||
|
|
||||||
.autocomplete
|
.autocomplete
|
||||||
display flex
|
|
||||||
opacity 0
|
opacity 0
|
||||||
transition all $transition
|
transition all $transition
|
||||||
overflow-x hidden
|
overflow-x hidden
|
||||||
pointer-events none
|
pointer-events none
|
||||||
// display flex
|
|
||||||
flex-direction column
|
|
||||||
list-style none
|
|
||||||
position absolute
|
position absolute
|
||||||
top calc(100% - 4px)
|
top calc(100% + 16px)
|
||||||
left 0
|
left 0
|
||||||
width 100%
|
width 100%
|
||||||
z-index 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
|
max-height 25vh
|
||||||
overflow-y auto
|
overflow-y auto
|
||||||
@media (max-width $mobile)
|
@media (max-width $mobile)
|
||||||
max-height 50vh
|
max-height 50vh
|
||||||
&.reverse
|
&.reverse
|
||||||
flex-direction column-reverse
|
|
||||||
top initial
|
top initial
|
||||||
bottom 100%
|
bottom calc(100% + 16px)
|
||||||
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%)
|
|
||||||
|
|
||||||
div + .autocomplete
|
div + .autocomplete
|
||||||
top calc(100% - 4px - .9em)
|
top 100%
|
||||||
|
|
||||||
input:focus ~ .autocomplete
|
input:focus ~ .autocomplete
|
||||||
select:focus ~ .autocomplete
|
select:focus ~ .autocomplete
|
||||||
@ -127,9 +103,13 @@
|
|||||||
font-size rem(16)
|
font-size rem(16)
|
||||||
transition color $transition
|
transition color $transition
|
||||||
opacity 1
|
opacity 1
|
||||||
color $darkGrayLight
|
// color $darkGrayLight
|
||||||
|
// @media (prefers-color-scheme dark)
|
||||||
|
// color $darkGrayDark
|
||||||
|
|
||||||
|
color black
|
||||||
@media (prefers-color-scheme dark)
|
@media (prefers-color-scheme dark)
|
||||||
color $darkGrayDark
|
color white
|
||||||
|
|
||||||
|
|
||||||
&:disabled
|
&:disabled
|
||||||
@ -162,14 +142,14 @@
|
|||||||
|
|
||||||
~ svg
|
~ svg
|
||||||
color @border-color
|
color @border-color
|
||||||
&::placeholder
|
// &::placeholder
|
||||||
color black
|
// color black
|
||||||
|
|
||||||
@media (prefers-color-scheme dark)
|
// @media (prefers-color-scheme dark)
|
||||||
color white
|
// color white
|
||||||
|
|
||||||
&:invalid
|
&:invalid
|
||||||
border-color $errorDark
|
border-color $errorLight
|
||||||
|
|
||||||
~ label
|
~ label
|
||||||
color @border-color
|
color @border-color
|
||||||
@ -177,7 +157,7 @@
|
|||||||
~ svg
|
~ svg
|
||||||
color @border-color
|
color @border-color
|
||||||
@media (prefers-color-scheme dark)
|
@media (prefers-color-scheme dark)
|
||||||
border-color $errorLight
|
border-color $errorDark
|
||||||
|
|
||||||
~ label
|
~ label
|
||||||
color @border-color
|
color @border-color
|
||||||
@ -190,53 +170,7 @@
|
|||||||
padding-left 16px + 24px + 10px
|
padding-left 16px + 24px + 10px
|
||||||
&.iconRight
|
&.iconRight
|
||||||
padding-right 16 + 24 + 10px
|
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
|
~ svg.rotate
|
||||||
transform rotateX(0)
|
transform rotateX(0)
|
||||||
transition $transition
|
transition $transition
|
||||||
@ -249,6 +183,9 @@
|
|||||||
padding 0 8px
|
padding 0 8px
|
||||||
font-size rem(14)
|
font-size rem(14)
|
||||||
|
|
||||||
&.block, &.block input, &.block select, &.block textarea
|
&.block
|
||||||
|
&.block input
|
||||||
|
&.block select
|
||||||
|
&.block textarea
|
||||||
width 100%
|
width 100%
|
||||||
display block
|
display block
|
||||||
|
@ -12,18 +12,26 @@ export default {
|
|||||||
export const Basic: Story<any> = (args: any) => <Component {...args} />
|
export const Basic: Story<any> = (args: any) => <Component {...args} />
|
||||||
|
|
||||||
let tmp = Basic.bind({})
|
let tmp = Basic.bind({})
|
||||||
tmp.args = {label: 'Label', helper: 'Helper', maxLength: 6, characterCount: true, icon: X}
|
tmp.args = {label: 'Label', helper: 'Helper', maxLength: 6, iconLeft: {
|
||||||
|
icon: X,
|
||||||
|
transformer: (v: string) => v + 1
|
||||||
|
}}
|
||||||
|
|
||||||
export const Normal = tmp
|
export const Normal = tmp
|
||||||
|
|
||||||
tmp = Basic.bind({})
|
tmp = Basic.bind({})
|
||||||
tmp.args = {label: 'Label', filled:true, helper: 'Helper', autocomplete: ['a', 'b', 'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'], characterCount: true, icon: X}
|
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
|
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>
|
|
@ -5,22 +5,28 @@ import Text from '../Text'
|
|||||||
import { Icon } from '../interfaces'
|
import { Icon } from '../interfaces'
|
||||||
import { buildClassName } from '../Util'
|
import { buildClassName } from '../Util'
|
||||||
import css from './Input.module.styl'
|
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> {
|
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
|
||||||
id?: string
|
id?: string
|
||||||
label?: string
|
label?: string
|
||||||
icon?: Icon
|
iconLeft?: Icon | {
|
||||||
iconRight?: Icon
|
icon: Icon
|
||||||
|
transformer: (value: string) => string
|
||||||
|
}
|
||||||
|
iconRight?: Icon | {
|
||||||
|
icon: Icon
|
||||||
|
transformer: (value: string) => string
|
||||||
|
}
|
||||||
helper?: string
|
helper?: string
|
||||||
inputRef?: React.RefObject<HTMLInputElement>
|
inputRef?: React.RefObject<HTMLInputElement>
|
||||||
selectRef?: React.RefObject<HTMLSelectElement>
|
|
||||||
type?: 'color' | 'text' | 'date' | 'datetime-local' |
|
type?: 'color' | 'text' | 'date' | 'datetime-local' |
|
||||||
'email' | 'file' | 'month' | 'number' | 'password' |
|
'email' | 'file' | 'month' | 'number' | 'password' |
|
||||||
'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
|
'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
|
||||||
// Custom Types
|
// Custom Types
|
||||||
'select' | 'textarea'
|
'textarea'
|
||||||
autocomplete?: Array<string>
|
choices?: Array<string | {display: string, value: string}>
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface States {
|
interface States {
|
||||||
@ -29,100 +35,78 @@ interface States {
|
|||||||
isInFirstPartOfScreen?: boolean
|
isInFirstPartOfScreen?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Input extends React.Component<Props, States> {
|
export default class Input extends React.PureComponent<Props, States> {
|
||||||
|
|
||||||
public state: States = {}
|
public state: States = {}
|
||||||
|
|
||||||
// any because f*ck types
|
// 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()
|
private parentRef: React.RefObject<HTMLDivElement> = React.createRef()
|
||||||
|
|
||||||
public componentDidMount() {
|
public componentDidMount() {
|
||||||
if (this.props.type === 'textarea') {
|
if (this.props.type === 'textarea') {
|
||||||
this.textareaHandler()
|
this.textareaHandler()
|
||||||
}
|
}
|
||||||
if (this.props.autocomplete) {
|
if (this.props.choices) {
|
||||||
window.addEventListener('scroll', this.parentScroll)
|
window.addEventListener('scroll', this.parentScroll)
|
||||||
this.parentScroll()
|
this.parentScroll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate() {
|
||||||
|
console.log(this.state)
|
||||||
|
}
|
||||||
|
|
||||||
public componentWillUnmount() {
|
public componentWillUnmount() {
|
||||||
if (this.props.autocomplete) {
|
if (this.props.choices) {
|
||||||
window.removeEventListener('scroll', this.parentScroll)
|
window.removeEventListener('scroll', this.parentScroll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
const props: Props = Object.assign({}, this.props)
|
const props: Props = objectClone(this.props)
|
||||||
delete props.label
|
delete props.label
|
||||||
delete props.children
|
delete props.iconLeft
|
||||||
delete props.icon
|
|
||||||
delete props.helper
|
|
||||||
delete props.autocomplete
|
|
||||||
delete props.iconRight
|
delete props.iconRight
|
||||||
delete props.inputRef
|
delete props.inputRef
|
||||||
delete props.selectRef
|
delete props.helper
|
||||||
delete props.color
|
delete props.choices
|
||||||
|
|
||||||
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
|
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
|
||||||
placeholder: this.props.label || this.props.placeholder || ' ',
|
placeholder: this.props.label || this.props.placeholder || ' ',
|
||||||
ref: this.props.inputRef || this.inputRef,
|
ref: this.props.inputRef || this.inputRef,
|
||||||
className: buildClassName(
|
className: buildClassName(
|
||||||
[css.iconLeft, this.props.icon],
|
[css.iconLeft, this.props.iconLeft],
|
||||||
[css.iconRight, this.props.iconRight || this.props.autocomplete]
|
[css.iconRight, this.props.iconRight || this.props.choices]
|
||||||
),
|
),
|
||||||
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
|
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
|
||||||
}
|
}
|
||||||
|
|
||||||
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
|
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
|
||||||
|
|
||||||
if (this.props.type === 'number') {
|
switch (this.props.type) {
|
||||||
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
|
case 'textarea':
|
||||||
}
|
delete baseProps.ref
|
||||||
|
input = (
|
||||||
if (this.props.type === 'select' && !this.props.readOnly) {
|
<textarea
|
||||||
input = (
|
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>}
|
||||||
<select
|
{...baseProps as any}
|
||||||
ref={this.props.selectRef || this.inputRef}
|
ref={this.inputRef}
|
||||||
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>}
|
style={{minHeight: this.state?.textAreaHeight}}
|
||||||
className={buildClassName(
|
onKeyDown={this.textareaHandler}
|
||||||
[css.iconLeft, this.props.icon],
|
onKeyUp={this.textareaHandler}
|
||||||
[css.iconRight, !this.props.disabled || this.props.iconRight],
|
onFocus={this.textareaHandler}
|
||||||
[css[this.props.color as string], this.props.color]
|
/>
|
||||||
)}
|
)
|
||||||
>
|
break
|
||||||
{this.props.children}
|
case 'number':
|
||||||
</select>
|
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
|
||||||
)
|
default:
|
||||||
// select is readonly
|
input = (
|
||||||
} else if (this.props.type === 'select') {
|
<input
|
||||||
input = (
|
{...props}
|
||||||
<input
|
{...baseProps}
|
||||||
{...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}
|
|
||||||
/>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,17 +118,15 @@ export default class Input extends React.Component<Props, States> {
|
|||||||
onChangeCapture={this.onChange}
|
onChangeCapture={this.onChange}
|
||||||
ref={this.parentRef}
|
ref={this.parentRef}
|
||||||
>
|
>
|
||||||
{input}
|
{input as any}
|
||||||
|
|
||||||
{/* Left Icon */}
|
{/* Left Icon */}
|
||||||
{this.props.icon && (
|
{this.getIcon('left')}
|
||||||
<this.props.icon size="18" className={css.left} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Right Icon */}
|
{/* Right Icon */}
|
||||||
{this.props.iconRight ? (
|
{this.props.iconRight ?
|
||||||
<this.props.iconRight size="18" className={css.right} />
|
this.getIcon('right') :
|
||||||
) : ((this.props.type === 'select' || this.props.autocomplete) && !this.props.disabled) && (
|
(this.props.choices && !this.props.disabled) && (
|
||||||
<ChevronDown size="18" className={buildClassName(css.right, css.rotate)} />
|
<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 */}
|
{/* List when this is an autocomplete */}
|
||||||
{this.props.autocomplete && this.props.autocomplete.indexOf(this.state?.value ?? this.props.value?.toString() ?? '') === -1 && (
|
{this.props.choices && (
|
||||||
<ul className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}>
|
// <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>))}
|
// {this.props.choices
|
||||||
</ul>
|
// .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>
|
</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 {
|
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}
|
if (!item || !item.current) {return}
|
||||||
return item.current
|
return item.current
|
||||||
}
|
}
|
||||||
@ -188,8 +231,7 @@ export default class Input extends React.Component<Props, States> {
|
|||||||
this.setState({textAreaHeight: this.inputRef.current.scrollHeight})
|
this.setState({textAreaHeight: this.inputRef.current.scrollHeight})
|
||||||
})
|
})
|
||||||
|
|
||||||
private onAutoCompleteClick = (value: string) => async () => {
|
private async setValue(value: string) {
|
||||||
// console.log('test')
|
|
||||||
const item = this.getElement()
|
const item = this.getElement()
|
||||||
if (!item) {return}
|
if (!item) {return}
|
||||||
const valueSetter = Object.getOwnPropertyDescriptor(item, 'value')?.set
|
const valueSetter = Object.getOwnPropertyDescriptor(item, 'value')?.set
|
||||||
|
38
src/dzeio/Menu/Menu.module.styl
Normal file
38
src/dzeio/Menu/Menu.module.styl
Normal 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
|
26
src/dzeio/Menu/Menu.stories.tsx
Normal file
26
src/dzeio/Menu/Menu.stories.tsx
Normal 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
31
src/dzeio/Menu/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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> = []
|
const classesFinal: Array<string> = []
|
||||||
root: for (const classe of classes) {
|
root: for (const classe of classes) {
|
||||||
if (typeof classe === 'undefined') {
|
if (typeof classe === 'undefined') {
|
||||||
|
@ -1,8 +1,15 @@
|
|||||||
//@import "_aileron"
|
//@import "_aileron"
|
||||||
@import "config"
|
@import "config"
|
||||||
|
|
||||||
body
|
*
|
||||||
|
*::before
|
||||||
|
*::after
|
||||||
box-sizing border-box
|
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
|
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
|
// Georgia,Cambria,"Times New Roman",Times,serif
|
||||||
@ -25,9 +32,9 @@ a
|
|||||||
text-decoration none
|
text-decoration none
|
||||||
|
|
||||||
/* width */
|
/* width */
|
||||||
::-webkit-scrollbar {
|
::-webkit-scrollbar
|
||||||
width: 16px;
|
width 16px
|
||||||
}
|
|
||||||
|
|
||||||
/* Track */
|
/* Track */
|
||||||
::-webkit-scrollbar-corner
|
::-webkit-scrollbar-corner
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
/**
|
/**
|
||||||
* Copyright (c) 2021
|
|
||||||
*
|
|
||||||
* @summary DZEIO Component Library
|
* @summary DZEIO Component Library
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -18,6 +16,7 @@ import Image from './dzeio/Image'
|
|||||||
import Input from './dzeio/Input'
|
import Input from './dzeio/Input'
|
||||||
import Link from './dzeio/Link'
|
import Link from './dzeio/Link'
|
||||||
import Loader from './dzeio/Loader'
|
import Loader from './dzeio/Loader'
|
||||||
|
import Menu from './dzeio/Menu'
|
||||||
import Navbar from './dzeio/Navbar'
|
import Navbar from './dzeio/Navbar'
|
||||||
import NotificationManager from './dzeio/NotificationManager'
|
import NotificationManager from './dzeio/NotificationManager'
|
||||||
import Popup from './dzeio/Popup'
|
import Popup from './dzeio/Popup'
|
||||||
@ -39,6 +38,7 @@ export {
|
|||||||
Input,
|
Input,
|
||||||
Link,
|
Link,
|
||||||
Loader,
|
Loader,
|
||||||
|
Menu,
|
||||||
Navbar,
|
Navbar,
|
||||||
NotificationManager,
|
NotificationManager,
|
||||||
Popup,
|
Popup,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user