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/
storybook-static/
index.js
*.js
style.css
yarn-error.log
node_modules
types

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,61 +5,21 @@ import NextImage from 'next/image'
import css from './Image.module.styl'
export interface ImageProps {
defaultHeight?: number
src?: string
sources?: Array<string>
src: string
deleteOnError?: boolean
downgradeOnError?: string
canFullscreen?: boolean
max?: {
height?: number|string
width?: number|string
}
width?: number|string
default?: {
height?: number|string
width?: number|string
}
width: number
height: number
alt?: string
classes?: string
// ClassNames
parentClassName?: string
className?: string
// Events
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>
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 pic: React.RefObject<HTMLDivElement> = React.createRef()
private wasDowngraded = false
private cardPos: Array<number> = []
private cardSize: Array<number> = []
@ -104,30 +63,15 @@ export default class Image extends React.Component<ImageProps> {
public render() {
const pic = (
// <picture ref={this.pic} className={this.props.classes}>
// {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)}>
<div ref={this.pic} className={buildClassName(this.props.parentClassName, css.parent)}>
<NextImage
className={buildClassName([css.image], [this.props.className])}
// ref={this.ref}
src={this.props.src || ''}
onClick={this.props.canFullscreen && this.onClick || this.props.onClick}
onLoad={this.props.default && this.onLoad || undefined}
src={this.props.src}
onClick={this.props.canFullscreen ? this.onClick : this.props.onClick}
onError={this.props.deleteOnError && this.onError || undefined}
// layout="fill"
width={200}
height={44}
// style={{
// width: this.props.default?.width,
// height: this.props.default?.height,
// maxHeight: this.props.max?.height,
// maxWidth: this.props.max?.width
// }}
width={this.props.width}
height={this.props.height}
alt={this.props.alt}
/>
</div>
@ -191,8 +135,8 @@ export default class Image extends React.Component<ImageProps> {
return
}
const w = this.valToPixel(this.props.width)
const mh = this.valToPixel(this.props.max?.height)
const mw = this.valToPixel(this.props.max?.width)
const mh = this.valToPixel(this.props?.height)
const mw = this.valToPixel(this.props?.width)
c.classList.add(css.none)
i.style.height = ''
i.style.width = w

View File

@ -2,7 +2,12 @@
.parent
position relative
margin 16px
margin 16px 0
max-width 100%
display inline-block
+ .parent
margin-left 16px
label
font-size 1rem
@ -22,23 +27,104 @@
svg
position absolute
color #AAA
top 14px
left 16px // input padding-left
transition color $transition
pointer-events none
top 14px
&.left
left 16px // input padding-left
~ label
left 16px + 24px + 16px
~ label
left 16px + 24px + 16px
&.right
right 16px
select
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
select
textarea
padding 14px 16px
height 56px
border 2px solid rgba(black, .3)
border-radius 4px
max-width 100%
box-sizing border-box
font-size .875rem
outline none
@ -49,7 +135,7 @@
border-color rgba(white, .3)
color white
&:hover
&:not(:disabled):hover
border-color black
@media (prefers-color-scheme dark)
border-color white
@ -72,6 +158,16 @@
@media (prefers-color-scheme dark)
background #202020
&:disabled
border-color #999
@media (prefers-color-scheme dark)
border-color #444
~label
color #444
~ label
color #999
&:invalid
border-color $danger
@ -89,69 +185,20 @@
~ svg
color @border-color
&.primary
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
&.iconLeft
padding-left 16px + 24px + 16px
&.iconRight
padding-right 16 + 24 + 16px
&.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
@ -161,7 +208,8 @@
&:hover
background rgba(gray, .2)
@media (prefers-color-scheme dark)
background rgba(white, .2)
&.opaque
background darken(white, 5%)
@ -169,6 +217,7 @@
background #1c1c1c
&:focus
background rgba(gray, .3)
border-bottom 2px solid $default
&.opaque
background white
@ -185,14 +234,20 @@
background transparent
padding 0
font-size .75rem
~ svg ~ label
~ svg.left ~ label
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
display flex
justify-content space-between
padding 0 16px
font-size .9em
&.block input
&.block, &.block input, &.block select, &.block textarea
width 100%
display block

View File

@ -1,7 +1,8 @@
import React, { FC } from 'react'
import { ChevronDown } from 'react-feather'
import { IconProps, ColorType } from '../interfaces'
import Text from '../Text'
import { IconProps } from '../interfaces'
import { buildClassName } from '../Util'
import css from './Input.module.styl'
@ -9,34 +10,57 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
id?: string
label?: string
icon?: FC<IconProps>
iconRight?: FC<IconProps>
helper?: string
characterCount?: boolean
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' | 'select'
maxLength?: number | undefined
'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
// Custom Types
'select' | 'textarea'
autocomplete?: Array<string>
infinityText?: string
filled?: boolean
opaque?: boolean
block?: boolean
color?: ColorType
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() {
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() {
const props: Props = Object.assign({}, this.props)
const Icon = this.props.icon
delete props.label
delete props.children
delete props.icon
delete props.opaque
delete props.helper
@ -50,30 +74,30 @@ export default class Input extends React.Component<Props> {
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
placeholder: this.props.placeholder || ' ',
ref: this.props.inputRef,
ref: this.props.inputRef || this.inputRef,
className: buildClassName(
[css.hasIcon, Icon],
[css.iconLeft, this.props.icon],
[css.iconRight, this.props.iconRight],
[css.filled, this.props.filled],
[css.opaque, this.props.opaque],
[css[this.props.color as string], this.props.color]
[css.opaque, this.props.opaque]
),
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault()
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
}
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
if (this.props.type === 'number') {
baseProps.type = 'text'
baseProps.inputMode = 'numeric'
baseProps.pattern = '[0-9]*'
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
}
if (this.props.type === 'select') {
input = (
<select
ref={this.props.selectRef}
ref={this.props.selectRef || this.inputRef}
{...this.props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>}
className={buildClassName(
css.hasIcon,
[css.iconLeft, this.props.icon],
[css.iconRight, !this.props.disabled || this.props.iconRight],
[css.filled, this.props.filled],
[css[this.props.color as string], this.props.color]
)}
@ -81,6 +105,19 @@ export default class Input extends React.Component<Props> {
{this.props.children}
</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 {
input = (
<input
@ -96,42 +133,94 @@ export default class Input extends React.Component<Props> {
[css.parent],
[css.block, this.props.block]
)}
onChangeCapture={this.props.characterCount ? this.updatecharCount : undefined}
onChangeCapture={this.onChange}
ref={this.parentRef}
>
{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 && (
<label className={css.label} htmlFor={this.props.id}>{this.props.label}</label>
)}
{(this.props.helper || this.props.characterCount) && (
<div>
<span>{this.props.helper}</span>
<span ref={this.charCountRef}></span>
<Text type="span">{this.props.helper}</Text>
{this.props.characterCount && (
<Text type="span">{this.state?.charCount}</Text>
)}
</div>
)}
</div>
)
}
private updatecharCount = async (event?: React.FormEvent<HTMLDivElement>) => {
if (this.props.characterCount && this.charCountRef.current) {
private parentScroll = async () => {
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'
let currentCount = 0
const baseItem = this.props.value || this.props.defaultValue || ''
let currentCount = baseItem.toString().length
if (event) {
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.charCountRef.current.innerText = currentCount + ' / ' + max
this.setState({charCount: `${currentCount}/${max}`})
}
if (event) {
this.setState({value: (event.target as HTMLInputElement).value })
}
}

View File

@ -1,131 +1,48 @@
import React from 'react'
import { Bell, Grid } from 'react-feather'
import Router from 'next/router'
import Link from 'next/link'
import Row from '../Row'
import Col from '../Col'
import Button from '../Button'
import Image from '../Image'
import { buildClassName } from '../Util'
import Menu from '../Menu'
import css from './Navbar.module.styl'
interface States {
domain?: string
showUserMenu?: boolean
}
interface Props {
username?: string
userPic?: string
userMenu?: Array<{href: string, name: string}>
loginUrl?: string
registerUrl?: string
logoUrl?: string
logoLabel?: string
logo?: {
link?: string
label?: string
src: string
alt: string
alt?: string
}
}
export default class Navbar extends React.Component<Props, States> {
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)
}
export default class Navbar extends React.Component<Props> {
public render = () => (
<nav className={buildClassName([css.navbar], [css.small, this.state.domain])}>
<Row >
<nav className={css.navbar}>
<Row nomargin>
{this.props.logo && (
<Col>
<Row align="center">
<Link href={this.props.logoUrl || '/'}>
<a aria-label={this.props.logoLabel || 'Homepage'}>
<Link href={this.props.logo.link || '/'}>
<a aria-label={this.props.logo.label || 'Homepage'}>
<Image
alt={this.props.logo.alt}
src={this.props.logo.src}
max={{ height: 70-32 }}
height={38}
width={120}
/>
</a>
</Link>
</Row>
</Col>
)}
<Col>
<Row justify="flex-end" align="center">
{this.props.username ? (
<>
{/* <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>
</>
)}
{this.props.children}
</Row>
</Col>
</Row>
</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
position fixed
height 100%
@ -6,9 +8,21 @@
left 0
background rgba(black, .3)
cursor pointer
z-index 1000
.popupChild
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
cursor pointer
height 100%

View File

@ -1,8 +1,9 @@
import React from 'react'
import { X } from 'react-feather'
import { BoxWrapper, BoxHeader, BoxBody } from '../Box'
import { Props as HeaderProps } from '../Box/BoxHeader'
import Text from '../Text'
import Box from '../Box'
import Row from '../Row'
import { Props as HeaderProps } from '../Box/BoxHeader'
import css from './Popup.module.styl'
@ -15,20 +16,16 @@ interface Props {
export default class Popup extends React.Component<Props> {
public render = () => (
<Row onClick={this.parentClose} justify="center" align="center" className={css.popup}>
<BoxWrapper className={css.popupChild}>
<BoxHeader {...this.props.header}>
<X onClick={this.props.onClose} className={css.exit} />
</BoxHeader>
<BoxBody>
{this.props.children}
</BoxBody>
</BoxWrapper>
<Row nomargin onClick={this.parentClose} justify="center" align="center" className={css.popup}>
<Box {...this.props.header} className={css.popupChild} onClick={(ev) => ev.stopPropagation()} headerButtons={(<Text><X onClick={this.props.onClose} className={css.exit} /></Text>)}>
{this.props.children}
</Box>
</Row>
)
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()
}
}

View File

@ -24,7 +24,7 @@ export default class SidebarContainer extends React.Component<Props> {
<nav className={css.sidebar}>
<Link href="/dashboard">
<a>
<Image src="/assets/logo.svg" max={{ width: '100%' }} />
<Image src="/assets/logo.svg" width={175} height={100} />
</a>
</Link>
{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
text-align right
.align-left
text-align left
@media (prefers-color-scheme dark)
.white:not(.noDarkTheme)

View File

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

View File

@ -1,16 +1,32 @@
$default = #4285f4 // This color should never appear
$primary = #4285f4
$secondary = #FCFCFC
$info = #03A9F4
$success = #2DCE89
$danger = #F5365C
$warning = #FB6340
$main = #4285F4
$textOnMain = white
$infoDark = #304FFE
$infoLight = #29B6F6
$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
$transitionFunction = ease-in-out
$transition = $transitionTime $transitionFunction
$darkBackground = #161616
// Breakpoints
$mobile = 768px
@ -22,7 +38,17 @@ $colCount = 12
$colCountTablet = 8
$colCountMobile = 4
$gapSize = 16px //$totalGapSize / ($colCount+1)
$gapSize = 16px
rem($a)
($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 "config"
*
*::before
*::after
box-sizing border-box
html
font-family Aileron, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif
html
@ -13,11 +12,36 @@ body
body > div[id="__next"]
margin 0
min-height 100vh
background #f5f5f5
background $backgroundLight
@media (prefers-color-scheme dark)
background #202020
background $backgroundDark
a
color inherit
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'
export type ColorType = 'primary' | 'secondary' | 'info' | 'success' | 'danger' | 'warning'
export type ColorType = 'primary' | 'info' | 'success' | 'error' | 'warning'
export interface IconProps extends SVGAttributes<SVGElement> {
color?: string

View File

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

View File

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