Signed-off-by: Avior <github@avior.me>
This commit is contained in:
Florian Bouillon 2021-10-06 17:57:59 +02:00
parent bb001148a5
commit 8d7a8c70f0
Signed by: Florian Bouillon
GPG Key ID: BEEAF3722D0EBF64
73 changed files with 1508 additions and 6817 deletions

View File

@ -3,7 +3,7 @@ const webpack = require('webpack')
module.exports = {
"stories": [
"../src/dzeio/**/*.stories.tsx",
"../src/**/*.stories.tsx",
],
core: {
builder: "webpack5"

View File

@ -1,3 +1,4 @@
import React from 'react'
// https://stackoverflow.com/a/64765638/7335674
import * as nextImage from 'next/image'

View File

@ -1,8 +1,9 @@
import Router from 'next/router';
import Router from 'next/router'
Router.router = {
push: async () => {},
replace: async () => {},
prefetch: () => {},
route: '/mock-route',
pathname: 'mock-path',};
pathname: 'mock-path',
}

View File

@ -1,4 +1,4 @@
import '../src/dzeio/general.styl'
import '../src/general.styl'
import './mockNextRouter'
import './mockNextImage'

7724
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -8,11 +8,11 @@
"@babel/core": "^7.12.16",
"@babel/preset-env": "^7.12.16",
"@babel/preset-react": "^7.12.13",
"@storybook/addon-essentials": "^6.1.14",
"@storybook/builder-webpack5": "^6.3.8",
"@storybook/cli": "^6.1.14",
"@storybook/manager-webpack5": "^6.3.8",
"@storybook/react": "^6.1.14",
"@storybook/addon-essentials": "^6.3.9",
"@storybook/builder-webpack5": "^6.3.9",
"@storybook/cli": "^6.3.9",
"@storybook/manager-webpack5": "^6.3.9",
"@storybook/react": "^6.3.9",
"@types/node": "^16.10.2",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",

View File

@ -27,6 +27,11 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
// Custom Types
'textarea'
choices?: Array<string | {display: string, value: string}>
/**
* Always display every choices
*/
displayAllOptions?: boolean
}
interface States {
@ -179,7 +184,7 @@ export default class Input extends React.PureComponent<Props, States> {
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)
(item) => this.props.displayAllOptions || !v || item.item.display.toLowerCase().includes(v) || item.item.display.toLowerCase().toLowerCase().includes(v)
)
.map((item) => ({display: item.item.display, value: item.index}))
}

View File

@ -22,6 +22,7 @@
margin-bottom 8px
color $darkGrayLight
cursor pointer
transition all $transition
font-weight bold
border-radius 8px
&:hover

View File

@ -0,0 +1,177 @@
@import '../config'
// $transition = 10s linear
// $transitionTime = 10s
// $transitionFunction = linear
.sidebarBody
margin-left 300px
transition margin-left $transition
&.short
margin-left 56px
.sidebar
background $foregroundLight
@media (prefers-color-scheme dark)
background $foregroundDark
position fixed
left 0
top 0
padding 24px
height 100vh
width 300px
z-index 100
display flex
flex-direction column
transition width $transition
.header
.userSpace
.header .imgContainer
> ul span
// transition all $transition
transition-property width, padding, margin, max-width
transition-duration $transitionTime
transition-timing-function $transitionFunction
overflow hidden
> ul span
width calc(100% - 40px)
max-width 100%
.header p
cursor pointer
.userSpace
> div
transition all $transition
p
overflow hidden
white-space nowrap
svg
cursor pointer
&.short
width 88px
.userSpace > div:first-child
.header > div:first-child
width 0
max-width 0
.header
margin-left -16px
> div:first-child
padding-left 0 !important
.header > div svg
.userSpace svg
margin 0 8px
.header .imgContainer
> ul
span
svg:last-child
width 0
padding-left 0
padding-right 0
margin 0 !important
max-width 0
.header
margin-left -8px
//min-height 70px
svg
margin-right 8px
hr
margin 0
ul ul
padding-left 40px
max-height 0
opacity 0
overflow hidden
transition all $transition
position relative
&::before
content " "
position absolute
background $darkGrayLight
@media (prefers-color-theme dark)
background $lightGrayDark
border-radius 2px
width 2px
height 100%
margin-top 8px
left 18px
li.activeMenu
svg:last-child
transform rotateX(180deg)
ul
opacity 1
// not the best but IDK what is better
max-height 100vh
ul li
margin-top 8px
div
width 100%
cursor pointer
border-radius 8px
transition all $transition
color $darkGrayLight
@media (prefers-color-scheme dark)
color $lightGrayDark
&:first-child
margin-top 0
&:hover
color black
background $lightGrayLight
@media (prefers-color-scheme dark)
color white
background $darkGrayDark
&.active
&:active
color $textOnMain
background $main
padding 8px
display flex
align-items center
z-index 111
position relative
svg
transition color $transition
&:first-child + span
margin-left 16px
span
display inline-block
white-space nowrap
a
width 100%
display flex
//max-height 24px
.navbar
.sidebar
ul
list-style none
margin 0
padding 0
.userMenu
padding 8px 16px
a
display inline-block
padding-bottom 16px
.mobileMenu
opacity 0
transition opacity $transition
pointer-events none
&.shown
opacity 1
pointer-events initial
.mainGradient
//WIP
fill $mainGradient

View File

@ -0,0 +1,43 @@
import React from 'react'
import { Meta, Story } from '@storybook/react/types-6-0'
import { Zap, ZapOff } from 'lucide-react'
import Component from '.'
export default {
title: 'DZEIO/Sidebar',
component: Component,
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Sidebar: Story<any> = (args: any) => <Component {...args} />
Sidebar.args = {
logo: {src: '/90-38.svg', width: 90, height: 38},
user: {
name: 'Username',
menu: [{
path: '/logout',
value: 'Logout'
}, {
path: '/logout',
value: 'Logout'
}]
},
menu: [{
name: 'Dasboard',
icon: Zap
}, {
name: 'With Childs',
icon: Zap,
subMenu: [{
name: 'With Childs'
}, {
name: 'With Childs'
}]
}, {
path: '/dashboard',
name: 'Link',
icon: ZapOff
}],
}

219
src/Sidebar/index.tsx Normal file
View File

@ -0,0 +1,219 @@
import React, { MouseEvent } from 'react'
import Router from 'next/router'
import Image, { ImageProps } from 'next/image'
import { ChevronDown, Minus, MoreHorizontal, Plus } from 'lucide-react'
import Text from '../Text'
import Col from '../Col'
import Row from '../Row'
import Link from '../Link'
import { buildClassName } from '../Util'
import css from './Sidebar.module.styl'
import { Icon } from '../interfaces'
import { Menu } from '..'
import Router from 'next/router'
interface MenuItem {
path?: string
icon?: Icon
name: string
subMenu?: Array<MenuItem>
}
interface Props {
/**
* Logo to display
*/
logo?: ImageProps & {height: number, width: number}
/**
* User Informations if loggedin
*/
user?: {
/**
* Username
*/
name: string
/**
* User Menu
*/
menu?: Menu['props']['items']
}
/**
* Links to display
*/
menu: Array<MenuItem>
/**
* Internal Use don't use it !
*/
onClose?: () => void
}
interface State {
path?: string
/**
* Define if the menu is open or closed
*
* in mobile it will be hidden when closed
*/
open: boolean
activeMenu?: string
isMobile: boolean
userMenu?: boolean
subMenu?: {
y: number
menu: Menu['props']['items']
}
}
/**
* Sidebar Component
* @version 1.0.0
*/
export default class Navbar extends React.Component<Props, State> {
public state: State = {
open: true,
isMobile: false
}
public componentDidMount() {
this.setState({
path: Router.asPath
})
Router.events.on('routeChangeComplete', () => {
this.setState({path: Router.asPath, open: false})
})
Router.events.on('routeChangeError', () => {
this.setState({path: Router.asPath, open: false})
})
document.body.classList.add(css.sidebarBody)
document.body.addEventListener('click', this.onBodyClick)
}
public componentDidUpdate() {
if (this.state.open) {
document.body.classList.remove(css.short)
} else {
document.body.classList.add(css.short)
}
}
public componentWillUnmount() {
document.body.classList.remove(css.short, css.sidebarBody)
document.body.removeEventListener('click', this.onBodyClick)
}
private onBodyClick = () => {
this.setState({subMenu: undefined, userMenu: false})
}
public onClick = (id: string, subMenu?: Array<MenuItem>) => (ev: MouseEvent) => {
ev.stopPropagation()
if (!this.state.open && subMenu) {
console.log(ev)
this.setState({
subMenu: {
y: (ev.currentTarget as HTMLElement).offsetTop,
menu: subMenu.map((v) => ({
display: v.name,
value: v.path
}))
}
})
} else {
this.setState({activeMenu: this.state.activeMenu === id ? undefined : id, subMenu: undefined})
}
}
public render = () => (
<>
<nav className={buildClassName(
css.sidebar,
[css.short, !this.state.open]
)}>
<Row nowrap justify="space-between" className={css.header} align="center">
{this.props.logo && (
<Col>
<Link href="/">
<Image {...this.props.logo} height={34} width={this.props.logo.width*34/this.props.logo.height} />
</Link>
</Col>
)}
<Col nogrow><Text tag="div">
{this.state.open ? (
<Minus size={24} onClick={() => this.setState({open: false, activeMenu: undefined})} />
) : (
<Plus size={24} onClick={() => this.setState({open: true})} />
)}
</Text></Col>
</Row>
<ul>
{this.props.menu.map((item) => this.makeMenuItem(item))}
</ul>
<div style={{flex: 1}}></div>
{/* Spacer */}
{this.props.user && (
<Row className={css.userSpace}>
<Col><Text>{this.props.user.name}</Text></Col>
<Col nogrow><Text><MoreHorizontal size={24} onClick={(ev) => {ev.stopPropagation(); this.setState({userMenu: !this.state.userMenu})}} /></Text></Col>
</Row>
)}
</nav>
{this.props.user?.menu && this.state.userMenu && (
<div style={{position: 'absolute', bottom: 16, left: this.state.open ? 316 : 104}}>
<Menu onClick={this.onMenuClick} outline items={this.props.user.menu} />
</div>
)}
{this.state.subMenu && (
<div style={{position: 'absolute', top: this.state.subMenu.y, left: this.state.open ? 316 : 104}}>
<Menu onClick={this.onMenuClick} outline items={this.state.subMenu.menu} />
</div>
)}
</>
)
private onMenuClick = (value?: string) => {
this.setState({userMenu: false, subMenu: undefined})
if (value) {
Router.push(value)
}
}
private makeMenuItem(obj: MenuItem, isSub = false) {
const id = obj.name + obj.path
const content = (
<>
{obj.icon && (
<obj.icon size={24} />
)}
<Text color="none" weight="bold" tag="span">
{obj.name}
</Text>
{obj.subMenu && (
<ChevronDown size={24} />
)}
</>
)
return <li key={id} className={buildClassName(
[css.active, obj?.path && this.state.path?.startsWith(obj.path)],
[css.activeMenu, id === this.state.activeMenu]
)}>
<div onClick={isSub ? undefined : this.onClick(id, obj.subMenu)}>
{obj.path ? (
<Link noStyle href={obj.path}>
{content}
</Link>
) : content}
</div>
{obj.subMenu && (
<ul>
{obj.subMenu.map((it, key) => (
<li key={it.name + key}>{this.makeMenuItem(it, true)}</li>
))}
</ul>
)}
</li>
}
}

View File

@ -5,7 +5,7 @@ import css from './Text.module.styl'
type Types = 'hero' | 'h1' | 'h2' | 'h3' | 'h4' | 'text' | 'light' | 'bold'
interface Props {
color?: 'black' | 'white'
color?: 'black' | 'white' | 'none'
type?: Types
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'em' | 'span' | 'div'
weight?: 'normal' | 'bold' | 'light'

View File

@ -2,29 +2,29 @@
* @summary DZEIO Component Library
*/
import './dzeio/general.styl'
import './general.styl'
import Box from './dzeio/Box'
import BoxHeader from './dzeio/Box/BoxHeader'
import Button from './dzeio/Button'
import Checkbox from './dzeio/Checkbox'
import Code from './dzeio/Code'
import Col from './dzeio/Col'
import Container from './dzeio/Container'
import Footer from './dzeio/Footer'
import GradientBackground from './dzeio/GradientBackground'
import Image from './dzeio/Image'
import Input from './dzeio/Input'
import Link from './dzeio/Link'
import Loader from './dzeio/Loader'
import Menu from './dzeio/Menu'
import Navbar from './dzeio/Navbar'
import NotificationManager from './dzeio/NotificationManager'
import Popup from './dzeio/Popup'
import Row from './dzeio/Row'
import Table from './dzeio/Table'
import Text from './dzeio/Text'
import * as Util from './dzeio/Util'
import Box from './Box'
import BoxHeader from './Box/BoxHeader'
import Button from './Button'
import Checkbox from './Checkbox'
import Code from './Code'
import Col from './Col'
import Container from './Container'
import Footer from './Footer'
import GradientBackground from './GradientBackground'
import Image from './Image'
import Input from './Input'
import Link from './Link'
import Loader from './Loader'
import Menu from './Menu'
import Navbar from './Navbar'
import NotificationManager from './NotificationManager'
import Popup from './Popup'
import Row from './Row'
import Table from './Table'
import Text from './Text'
import * as Util from './Util'
export {
Box,