Signed-off-by: Florian BOUILLON <florian.bouillon@delta-wings.net>
This commit is contained in:
Florian Bouillon 2021-02-23 14:06:55 +01:00
parent 2013f0bf41
commit d80fda2589
25 changed files with 654 additions and 404 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=lf

3
.gitignore vendored
View File

@ -1,6 +1,7 @@
module/ module/
storybook-static/ storybook-static/
index.js *.js
style.css style.css
yarn-error.log yarn-error.log
node_modules node_modules
types

View File

@ -1,6 +1,6 @@
{ {
"name": "@dzeio/components", "name": "@dzeio/components",
"version": "0.1.0", "version": "0.2.0",
"license": "MIT", "license": "MIT",
"main": "./index.js", "main": "./index.js",
"module": "./module/index.js", "module": "./module/index.js",
@ -44,7 +44,7 @@
"scripts": { "scripts": {
"storybook": "start-storybook -p 6006", "storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"build": "tsc || rollup --config", "build": "tsc && rollup --config",
"prepublishOnly": "yarn build" "prepublishOnly": "yarn build"
} }
} }

View File

@ -9,6 +9,7 @@ interface Props {
// Wrapper // Wrapper
outline?: boolean outline?: boolean
className?: string className?: string
onClick?: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void
// Header // Header
title?: string title?: string
@ -25,7 +26,7 @@ interface Props {
export default class Box extends React.Component<Props> { export default class Box extends React.Component<Props> {
public render = () => ( public render = () => (
<BoxWrapper outline={this.props.outline} className={this.props.className}> <BoxWrapper onClick={this.props.onClick} outline={this.props.outline} className={this.props.className}>
{(this.props.headerButtons || this.props.title || this.props.titleColSize || this.props.subtitle || this.props.delimiter || this.props.titleClassName) && ( {(this.props.headerButtons || this.props.title || this.props.titleColSize || this.props.subtitle || this.props.delimiter || this.props.titleClassName) && (
<BoxHeader <BoxHeader
title={this.props.title} title={this.props.title}
@ -37,7 +38,6 @@ export default class Box extends React.Component<Props> {
{this.props.headerButtons} {this.props.headerButtons}
</BoxHeader> </BoxHeader>
)} )}
<BoxHeader></BoxHeader>
<BoxBody noPadding={this.props.noPadding}> <BoxBody noPadding={this.props.noPadding}>
{this.props.children} {this.props.children}
</BoxBody> </BoxBody>

View File

@ -15,8 +15,8 @@
border-radius 4px border-radius 4px
border none border none
justify-content center justify-content center
color white color $textOnMain
background-color $default background-color $main
// Chrome Specific // Chrome Specific
outline none outline none
@ -68,22 +68,31 @@
margin-top 8px margin-top 8px
&:disabled &:disabled
background #E0E0E0 !important background $grayLight
color #B0B0B0 !important color $grayDark
transform none !important transform none
box-shadow none !important box-shadow none
cursor initial cursor initial
&.outline
border 2px solid @grayDark
background transparent
color @grayDark
@media (prefers-color-scheme dark)
border 2px solid @grayLight
color @grayLight
&.loading &.loading
color transparent !important color transparent
position relative position relative
pointer-events none pointer-events none
&::after &::after
content "" content ""
display block display block
border white 2px solid border $textOnMain 2px solid
border-color transparent transparent white white border-color transparent transparent $textOnMain $textOnMain
width 1em width 1em
position absolute position absolute
top calc(50% - (1em / 2)) top calc(50% - (1em / 2))
@ -93,105 +102,69 @@
box-sizing inherit box-sizing inherit
animation ButtonLoading 1s infinite linear animation ButtonLoading 1s infinite linear
.textInner .textInner
margin-left 8px margin-left 8px
.primary /**
background-color $primary * $color: the color to use
* $theme: the theme used ('lighten' | 'darken')
*/
btn($color, $theme)
background-color $color
// Get Text Color
$textColor = white
if $theme is 'darken'
$textColor = black
else
$textColor = white
color $textColor
&.outline &.outline
color @background-color color @background-color
border-color @background-color border-color @background-color
&:hover &:hover
background-color @background-color
&:active &:active
&:focus &:focus
background-color darken(@background-color, 30%)
&.loading::after
border-color transparent transparent @background-color @background-color
.secondary
background-color $secondary
color black
&.outline
color @color color @color
border-color @background-color
&:hover &:hover
background-color @background-color background-color @background-color
box-shadow 0 4px 4px rgba(@background-color,.2)
&:active &:active
&:focus &:focus
if $theme is 'darken'
background-color darken(@background-color, 30%) background-color darken(@background-color, 30%)
else
background-color lighten(@background-color, 30%)
&.loading::after &.loading
border-color transparent transparent @color @color color transparent
.info &::after
background-color $info border-color transparent transparent $textColor $textColor
&.outline
color @background-color
border-color @background-color
&:hover .info:not(:disabled)
background-color @background-color btn($infoLight, 'darken')
@media (prefers-color-scheme dark)
btn($infoDark, 'lighten')
&:active .success:not(:disabled)
&:focus btn($successLight, 'darken')
background-color darken(@background-color, 30%) @media (prefers-color-scheme dark)
btn($successDark, 'lighten')
&.loading::after .error:not(:disabled)
border-color transparent transparent @background-color @background-color btn($errorLight, 'darken')
.success @media (prefers-color-scheme dark)
background-color $success btn($errorDark, 'lighten')
&.outline
color @background-color
border-color @background-color
&:hover .warning:not(:disabled)
background-color @background-color btn($warningLight, 'darken')
@media (prefers-color-scheme dark)
&:active btn($warningDark, 'lighten')
&:focus
background-color darken(@background-color, 30%)
&.loading::after
border-color transparent transparent @background-color @background-color
.danger
background-color $danger
&.outline
color @background-color
border-color @background-color
&:hover
background-color @background-color
&:active
&:focus
background-color darken(@background-color, 30%)
&.loading::after
border-color transparent transparent @background-color @background-color
.warning
background-color $warning
&.outline
color @background-color
border-color @background-color
&:hover
background-color @background-color
&:active
&:focus
background-color darken(@background-color, 30%)
&.loading::after
border-color transparent transparent @background-color @background-color
@keyframes ButtonLoading @keyframes ButtonLoading
0% 0%

View File

@ -6,6 +6,8 @@ import Image from '../Image'
import css from './Button.module.styl' import css from './Button.module.styl'
// MAKE OUTLINE use Fieldset instead of the current one xd
interface Props { interface Props {
outline?: boolean outline?: boolean
nomargintop?: boolean nomargintop?: boolean
@ -31,7 +33,7 @@ export default class Button extends React.Component<Props> {
inner = ( inner = (
<> <>
{typeof Icon === 'string' ? ( {typeof Icon === 'string' ? (
<Image src={Icon} max={{ height: 16, width: 16 }} /> <Image src={Icon} width={16} height={16} />
) : ( ) : (
<Icon size={16} /> <Icon size={16} />
)} )}

View File

@ -17,7 +17,6 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
export default class Checkbox extends React.Component<Props> { export default class Checkbox extends React.Component<Props> {
public render() { public render() {
const props: Props = Object.assign({}, this.props) const props: Props = Object.assign({}, this.props)
delete props.label delete props.label

View File

@ -1,7 +1,7 @@
@import '../config.styl' @import '../config.styl'
.fieldset .fieldset
border-radius 8px border-radius 4px
border 2px solid grey border 2px solid grey
transition all $transition transition all $transition
margin 0 margin 0
@ -10,9 +10,10 @@
font-weight 600 font-weight 600
transition all $transition transition all $transition
padding 0 4px padding 0 4px
@media (prefers-color-scheme dark)
color white
&:hover &:hover
border-color black border-color black
> legend @media (prefers-color-scheme dark)
color $default border-color white

View File

@ -5,61 +5,21 @@ import NextImage from 'next/image'
import css from './Image.module.styl' import css from './Image.module.styl'
export interface ImageProps { export interface ImageProps {
defaultHeight?: number src: string
src?: string
sources?: Array<string>
deleteOnError?: boolean deleteOnError?: boolean
downgradeOnError?: string
canFullscreen?: boolean canFullscreen?: boolean
max?: { width: number
height?: number|string height: number
width?: number|string
}
width?: number|string
default?: {
height?: number|string
width?: number|string
}
alt?: string alt?: string
classes?: string
// ClassNames
parentClassName?: string
className?: string className?: string
// Events
onClick?: () => void onClick?: () => void
} }
enum images {
JPEG = 'image/jpeg',
XICON = 'image/x-icon',
TIFF = 'image/tiff'
}
const mimeTypes = {
apng: 'image/apng',
bmp: 'image/bmp',
gif: 'image/gif',
ico: images.XICON,
cur: images.XICON,
jpg: images.JPEG,
jpeg: images.JPEG,
jfif: images.JPEG,
pjpeg: images.JPEG,
pjp: images.JPEG,
png: 'image/png',
svg: 'image/svg+xml',
tif: images.TIFF,
tiff: images.TIFF,
webp: 'image/webp'
}
const getMimeType = (img: string) => {
const arr = img.split('.')
return mimeTypes[arr[arr.length-1] as 'apng'] || mimeTypes.png
}
type evType<T = HTMLImageElement> = React.SyntheticEvent<T, Event> type evType<T = HTMLImageElement> = React.SyntheticEvent<T, Event>
export default class Image extends React.Component<ImageProps> { export default class Image extends React.Component<ImageProps> {
@ -69,7 +29,6 @@ export default class Image extends React.Component<ImageProps> {
private parent: React.RefObject<HTMLDivElement> = React.createRef() private parent: React.RefObject<HTMLDivElement> = React.createRef()
private pic: React.RefObject<HTMLDivElement> = React.createRef() private pic: React.RefObject<HTMLDivElement> = React.createRef()
private wasDowngraded = false
private cardPos: Array<number> = [] private cardPos: Array<number> = []
private cardSize: Array<number> = [] private cardSize: Array<number> = []
@ -104,30 +63,15 @@ export default class Image extends React.Component<ImageProps> {
public render() { public render() {
const pic = ( const pic = (
// <picture ref={this.pic} className={this.props.classes}> <div ref={this.pic} className={buildClassName(this.props.parentClassName, css.parent)}>
// {this.props.sources && this.props.sources.map((el, index) => (
// <source key={index} srcSet={el} type={getMimeType(el)}/>
// ))}
// <img
// />
// </picture>
<div ref={this.pic} className={buildClassName(this.props.classes, css.parent)}>
<NextImage <NextImage
className={buildClassName([css.image], [this.props.className])} className={buildClassName([css.image], [this.props.className])}
// ref={this.ref} src={this.props.src}
src={this.props.src || ''} onClick={this.props.canFullscreen ? this.onClick : this.props.onClick}
onClick={this.props.canFullscreen && this.onClick || this.props.onClick}
onLoad={this.props.default && this.onLoad || undefined}
onError={this.props.deleteOnError && this.onError || undefined} onError={this.props.deleteOnError && this.onError || undefined}
// layout="fill" // layout="fill"
width={200} width={this.props.width}
height={44} height={this.props.height}
// style={{
// width: this.props.default?.width,
// height: this.props.default?.height,
// maxHeight: this.props.max?.height,
// maxWidth: this.props.max?.width
// }}
alt={this.props.alt} alt={this.props.alt}
/> />
</div> </div>
@ -191,8 +135,8 @@ export default class Image extends React.Component<ImageProps> {
return return
} }
const w = this.valToPixel(this.props.width) const w = this.valToPixel(this.props.width)
const mh = this.valToPixel(this.props.max?.height) const mh = this.valToPixel(this.props?.height)
const mw = this.valToPixel(this.props.max?.width) const mw = this.valToPixel(this.props?.width)
c.classList.add(css.none) c.classList.add(css.none)
i.style.height = '' i.style.height = ''
i.style.width = w i.style.width = w

View File

@ -2,7 +2,12 @@
.parent .parent
position relative position relative
margin 16px margin 16px 0
max-width 100%
display inline-block
+ .parent
margin-left 16px
label label
font-size 1rem font-size 1rem
@ -22,23 +27,104 @@
svg svg
position absolute position absolute
color #AAA color #AAA
top 14px
left 16px // input padding-left
transition color $transition transition color $transition
pointer-events none pointer-events none
top 14px
&.left
left 16px // input padding-left
~ label ~ label
left 16px + 24px + 16px left 16px + 24px + 16px
&.right
right 16px
select select
appearance none appearance none
option
background $foregroundLight
color black
@media (prefers-color-scheme dark)
background lighten($foregroundDark, 5%)
color white
textarea
resize none
overflow-y hidden
/* Remove the arrows from the Number Input */
input[type="number"]
-moz-appearance textfield
input::-webkit-outer-spin-button
input::-webkit-inner-spin-button
-webkit-appearance none
margin 0
/* End */
.autocomplete
display flex
opacity 0
transition all $transition
pointer-events none
// display flex
flex-direction column
list-style none
position absolute
top calc(100% - 4px)
left 0
width 100%
z-index 100
box-shadow 0 4px 8px rgba(black, .3)
margin 0
padding 0
background darken($foregroundLight, 5%)
@media (prefers-color-scheme dark)
background lighten($foregroundDark, 5%)
border-bottom-left-radius 4px
border-bottom-right-radius 4px
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%)
input:focus + .autocomplete
select:focus + .autocomplete
textarea:focus + .autocomplete
.autocomplete:hover
opacity 1
pointer-events inherit
input input
select select
textarea
padding 14px 16px padding 14px 16px
height 56px height 56px
border 2px solid rgba(black, .3) border 2px solid rgba(black, .3)
border-radius 4px border-radius 4px
max-width 100%
box-sizing border-box box-sizing border-box
font-size .875rem font-size .875rem
outline none outline none
@ -49,7 +135,7 @@
border-color rgba(white, .3) border-color rgba(white, .3)
color white color white
&:hover &:not(:disabled):hover
border-color black border-color black
@media (prefers-color-scheme dark) @media (prefers-color-scheme dark)
border-color white border-color white
@ -72,6 +158,16 @@
@media (prefers-color-scheme dark) @media (prefers-color-scheme dark)
background #202020 background #202020
&:disabled
border-color #999
@media (prefers-color-scheme dark)
border-color #444
~label
color #444
~ label
color #999
&:invalid &:invalid
border-color $danger border-color $danger
@ -89,69 +185,20 @@
~ svg ~ svg
color @border-color color @border-color
&.primary &.iconLeft
border-color $primary
~ label
color @border-color
~ svg
color @border-color
&.secondary
border-color $secondary
~ label
color @border-color
~ svg
color @border-color
&.info
border-color $info
~ label
color @border-color
~ svg
color @border-color
&.success
border-color $success
~ label
color @border-color
~ svg
color @border-color
&.danger
border-color $danger
~ label
color @border-color
~ svg
color @border-color
&.warning
border-color $warning
~ label
color @border-color
~ svg
color @border-color
&.hasIcon
padding-left 16px + 24px + 16px padding-left 16px + 24px + 16px
&.iconRight
padding-right 16 + 24 + 16px
&.filled &.filled
border none border none
background rgba(gray, .1) background rgba(gray, .1)
border-radius @border-radius @border-radius 0 0 border-radius @border-radius @border-radius 0 0
border-bottom 2px solid rgba(black,.4) border-bottom 2px solid rgba(black,.4)
@media (prefers-color-scheme dark)
background rgba(white, .1)
border-bottom 2px solid rgba(white,.4)
&.opaque &.opaque
background white background white
@ -161,7 +208,8 @@
&:hover &:hover
background rgba(gray, .2) background rgba(gray, .2)
@media (prefers-color-scheme dark)
background rgba(white, .2)
&.opaque &.opaque
background darken(white, 5%) background darken(white, 5%)
@ -169,6 +217,7 @@
background #1c1c1c background #1c1c1c
&:focus &:focus
background rgba(gray, .3) background rgba(gray, .3)
border-bottom 2px solid $default
&.opaque &.opaque
background white background white
@ -185,14 +234,20 @@
background transparent background transparent
padding 0 padding 0
font-size .75rem font-size .75rem
~ svg ~ label ~ svg.left ~ label
left 16px + 24px + 16px // .input/padding-left label/padding-left left 16px + 24px + 16px // .input/padding-left label/padding-left
~ svg.rotate
transform rotateX(0)
transition $transition
&:hover:focus ~ svg.rotate, ~.autocomplete:hover ~ svg.rotate
transform rotateX(180deg)
div div
display flex display flex
justify-content space-between justify-content space-between
padding 0 16px padding 0 16px
font-size .9em font-size .9em
&.block input &.block, &.block input, &.block select, &.block textarea
width 100% width 100%
display block

View File

@ -1,7 +1,8 @@
import React, { FC } from 'react' import React, { FC } from 'react'
import { ChevronDown } from 'react-feather' import { ChevronDown } from 'react-feather'
import { IconProps, ColorType } from '../interfaces' import Text from '../Text'
import { IconProps } from '../interfaces'
import { buildClassName } from '../Util' import { buildClassName } from '../Util'
import css from './Input.module.styl' import css from './Input.module.styl'
@ -9,34 +10,57 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
id?: string id?: string
label?: string label?: string
icon?: FC<IconProps> icon?: FC<IconProps>
iconRight?: FC<IconProps>
helper?: string helper?: string
characterCount?: boolean characterCount?: boolean
inputRef?: React.RefObject<HTMLInputElement> inputRef?: React.RefObject<HTMLInputElement>
selectRef?: React.RefObject<HTMLSelectElement> 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' | 'select' 'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
maxLength?: number | undefined // Custom Types
'select' | 'textarea'
autocomplete?: Array<string>
infinityText?: string infinityText?: string
filled?: boolean filled?: boolean
opaque?: boolean opaque?: boolean
block?: boolean block?: boolean
color?: ColorType
children?: React.ReactNode children?: React.ReactNode
} }
export default class Input extends React.Component<Props> { interface States {
charCount?: string
textAreaHeight?: number
value?: string
isInFirstPartOfScreen?: boolean
}
private charCountRef: React.RefObject<HTMLSpanElement> = React.createRef() export default class Input extends React.Component<Props, States> {
// any because f*ck types
private inputRef: React.RefObject<any> = React.createRef()
private parentRef: React.RefObject<HTMLDivElement> = React.createRef()
public componentDidMount() { public componentDidMount() {
this.updatecharCount() if (this.props.characterCount) {
this.onChange()
}
if (this.props.type === 'textarea') {
this.textareaHandler()
}
if (this.props.autocomplete) {
window.addEventListener('scroll', this.parentScroll)
}
}
public componentWillUnmount() {
window.removeEventListener('scroll', this.parentScroll)
} }
public render() { public render() {
const props: Props = Object.assign({}, this.props) const props: Props = Object.assign({}, this.props)
const Icon = this.props.icon
delete props.label delete props.label
delete props.children
delete props.icon delete props.icon
delete props.opaque delete props.opaque
delete props.helper delete props.helper
@ -50,30 +74,30 @@ export default class Input extends React.Component<Props> {
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = { const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
placeholder: this.props.placeholder || ' ', placeholder: this.props.placeholder || ' ',
ref: this.props.inputRef, ref: this.props.inputRef || this.inputRef,
className: buildClassName( className: buildClassName(
[css.hasIcon, Icon], [css.iconLeft, this.props.icon],
[css.iconRight, this.props.iconRight],
[css.filled, this.props.filled], [css.filled, this.props.filled],
[css.opaque, this.props.opaque], [css.opaque, this.props.opaque]
[css[this.props.color as string], this.props.color]
), ),
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') { if (this.props.type === 'number') {
baseProps.type = 'text' baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
baseProps.inputMode = 'numeric'
baseProps.pattern = '[0-9]*'
} }
if (this.props.type === 'select') { if (this.props.type === 'select') {
input = ( input = (
<select <select
ref={this.props.selectRef} ref={this.props.selectRef || this.inputRef}
{...this.props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>}
className={buildClassName( className={buildClassName(
css.hasIcon, [css.iconLeft, this.props.icon],
[css.iconRight, !this.props.disabled || this.props.iconRight],
[css.filled, this.props.filled], [css.filled, this.props.filled],
[css[this.props.color as string], this.props.color] [css[this.props.color as string], this.props.color]
)} )}
@ -81,6 +105,19 @@ export default class Input extends React.Component<Props> {
{this.props.children} {this.props.children}
</select> </select>
) )
} 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 { } else {
input = ( input = (
<input <input
@ -96,42 +133,94 @@ export default class Input extends React.Component<Props> {
[css.parent], [css.parent],
[css.block, this.props.block] [css.block, this.props.block]
)} )}
onChangeCapture={this.props.characterCount ? this.updatecharCount : undefined} onChangeCapture={this.onChange}
ref={this.parentRef}
> >
{input} {input}
{this.props.type === 'select' && (
<ChevronDown /> {this.props.autocomplete && this.props.autocomplete.indexOf(this.state?.value || '') === -1 && (
<ul className={buildClassName(css.autocomplete, [css.reverse, !this.state?.isInFirstPartOfScreen])}>
{this.props.autocomplete.filter((item) => item.includes(this.state?.value || '')).map((item) => (<li key={item} onClick={this.onAutoCompleteClick(item)}><Text>{item}</Text></li>))}
</ul>
)} )}
{Icon && (
<Icon /> {/* Process Icon */}
{this.props.icon && (
<this.props.icon className={css.left} />
)} )}
{this.props.iconRight ? (
<this.props.iconRight className={css.right} />
) : ((this.props.type === 'select' || this.props.autocomplete) && !this.props.disabled) && (
<ChevronDown className={buildClassName(css.right, css.rotate)} />
)}
{/* Input Label */}
{this.props.label && ( {this.props.label && (
<label className={css.label} htmlFor={this.props.id}>{this.props.label}</label> <label className={css.label} htmlFor={this.props.id}>{this.props.label}</label>
)} )}
{(this.props.helper || this.props.characterCount) && ( {(this.props.helper || this.props.characterCount) && (
<div> <div>
<span>{this.props.helper}</span> <Text type="span">{this.props.helper}</Text>
<span ref={this.charCountRef}></span> {this.props.characterCount && (
<Text type="span">{this.state?.charCount}</Text>
)}
</div> </div>
)} )}
</div> </div>
) )
} }
private updatecharCount = async (event?: React.FormEvent<HTMLDivElement>) => { private parentScroll = async () => {
if (this.props.characterCount && this.charCountRef.current) { const div = this.parentRef.current
if (!div) {return}
const result = !(div.offsetTop - window.scrollY >= window.innerHeight / 2)
if (this.state.isInFirstPartOfScreen !== result) {
this.setState({isInFirstPartOfScreen: result})
}
}
private getElement(): undefined | HTMLInputElement {
const item = this.props.inputRef || this.props.selectRef || this.inputRef
if (!item || !item.current) {return}
return item.current
}
private textareaHandler = async () =>
this.setState({textAreaHeight: undefined}, () => {
if (!this.inputRef.current) {return}
this.setState({textAreaHeight: this.inputRef.current.scrollHeight})
})
private onAutoCompleteClick = (value: string) => () => {
console.log('test')
const item = this.getElement()
if (!item) {return}
const valueSetter = Object.getOwnPropertyDescriptor(item, 'value')?.set
const prototype = Object.getPrototypeOf(item)
const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set
if (valueSetter && valueSetter !== prototypeValueSetter) {
// @ts-expect-error IDK why
prototypeValueSetter.call(item, value)
} else {
// @ts-expect-error IDK why
valueSetter.call(item, value)
}
item.dispatchEvent(new Event('input', {bubbles: true}))
}
private onChange = async (event?: React.FormEvent<HTMLDivElement>) => {
if (this.props.characterCount) {
const max = this.props.maxLength || this.props.infinityText || 'Infinity' const max = this.props.maxLength || this.props.infinityText || 'Infinity'
let currentCount = 0 const baseItem = this.props.value || this.props.defaultValue || ''
let currentCount = baseItem.toString().length
if (event) { if (event) {
currentCount = (event.target as HTMLInputElement).value.length currentCount = (event.target as HTMLInputElement).value.length
} else {
if (this.props.defaultValue) {
currentCount = this.props.defaultValue.toString().length
} else if (this.props.value) {
currentCount = this.props.value.toString().length
} }
this.setState({charCount: `${currentCount}/${max}`})
} }
this.charCountRef.current.innerText = currentCount + ' / ' + max if (event) {
this.setState({value: (event.target as HTMLInputElement).value })
} }
} }

View File

@ -1,131 +1,48 @@
import React from 'react' import React from 'react'
import { Bell, Grid } from 'react-feather'
import Router from 'next/router'
import Link from 'next/link' import Link from 'next/link'
import Row from '../Row' import Row from '../Row'
import Col from '../Col' import Col from '../Col'
import Button from '../Button'
import Image from '../Image' import Image from '../Image'
import { buildClassName } from '../Util'
import Menu from '../Menu'
import css from './Navbar.module.styl' import css from './Navbar.module.styl'
interface States {
domain?: string
showUserMenu?: boolean
}
interface Props { interface Props {
username?: string
userPic?: string
userMenu?: Array<{href: string, name: string}>
loginUrl?: string
registerUrl?: string
logoUrl?: string
logoLabel?: string
logo?: { logo?: {
link?: string
label?: string
src: string src: string
alt: string alt?: string
} }
} }
export default class Navbar extends React.Component<Props, States> { export default class Navbar extends React.Component<Props> {
public state: States = {}
public constructor(props: Props) {
super(props)
}
public componentDidMount() {
this.setState({
domain: Router.query.domain as string
})
Router.events.on('routeChangeComplete', () => {
this.setState({
domain: Router.query.domain as string | undefined
})
})
document.body.addEventListener('click', this.onClickAnywhere)
}
public componentWillUnmount() {
document.body.removeEventListener('click', this.onClickAnywhere)
}
public render = () => ( public render = () => (
<nav className={buildClassName([css.navbar], [css.small, this.state.domain])}> <nav className={css.navbar}>
<Row > <Row nomargin>
{this.props.logo && ( {this.props.logo && (
<Col> <Col>
<Row align="center"> <Row align="center">
<Link href={this.props.logoUrl || '/'}> <Link href={this.props.logo.link || '/'}>
<a aria-label={this.props.logoLabel || 'Homepage'}> <a aria-label={this.props.logo.label || 'Homepage'}>
<Image <Image
alt={this.props.logo.alt} alt={this.props.logo.alt}
src={this.props.logo.src} src={this.props.logo.src}
max={{ height: 70-32 }} height={38}
width={120}
/> />
</a> </a>
</Link> </Link>
</Row> </Row>
</Col> </Col>
)} )}
<Col> <Col>
<Row justify="flex-end" align="center"> <Row justify="flex-end" align="center">
{this.props.username ? ( {this.props.children}
<>
{/* <Bell className={css.icon} />
<Grid className={css.icon} /> */}
{this.props.userPic && (
<Image
onClick={this.onMenuClick}
alt="User Profile Picture"
classes={buildClassName([css.favicon], [css.userIcon])}
src={this.props.userPic}
/>
)}
<p
onClick={this.props.userMenu ? this.onMenuClick : undefined}
className={css.text}
>
{this.props.username}
</p>
{this.props.userMenu && (
<Menu
show={this.state.showUserMenu}
pos={{ right: 16, top: 86 }}
content={this.props.userMenu}
/>
)}
</>
) : (
<>
<Button nomargintop href={this.props.loginUrl}>Login</Button>
<Button nomargintop color="secondary" href={this.props.registerUrl}>Register</Button>
</>
)}
</Row> </Row>
</Col> </Col>
</Row> </Row>
</nav> </nav>
) )
private onClickAnywhere = () => {
this.setState({
showUserMenu: false
})
}
private onMenuClick = () => {
const newMenuState = !this.state.showUserMenu
this.setState({
showUserMenu: newMenuState
})
}
} }

View File

@ -1,3 +1,5 @@
@import "../config.styl"
.popup .popup
position fixed position fixed
height 100% height 100%
@ -6,9 +8,21 @@
left 0 left 0
background rgba(black, .3) background rgba(black, .3)
cursor pointer cursor pointer
z-index 1000
.popupChild .popupChild
cursor initial cursor initial
z-index 1001
min-width 50%
@media (max-width $tablet)
min-width 70%
@media (max-width $mobile)
min-width 90%
// min-height 50vh
.exit .exit
cursor pointer cursor pointer
height 100%

View File

@ -1,8 +1,9 @@
import React from 'react' import React from 'react'
import { X } from 'react-feather' import { X } from 'react-feather'
import { BoxWrapper, BoxHeader, BoxBody } from '../Box' import Text from '../Text'
import { Props as HeaderProps } from '../Box/BoxHeader' import Box from '../Box'
import Row from '../Row' import Row from '../Row'
import { Props as HeaderProps } from '../Box/BoxHeader'
import css from './Popup.module.styl' import css from './Popup.module.styl'
@ -15,20 +16,16 @@ interface Props {
export default class Popup extends React.Component<Props> { export default class Popup extends React.Component<Props> {
public render = () => ( public render = () => (
<Row onClick={this.parentClose} justify="center" align="center" className={css.popup}> <Row nomargin onClick={this.parentClose} justify="center" align="center" className={css.popup}>
<BoxWrapper className={css.popupChild}> <Box {...this.props.header} className={css.popupChild} onClick={(ev) => ev.stopPropagation()} headerButtons={(<Text><X onClick={this.props.onClose} className={css.exit} /></Text>)}>
<BoxHeader {...this.props.header}>
<X onClick={this.props.onClose} className={css.exit} />
</BoxHeader>
<BoxBody>
{this.props.children} {this.props.children}
</BoxBody> </Box>
</BoxWrapper>
</Row> </Row>
) )
private parentClose = (ev: React.MouseEvent<HTMLDivElement>) => { private parentClose = (ev: React.MouseEvent<HTMLDivElement>) => {
if ((ev.target as HTMLElement).classList.contains(css.popup) && this.props.onClose) { const target = ev.currentTarget
if (target.classList.contains(css.popup) && this.props.onClose) {
this.props.onClose() this.props.onClose()
} }
} }

View File

@ -24,7 +24,7 @@ export default class SidebarContainer extends React.Component<Props> {
<nav className={css.sidebar}> <nav className={css.sidebar}>
<Link href="/dashboard"> <Link href="/dashboard">
<a> <a>
<Image src="/assets/logo.svg" max={{ width: '100%' }} /> <Image src="/assets/logo.svg" width={175} height={100} />
</a> </a>
</Link> </Link>
{this.menu.map((item, index) => ( {this.menu.map((item, index) => (

View File

@ -0,0 +1,139 @@
@import '../config'
.tag
padding 8px 12px
border-radius 8px
margin-left 8px
height 32px
line-height 1
display inline-block
color white
background $default
outline none
&:hover
background darken($default, 10%)
&:focus
background darken($default, 20%)
&.outline
border 2px solid $default
padding 6px 10px
background transparent
&:hover
background rgba($default, .5)
&:focus
background rgba($default, .7)
.primary
$color = $primary
color white
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color black
border 2px solid $color
background transparent
&:hover
color white
background rgba($color, .5)
&:focus
color white
background rgba($color, .7)
.secondary
$color = $secondary
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color white
border 2px solid $color
background transparent
&:hover
color black
background rgba($color, .5)
&:focus
color black
background rgba($color, .7)
.info
$color = $info
color white
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color black
border 2px solid $color
background transparent
&:hover
color white
background rgba($color, .5)
&:focus
color white
background rgba($color, .7)
.success
$color = $success
color white
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color black
border 2px solid $color
background transparent
&:hover
color white
background rgba($color, .5)
&:focus
color white
background rgba($color, .7)
.danger
$color = $danger
color white
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color black
border 2px solid $color
background transparent
&:hover
color white
background rgba($color, .5)
&:focus
color white
background rgba($color, .7)
.warning
$color = $warning
color white
background $color
&:hover
background lighten($color, 30%)
&:focus
background lighten($color, 15%)
&.outline
color black
border 2px solid $color
background transparent
&:hover
color white
background rgba($color, .5)
&:focus
color white
background rgba($color, .7)

View File

@ -0,0 +1,17 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Tag',
component: Component
} as Meta
export const Basic = (args: any) => {
const content = args.content
delete args.content
return (
<Component {...args}>{content}</Component>
)
}

42
src/dzeio/Tag/index.tsx Normal file
View File

@ -0,0 +1,42 @@
import React from 'react'
import { ColorType } from '../interfaces'
import { buildClassName } from '../Util'
import Link from '../Link'
import css from './Tag.module.styl'
import Text from '../Text'
interface Props {
text: string
color?: ColorType
href?: string
outline?: boolean
}
export default class Tag extends React.Component<Props> {
public render() {
const classes = buildClassName(
css.tag,
[css[this.props.color as string], this.props.color],
[css.outline, this.props.outline]
)
if (!this.props.href) {
return (
<Text
className={classes}
>{this.props.text}</Text>
)
}
return (
<Link
href={this.props.href}
className={classes}
>
{this.props.text}
</Link>
)
}
}

View File

@ -13,6 +13,8 @@
.align-right .align-right
text-align right text-align right
.align-left
text-align left
@media (prefers-color-scheme dark) @media (prefers-color-scheme dark)
.white:not(.noDarkTheme) .white:not(.noDarkTheme)

View File

@ -3,10 +3,10 @@ import { buildClassName } from '../Util'
import css from './Text.module.styl' import css from './Text.module.styl'
interface Props { interface Props {
color?: 'black' | 'white' color?: 'black' | 'white'
type?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'em' type?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'em' | 'span'
className?: string className?: string
noDarkTheme?: boolean noDarkTheme?: boolean
align?: 'right' | 'center' align?: 'left' | 'right' | 'center'
children: React.ReactNode children: React.ReactNode
} }
@ -31,6 +31,7 @@ export default class Text extends React.Component<Props> {
case 'h5': return (<h5 className={classes}>{this.props.children}</h5>) case 'h5': return (<h5 className={classes}>{this.props.children}</h5>)
case 'h6': return (<h6 className={classes}>{this.props.children}</h6>) case 'h6': return (<h6 className={classes}>{this.props.children}</h6>)
case 'em': return (<p className={classes}><em>{this.props.children}</em></p>) case 'em': return (<p className={classes}><em>{this.props.children}</em></p>)
case 'span': return (<span className={classes}>{this.props.children}</span>)
default: return (<p className={classes}>{this.props.children}</p>) default: return (<p className={classes}>{this.props.children}</p>)
} }
} }

View File

@ -1,16 +1,32 @@
$default = #4285f4 // This color should never appear
$primary = #4285f4 $main = #4285F4
$secondary = #FCFCFC $textOnMain = white
$info = #03A9F4
$success = #2DCE89 $infoDark = #304FFE
$danger = #F5365C $infoLight = #29B6F6
$warning = #FB6340
$successDark = #388E3C
$successLight = #4CAF50
$errorDark = #D32F2F
$errorLight = #F44336
$warningDark = #F57C00
$warningLight = #FF9800
$backgroundDark = #161616
$backgroundLight = #EEEEEE
$foregroundDark = #202020
$foregroundLight = #FFFFFF
$grayDark = #B0B0B0
$grayLight = #E0E0E0
$transitionTime = .15s $transitionTime = .15s
$transitionFunction = ease-in-out $transitionFunction = ease-in-out
$transition = $transitionTime $transitionFunction $transition = $transitionTime $transitionFunction
$darkBackground = #161616
// Breakpoints // Breakpoints
$mobile = 768px $mobile = 768px
@ -22,7 +38,17 @@ $colCount = 12
$colCountTablet = 8 $colCountTablet = 8
$colCountMobile = 4 $colCountMobile = 4
$gapSize = 16px //$totalGapSize / ($colCount+1) $gapSize = 16px
rem($a) rem($a)
($a / 16)rem ($a / 16)rem
// @deprecated colors
$primary = $main
$default = $main
$secondary = $main
$info = $infoLight
$success = $successLight
$danger = $errorLight
$warning = $warningLight
$darkBackground = $backgroundDark

View File

@ -1,11 +1,10 @@
@import "_aileron" @import "_aileron"
@import "config"
* *
*::before *::before
*::after *::after
box-sizing border-box box-sizing border-box
html
font-family Aileron, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif font-family Aileron, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
html html
@ -13,11 +12,36 @@ body
body > div[id="__next"] body > div[id="__next"]
margin 0 margin 0
min-height 100vh min-height 100vh
background #f5f5f5 background $backgroundLight
@media (prefers-color-scheme dark) @media (prefers-color-scheme dark)
background #202020 background $backgroundDark
a a
color inherit color inherit
text-decoration none text-decoration none
/* width */
::-webkit-scrollbar {
width: 16px;
}
/* Track */
::-webkit-scrollbar-track
background $foregroundLight
@media (prefers-color-scheme dark)
background $foregroundDark
::-webkit-scrollbar-thumb
background: darken($foregroundLight, 16%)
@media (prefers-color-scheme dark)
background lighten($foregroundDark, 16%)
&:hover
background: darken($foregroundLight, 24%)
@media (prefers-color-scheme dark)
background: lighten($foregroundDark, 24%)
&:active
background: darken($foregroundLight, 32%)
@media (prefers-color-scheme dark)
background: lighten($foregroundDark, 32%)

View File

@ -1,6 +1,6 @@
import { SVGAttributes } from 'react' import { SVGAttributes } from 'react'
export type ColorType = 'primary' | 'secondary' | 'info' | 'success' | 'danger' | 'warning' export type ColorType = 'primary' | 'info' | 'success' | 'error' | 'warning'
export interface IconProps extends SVGAttributes<SVGElement> { export interface IconProps extends SVGAttributes<SVGElement> {
color?: string color?: string

View File

@ -1,11 +1,15 @@
.fullscrean @import './config'
.fullscreen
min-height 100vh min-height 100vh
.fullHeight .w100p
height 50vh width 100%
.hideMobile
@media (max-width $mobile)
display none
.hideTablet
// Utils for Width @media (max-width $tablet)
.w50p display none
width 50%

View File

@ -18,6 +18,7 @@ import Popup from './dzeio/Popup'
import Row from './dzeio/Row' import Row from './dzeio/Row'
import SidebarContainer from './dzeio/SidebarContainer' import SidebarContainer from './dzeio/SidebarContainer'
import Table from './dzeio/Table' import Table from './dzeio/Table'
import Tag from './dzeio/Tag'
import Text from './dzeio/Text' import Text from './dzeio/Text'
import * as Util from './dzeio/Util' import * as Util from './dzeio/Util'
@ -47,6 +48,7 @@ export {
Row, Row,
SidebarContainer, SidebarContainer,
Table, Table,
Tag,
Text, Text,
Util Util
} }