Compare commits

..

1 Commits

Author SHA1 Message Date
04d79ed9dd Bump minimist from 1.2.5 to 1.2.8
Bumps [minimist](https://github.com/minimistjs/minimist) from 1.2.5 to 1.2.8.
- [Release notes](https://github.com/minimistjs/minimist/releases)
- [Changelog](https://github.com/minimistjs/minimist/blob/main/CHANGELOG.md)
- [Commits](https://github.com/minimistjs/minimist/compare/v1.2.5...v1.2.8)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-22 09:48:41 +00:00
129 changed files with 39519 additions and 13682 deletions

View File

@ -1,5 +0,0 @@
module.exports = {
extends: [
"./node_modules/@dzeio/config/eslint/react-typescript"
]
}

5
.gitattributes vendored
View File

@ -1,4 +1 @@
* text=auto eol=lf
*.png binary
*.jpg binary
*.jpeg binary
* text=lf

View File

@ -1,17 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "npm" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
# Workflow files stored in the
# default location of `.github/workflows`
directory: "/"
schedule:
interval: "daily"

View File

@ -1,29 +0,0 @@
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
name: Build & Check
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 16
uses: actions/setup-node@v2.4.1
with:
node-version: 16.x
- name: Install packages
run: npm ci
- name: Build components
run: npm run build

View File

@ -1,65 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
language: [ 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

View File

@ -1,37 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow integrates a collection of open source static analysis tools
# with GitHub code scanning. For documentation, or to provide feedback, visit
# https://github.com/github/ossar-action
name: OSSAR
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
jobs:
OSSAR-Scan:
# OSSAR runs on windows-latest.
# ubuntu-latest and macos-latest support coming soon
runs-on: windows-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
# Run open source static analysis tools
- name: Run OSSAR
uses: github/ossar-action@v1
id: ossar
# Upload results to the Security tab
- name: Upload OSSAR results
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: ${{ steps.ossar.outputs.sarifFile }}

View File

@ -1,28 +0,0 @@
name: publish
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup NodeJS
uses: actions/setup-node@v3
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Publish
run: npm publish
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

12
.gitignore vendored
View File

@ -1,7 +1,11 @@
module/
node_modules/
*.mjs
*.js
!src/dzeio/stylusUtils.js
*.d.ts
types/
index.es.js
index.umd.js
!rollup.config.js
!src/stylus.d.ts
!.storybook/*.js
style.css
*.tgz

View File

@ -1,13 +1,8 @@
.github/
.storybook/
node_modules/
src/
.editorconfig
*.stories.js
.gitattributes
.gitignore
.npmignore
LICENSE
vite.config.js
package-lock.json
tsconfig.json
README.md
yarn.lock
yarn-error.log

View File

@ -1,17 +1,13 @@
const path = require("path");
const webpack = require('webpack')
module.exports = {
"stories": [
"../src/**/*.stories.tsx",
"../src/dzeio/**/*.stories.tsx",
],
core: {
builder: "@storybook/builder-vite"
},
staticDirs: ["./public"],
"addons": [
"@storybook/addon-essentials"
],
reactOptions: {
strictMode: true
},
typescript: {
check: false,
checkOptions: {},
@ -20,5 +16,19 @@ module.exports = {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
},
},
presets: [path.resolve(__dirname, "./next.js")],
// Allow to use Next/Image
webpackFinal: (config) => {
config.plugins.push(new webpack.DefinePlugin({
'process.env.__NEXT_IMAGE_OPTS': JSON.stringify({
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
domains: [],
path: '/',
loader: 'default',
})
}))
return config
}
}

View File

@ -0,0 +1,17 @@
// https://stackoverflow.com/a/64765638/7335674
import * as nextImage from 'next/image'
Object.defineProperty(nextImage, 'default', {
configurable: true,
value: (props) => {
return (
<div style={{display: 'inline-block', maxWidth: '100%', overflow: 'hidden', position: 'relative', boxSizing: 'border-box', margin: 0}}>
<div style={{boxSizing: 'border-box', display: 'block', maxWidth: '100%'}}>
<img {...props} alt="" aria-hidden="true" role="presentation" style={{maxWidth: '100%', display: 'block', margin: 0, border: 'none', padding: 0}} />
</div>
<img {...props} style={{position: 'absolute', inset: 0, boxSizing: 'border-box', padding: 0, border: 'none', margin: 'auto', display: 'block', width: 0, height: 0, minWidth: '100%', maxWidth: '100%', minHeight: '100%', maxHeight: '100%'}} />
</div>
)
},
})

View File

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

37
.storybook/next.js Normal file
View File

@ -0,0 +1,37 @@
module.exports = {
webpackFinal: async (baseConfig, options) => {
const { module = {} } = baseConfig;
const newConfig = {
...baseConfig,
module: {
...module,
rules: [...(module.rules || [])],
},
};
// TypeScript
newConfig.module.rules.push({
test: /\.(ts|tsx)$/,
// include: [path.resolve(__dirname, '../src/client/components')],
use: ['babel-loader', 'ts-loader']
});
newConfig.resolve.extensions.push('.ts', '.tsx');
// Stylus
newConfig.module.rules.push({
test: /\.styl$/,
use: ['style-loader', {
loader: 'css-loader',
options: {
url: false,
importLoaders: 1,
modules: true
},
}, 'stylus-loader'],
});
newConfig.resolve.extensions.push('.styl');
return newConfig;
},
};

View File

@ -1,7 +1,7 @@
import '../src/general.styl'
import '../src/dzeio/general.styl'
import './mockNextRouter'
import './mockNextImage'
export const parameters = {
layout: 'centered',
actions: { argTypesRegex: '^on.*' }
}
layout: 'centered'
}

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Florian Bouillon
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1 +0,0 @@
# Dzeio Components

46937
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,40 +1,50 @@
{
"name": "@dzeio/components",
"version": "1.0.0-beta.23",
"version": "0.11.3",
"license": "MIT",
"main": "./index.umd.js",
"module": "./index.es.js",
"main": "./index.js",
"types": "./types/index.d.ts",
"devDependencies": {
"@storybook/addon-essentials": "^6",
"@storybook/builder-vite": "^0.2.2",
"@storybook/cli": "^6",
"@storybook/react": "^6",
"@types/json5": "^2",
"@types/node": "^18",
"@types/react": "^18",
"@types/react-dom": "^18",
"html-webpack-plugin": "^5.5.0",
"next": "^12",
"react": "^18",
"react-dom": "^18",
"style-loader": "^3",
"stylus": "^0.59.0",
"typescript": "^4",
"vite": "^3"
"@babel/core": "^7.12.16",
"@babel/preset-env": "^7.12.16",
"@babel/preset-react": "^7.12.13",
"@storybook/addon-essentials": "^6.1.14",
"@storybook/cli": "^6.1.14",
"@storybook/react": "^6.1.14",
"@types/node": "^15.12.1",
"@types/react": "^17.0.2",
"@types/react-dom": "^17.0.1",
"babel-loader": "^8.2.2",
"css-loader": "^5.0.2",
"lucide-react": "^0.15.19",
"next": "^10.0.0",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"style-loader": "^2.0.0",
"stylus": "^0.54.8",
"stylus-loader": "^4.3.3",
"ts-loader": "^8.0.17",
"typescript": "^4.2.3",
"webpack": "^4.44.2"
},
"peerDependencies": {
"next": ">=11.0.0",
"react": ">=17.0.0",
"react-dom": ">=17.0.0"
"lucide-react": "^0.15.19",
"next": "^10.0.0 || ^11.0.0",
"react": "^16.0.0 || ^17.0.0",
"react-dom": "^16.0.0 || ^17.0.0",
"stylus": "^0.54.8",
"typescript": "^4.0.0"
},
"scripts": {
"dev": "start-storybook --port 6006 --no-version-updates --disable-telemetry --no-manager-cache --modern --no-open",
"build": "npx vite build && npx tsc --emitDeclarationOnly",
"prepublishOnly": "npx vite build && npx tsc --emitDeclarationOnly"
"dev": "start-storybook -s ./.storybook/public -p 6006",
"build": "rollup --config",
"prepublishOnly": "npm run build",
"postinstall": "rollup --config"
},
"dependencies": {
"@dzeio/object-util": "^1",
"lucide-react": "^0.104.1"
"rollup": "^2.44.0",
"rollup-plugin-styles": "^3.14.1",
"rollup-plugin-typescript2": "^0.30.0",
"tslib": "^2.1.0"
}
}

29
rollup.config.js Normal file
View File

@ -0,0 +1,29 @@
import typescript from 'rollup-plugin-typescript2';
import styles from 'rollup-plugin-styles'
import pkg from './package.json';
export default [
{
input: 'src/index.ts',
external: ['ms'],
plugins: [
styles({
modules: true,
url: false,
autoModules: true,
mode: 'extract',
modules: {
generateScopedName: '[local][hash:5]'
}
}),
typescript({useTsconfigDeclarationDir: true}), // so Rollup can convert TypeScript to JavaScript
],
output: [
{
file: pkg.main,
format: 'cjs',
assetFileNames: 'style.css'
}
]
}
];

View File

@ -1,36 +0,0 @@
@import "../config"
.box
background var(--theme-50)
@media (prefers-color-scheme dark)
background var(--gray-800)
border-radius 16px
&.noBottomBorder
border-radius 16px 16px 0 0
.header
padding 16px
&.noSidePadding
padding 16px 0
+ .body
padding-top 0
.title
font-weight 700
font-size rem(16)
display inline-flex
// BODY
.body
padding 16px
.icon
padding 2px
width 24px
height 24px
display inline-block
background var(--theme-500)
border-radius 4px
margin-right 8px

View File

@ -1,30 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
import Text from '../Text'
import { Lightbulb } from 'lucide-react'
import BoxHeader from './BoxHeader'
export default {
title: 'DZEIO/Box',
component: Component,
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Box = (args: any) => (
<Component {...args}><Text>Test</Text></Component>
)
export const Complete = (args: any) => (
<Component
title="Test"
icon={Lightbulb}
rightHeader={<Text>Test</Text>}
// {...args}
>
<Text>Test</Text>
<BoxHeader title="BoxHeader" />
</Component>
)

View File

@ -1,24 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from './BoxHeader'
import Text from '../Text'
import { Lightbulb } from 'lucide-react'
export default {
title: 'DZEIO/BoxHeader',
component: Component,
parameters: {
layout: 'fullscreen'
}
} as Meta
export const BoxHeader = (args: any) => (
<Component titel="Test" {...args} />
)
export const Complete = (args: any) => (
<Component
title="Test"
icon={Lightbulb}
><Text>Test</Text></Component>
)

View File

@ -1,44 +0,0 @@
import React from 'react'
import css from './Box.module.styl'
import Row from '../Row'
import Col from '../Col'
import Text from '../Text'
import { Icon } from '../interfaces'
import { buildClassName } from '../Util'
interface Props extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
title?: string
icon?: Icon
sidePadding?: boolean
children?: React.ReactNode
}
/**
* The box header that caan be used out of a box
*
* @version 1.0.0
*/
export default class BoxHeader extends React.Component<Props> {
public render = () => (
<div className={buildClassName(css.header, [css.noSidePadding, !this.props.sidePadding])}>
<Row justify="space-between">
<Col>
<Text className={css.title} weight="bold">
{this.props.icon && (
<span className={css.icon}>
<this.props.icon strokeWidth="2" fontWeight="800" size="20" color="white" />
</span>
)}
{this.props.title ? this.props.title : undefined}
</Text>
</Col>
{this.props.children && (
<Col nogrow>
{this.props.children}
</Col>
)}
</Row>
</div>
)
}

View File

@ -1,42 +0,0 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './Box.module.styl'
import { Icon } from '../interfaces'
import { objectOmit } from '@dzeio/object-util'
import BoxHeader from './BoxHeader'
interface Props extends React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> {
noPadding?: boolean
// Header
title?: string
icon?: Icon
rightHeader?: React.ReactNode
noBottomBorder?: boolean
}
/**
* The basic Box Component
*
* @version 1.0.0
*/
export default class Box extends React.Component<Props> {
public render = () => (
<div
{...objectOmit<Record<string, any>>(this.props, 'title', 'icon', 'rightHeader', 'noPadding')}
className={buildClassName(css.box, this.props?.className, [css.noBottomBorder, this.props.noBottomBorder])}
>
{(this.props.rightHeader || this.props.title || this.props.icon) && (
<BoxHeader title={this.props.title} sidePadding icon={this.props.icon}>{this.props.rightHeader}</BoxHeader>
)}
{this.props.children && (
<div className={buildClassName([css.body, !this.props.noPadding])}>
{this.props.children}
</div>
)}
</div>
)
}

View File

@ -1,23 +0,0 @@
@import "../config"
.breadcrumb ol
display flex
display inline-flex
padding 0
margin 0
align-items center
flex-wrap wrap
li
display inline-block
&:first-child .item
padding-left 0
&:last-child .item
padding-right 0
.item
padding 0 16px
.chevron
color var(--theme-500)
@media (prefers-color-scheme dark)
color white

View File

@ -1,20 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap } from 'lucide-react'
import Box from '../Box'
import Component from '.'
export default {
title: 'DZEIO/Breadcrumb',
component: Component
} as Meta
export const Breadcrumb = (args: any) => <Box><Component {...args}>Button</Component></Box>
Breadcrumb.args = {
items: [{
display: "Pouet",
href: '/pouet'
}, {
display: "Pouet",
}]
}

View File

@ -1,49 +0,0 @@
import React from 'react'
import Link from '../Link'
import Text from '../Text'
import css from './Breadcrumb.module.styl'
import { ChevronRight, Home } from 'lucide-react'
interface Props {
items: Array<{
display: string
href?: string
}>
textProps?: Text['props']
}
/**
* A breadcrumb compatible with Schema.org BreadcrumbList type
*
* @version 1.0.0
*/
export default class Breadcrumb extends React.Component<Props> {
public render() {
return (
<nav className={css.breadcrumb}>
<ol vocab="https://schema.org/" typeof="BreadcrumbList">
<li>
<Link className={css.item} href="/" noStyle>
<Text {...this.props.textProps} tag="span"><Home size={16} /></Text>
</Link>
</li>
{this.props.items.map((el, index) => (
<li property="itemListElement" typeof="ListItem" key={index}>
<Text {...this.props.textProps} tag="span"><ChevronRight size={14} className={css.chevron} /></Text>
{el.href ? (
<Link className={css.item} noStyle href={el.href.replace(/ /g, '-')} linkProps={{ property: "item", typeof: "WebPage" }}>
<Text {...this.props.textProps} tag="span" textProps={{ property: "name" }}>{el.display}</Text>
</Link>
) : (
<Text className={css.item} {...this.props.textProps} tag="span" weight="bold" textProps={{ property: "name" }}>{el.display}</Text>
)}
<meta property="position" content={index.toString()} />
</li>
))}
</ol>
</nav>
)
}
}

View File

@ -1,143 +0,0 @@
@import '../config'
.button
font-size rem(16)
position relative
transition all $transition
font-weight 600
line-height 1.5
display inline-flex
padding 10px 20px
cursor pointer
align-items center
text-align center
border-radius 4px
border none
justify-content center
align-items center
color var(--theme-500-text)
background-color var(--theme-500)
// Chrome Specific
outline none
// Link specific
text-decoration none
&.outline
&.ghost
&:hover
box-shadow none
background-color nativeRGBA(var(--theme-500-rgb), .2)
&:active
&:focus
background-color var(--theme-500)
color var(--theme-500-text)
&.outline
border 2px solid @background-color
padding 8px 18px // @padding - @border
background transparent
color @background-color
&:not(:disabled)
&:active
&:focus
color var(--theme-500-text)
&.ghost
background transparent
color black
@media (prefers-color-scheme dark)
color white
&:hover
background-color @background-color
box-shadow 0 0 0 4px nativeRGBA(var(--theme-500-rgb), .2)
&:active
&:focus
background-color var(--theme-800)
&.block
display flex
width 100%
margin 0
margin-top 8px
@media (max-width $mobile)
&.mobileBlock
display flex
width 100%
margin 0
margin-top 8px
&.large
padding 15px 30px
font-size rem(20)
&.outline
padding 13px 28px // @padding - @border
&.small
padding 5px 10px
font-size rem(14)
&.outline
padding 3px 8px // @padding - @border
&:disabled
background var(--gray-500)
color var(--gray-500--text)
transform none
box-shadow none
cursor initial
&.outline
border 2px solid var(--gray-500)
background transparent
@media (prefers-color-scheme dark)
border 2px solid var(--gray-500)
&.loading
color transparent
position relative
pointer-events none
&::after
content ""
display block
border white 2px solid
border-color transparent transparent white white
width 1em
position absolute
top calc(50% - (1em / 2))
left calc(50% - (1em / 2))
border-radius 100%
height 1em
box-sizing inherit
animation ButtonLoading .75s infinite linear
// &:disabled::after
// $rgb = var(--gray-500)
// border-color transparent transparent $rgb $rgb
// @media (prefers-color-scheme dark)
// border-color transparent transparent $rgb $rgb
svg + .textInner
margin-left 8px
.textInner + svg
margin-left 8px
@keyframes ButtonLoading
0%
transform rotate(0)
100%
transform rotate(365deg)
.img
min-width 16px

View File

@ -1,78 +0,0 @@
import React from 'react'
import Image from '../Image'
import { ColorType, Icon } from '../interfaces'
import Link from '../Link'
import { buildClassName } from '../Util'
import css from './Button.module.styl'
// MAKE OUTLINE use Fieldset instead of the current one xd
interface Props {
color?: ColorType
children?: React.ReactNode
className?: string
icon?: Icon | string
iconLeft?: Icon | string
size?: 'large' | 'small'
type?: 'outline' | 'ghost'
block?: boolean
href?: string
linkExternal?: boolean
mobileBlock?: boolean
disabled?: boolean
loading?: boolean
onClick?: (event: React.MouseEvent<HTMLButtonElement|HTMLAnchorElement, MouseEvent>) => void
}
export default class Button extends React.Component<Props> {
public render = () => {
let inner: any = this.props.children
if (this.props.icon || this.props.iconLeft) {
inner = (
<>
{this.props.icon && (typeof this.props.icon === 'string' ? (
<Image imageProps={{src: this.props.icon, width: 16, height: 16}} />
) : (
<this.props.icon size={this.props.size === 'large' ? 20 : this.props.size === 'small' ? 14 : 16} />
))}
{this.props.children && (
<span className={css.textInner}>{this.props.children}</span>
)}
{this.props.iconLeft && (typeof this.props.iconLeft === 'string' ? (
<Image imageProps={{src: this.props.iconLeft, width: 16, height: 16}} />
) : (
<this.props.iconLeft size={this.props.size === 'large' ? 20 : this.props.size === 'small' ? 14 : 16} />
))}
</>
)
}
const classes = buildClassName(
[css.button],
[css[this.props.color as string], this.props.color],
[css[this.props.type as string], this.props.type],
[css.block, this.props.block],
[css[this.props.size as string], this.props.size],
[css.loading, this.props.loading],
[css.mobileBlock, this.props.mobileBlock],
this.props.className
)
if (this.props.href && !this.props.disabled) {
return (
<Link external={this.props.linkExternal} linkProps={{onClick: this.props.onClick}} noStyle href={this.props.href} className={buildClassName(classes, [css.disabled, this.props.disabled])}>
{inner}
</Link>
)
}
return (
<button onClick={this.props.onClick} disabled={this.props.disabled} className={classes}>{inner}</button>
)
}
}

View File

@ -1,113 +0,0 @@
@import "../config.styl"
$backColor = #757575
.label
position relative
display flex
user-select none
align-items center
+ .label
margin-top 8px
p
margin-left 4px
span
top 0
left 0
width 20px
height @width
position relative
box-shadow inset 0 0 0 2px var(--gray-600)
@media (prefers-color-scheme dark)
box-shadow inset 0 0 0 2px var(--gray-400)
border-radius 4px
transition all $transition
&::after
border-radius 20px
position absolute
transition all $transition
background var(--theme-500)
svg
transition $transition
transform scale(0)
color transparent
margin 2px
input
// visibility hidden
position absolute
top 0
left 0
opacity 0
&:checked + span
background var(--theme-500)
box-shadow inset 0 0 0 2px var(--theme-500)
svg
color white
transform scale(1)
&:hover
span
box-shadow inset 0 0 0 2px var(--theme-500)
.radio
span
border-radius 20px
&::after
content " "
top 5px
left 5px
width 10px
height @width
transform scale(0)
input:checked + span::after
transform scale(1)
background white
.switch
padding 0 0 0 10px // 2px base padding 10px circle padding
&:hover span
box-shadow none
&::after
background var(--theme-500)
span
width 24px
height 14px
border-radius 20px
margin-right 10px
box-shadow none
background var(--gray-500)
&::after
content " "
top 50%
transform translate(-50%, -50%)
left 0
background var(--gray-600)
width 20px
height @width
input
margin 0 8px
width 20px
&:checked + span
box-shadow none
background var(--theme-300)
&::after
left 100%
transform translate(-50%, -50%)
background var(--theme-500)

View File

@ -1,15 +0,0 @@
@import "../config"
.code
.pre
background var(--theme-50)
padding 4px 8px
border-radius 8px
@media (prefers-color-scheme dark)
background var(--gray-700)
color var(--gray-700-text)
.pre
display block
.code
padding 0

View File

@ -1,10 +0,0 @@
.container
padding 0 32px
width 100%
max-width 1280px + @padding[1] * 2
margin auto auto
> *:not(:first-child)
margin-top 48px
&.main
margin 0 auto
padding 48px @padding[1]

View File

@ -1,19 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
import Text from '../Text'
export default {
title: 'DZEIO/Container',
component: Component,
argTypes: {
title: { control: 'text'}
},
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Container = (args: any) => (
<Component {...args}><Text>Test</Text></Component>
)

View File

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

View File

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

View File

@ -1,10 +0,0 @@
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Image',
component: Component
} as Meta
export const Image: Story<any> = (args: any) => <Component imageProps={{src: '/90-38.svg', width: 90, height: 38}} fullscreen {...args} />

View File

@ -1,221 +0,0 @@
@import '../config'
.label
font-size rem(16)
display block
font-weight bold
color black
padding-left 8px
padding-bottom 4px
cursor pointer
@media (prefers-color-scheme dark)
color white
.parent
position relative
max-width 100%
display inline-block
&:not(.block) + .parent:not(.block)
margin-left 16px
svg
position absolute
user-select none
padding 16px
color black
@media (prefers-color-scheme dark)
color white
transition color $transition
pointer-events none
&.iconClickable
pointer-events all
cursor pointer
top -2px
&.left
left 0px // input padding-left
~ label
left 0px + 24px + 10px
&.right
right 0
select
appearance none
option
background var(--theme-50)
color black
@media (prefers-color-scheme dark)
background var(--gray-800)
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
opacity 0
transition all $transition
overflow-x hidden
pointer-events none
position absolute
top calc(100% + 16px)
left 0
width 100%
z-index 100
max-height 25vh
overflow-y auto
@media (max-width $mobile)
max-height 50vh
&.reverse
top initial
bottom calc(100% + 16px)
div + .autocomplete
top 100%
input:focus ~ .autocomplete
select:focus ~ .autocomplete
textarea:focus ~ .autocomplete
.autocomplete:hover
opacity 1
pointer-events inherit
input
select
textarea
padding 12px
border-radius 8px
max-width 100%
font-size .875rem
outline none
background var(--theme-50)
transition all $transition
border 2px solid black
color black
@media (prefers-color-scheme dark)
background var(--gray-800)
border-color transparent
border 2px solid var(--gray-500)
color white
&::placeholder
font-size rem(16)
transition color $transition
opacity 1
color black
@media (prefers-color-scheme dark)
color white
&:disabled
border-color var(--gray-500)
&:not(:disabled)
&:hover
border-color black
@media (prefers-color-scheme dark)
border-color white
~ svg
&::placeholder
color black
@media (prefers-color-scheme dark)
color white
&:focus
border-color var(--theme-500)
~ svg
color @border-color
// &::placeholder
// color black
// @media (prefers-color-scheme dark)
// color white
&.iconLeft
padding-left 16px + 24px + 10px
&.iconRight
padding-right 16 + 24 + 10px
~ svg.rotate
transform rotateX(0)
transition $transition
&:focus ~ svg.rotate
~ .autocomplete:hover ~ svg.rotate
transform rotateX(180deg)
input[type="range"]
appearance none
background transparent
cursor pointer
width 100%
border none
$height = 4px
&::-webkit-slider-runnable-track
appearance none
background-color var(--theme-100)
height $height
width 100%
border-radius $height
transition $transition
@media (prefers-color-scheme dark)
background-color var(--gray-800)
&:active::-webkit-slider-runnable-track
background var(--theme-200)
@media (prefers-color-scheme dark)
background-color var(--gray-700)
&::-moz-range-track
appearance none
background-color var(--theme-100)
height $height
width 100%
border-radius $height
transition $transition
@media (prefers-color-scheme dark)
background-color var(--gray-800)
&::-webkit-slider-thumb
appearance none
$size = 16px
margin-top ($height / 2) - ($size / 2)
height $size
width $size
border none
background-color var(--theme-500)
border-radius $size
&::-moz-range-thumb
appearance none
$size = 16px
margin-top ($height / 2) - ($size / 2)
height $size
width $size
border none
background-color var(--theme-500)
border-radius $size
p
padding 0 8px
font-size rem(14)
&.block
&.block input
&.block textarea
width 100%
display block

View File

@ -1,115 +0,0 @@
import { Story } from "@storybook/react"
import { Meta } from '@storybook/react/types-6-0'
import { X } from 'lucide-react'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Input',
component: Component
} as Meta
export const Input: Story<any> = (args: any) => <Component {...args} />
let tmp = Input.bind({})
tmp.args = {
label: 'Label',
helper: 'Helper',
maxLength: 6,
// iconLeft: {
// icon: X,
// transformer: (v: string) => v + 1
// },
min: 0,
id: 'pouet',
type: 'number',
step: 10,
defaultValue: 'test',
placeholder: 'test',
disabled: false
}
export const Normal = tmp
tmp = Input.bind({})
tmp.args = {defaultValue : 'd', label: 'Label', helper: 'Helper', choices: [
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'b',
{value: 'd', display: 'D'},
{value: '4', display: 'Mai'},
'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
], iconLeft: {
icon: X,
transformer: (v: string) => {
console.log("POUET :D")
return ""
}
}}
export const AutoComplete = tmp
tmp = Input.bind({})
tmp.args = {label: 'Label', helper: 'Helper', strictChoices: true, choices: [
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'b',
{value: 'd', display: 'D'},
{value: '4', display: 'Mai'},
'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
], iconLeft: {
icon: X,
transformer: (v: string) => {
console.log("POUET :D")
return ""
}
}}
export const Select = tmp
tmp = Input.bind({})
tmp.args = {label: 'Label', helper: 'Helper', type: 'number', iconLeft: {
icon: X,
transformer: (v: string) => {
console.log("POUET :D")
return v + 2
}
}}
export const Number = tmp
tmp = Input.bind({})
tmp.args = {block: true, type: 'textarea', defaultValue : 'd', label: 'Label', helper: 'Helper', choices: [
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'a',
'b',
{value: 'd', display: 'D'},
{value: '4', display: 'Mai'},
'cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc'
], iconLeft: {
icon: X,
transformer: (v: string) => {
console.log("POUET :D")
return ""
}
}}
export const TextArea = tmp

View File

@ -1,407 +0,0 @@
import React, { FocusEvent } from 'react'
import { objectEqual, objectOmit } from '@dzeio/object-util'
import { ChevronDown, MinusSquare, PlusSquare } from 'lucide-react'
import Menu from '../Menu'
import Text from '../Text'
import { buildClassName } from '../Util'
import { Icon } from '../interfaces'
import css from './Input.module.styl'
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
id?: string
label?: string
iconLeft?: Icon | {
icon: Icon
transformer: (value: string) => string
}
iconRight?: Icon | {
icon: Icon
transformer: (value: string) => string
}
helper?: string
inputRef?: React.RefObject<HTMLInputElement>
type?: 'color' | 'text' | 'date' | 'datetime-local' |
'email' | 'file' | 'month' | 'number' | 'password' |
'range' | 'search' | 'tel' | 'time' | 'url' | 'week' |
// Custom Types
'textarea'
choices?: Array<string | {display: string, value: string}>
/**
* Always display every choices
*/
displayAllOptions?: boolean
/**
* Handle the change event
* you will be returned the value (for choices too)
*/
onValue?: (newValue: string) => void
/**
* Make the input take the whole width
*/
block?: boolean
/**
* if enabled value will not be sent if it is not contained in the choices
*/
strictChoices?: boolean
/**
* Allows you to disable automatic icons
*/
disableAutoIcons?: boolean
placeholder?: string
}
interface States {
textAreaHeight?: number
value?: string
displayedValue?: string
valueUpdate: boolean
isInFirstPartOfScreen?: boolean
list: Menu['props']['items']
}
export default class Input extends React.Component<Props, States> {
public state: States = {
valueUpdate: false,
list: []
}
private inputRef: React.RefObject<HTMLInputElement> = React.createRef()
private parentRef: React.RefObject<HTMLDivElement> = React.createRef()
public componentDidMount() {
// Handle Text Area
if (this.props.type === 'textarea') {
this.textareaHandler()
}
// Handle choices
if (this.props.choices) {
window.addEventListener('scroll', this.parentScroll)
this.parentScroll()
}
// Handle default Value
if (typeof (this.props.defaultValue ?? this.props.value) !== 'undefined') {
const value = this.props.defaultValue ?? this.props.value ?? ''
if (!this.props.choices) {
this.setState({displayedValue: value.toString()})
} else {
const res = this.props.choices.find(
(it) => typeof it === 'string' ? it === value : it.value === value
)
if (!res) {
if (this.props.strictChoices) {
this.setState({value: '', displayedValue: ''})
} else {
this.setState({displayedValue: value.toString()})
}
return
}
this.setState({
displayedValue: typeof res === 'string' ? res : res.display
})
}
}
if (this.props.choices) {
this.setState({list: this.buildList()})
}
}
public componentWillUnmount() {
if (this.props.choices) {
window.removeEventListener('scroll', this.parentScroll)
}
}
public async componentDidUpdate(prevProps: Props, prevStates: States) {
if (prevProps.value !== this.props.value && this.props.value !== this.state.value) {
if (this.props.choices) {
const choice = this.props.choices.find((it) => typeof it === 'string' ? it : it.value === this.props.value?.toString())
if (choice) {
this.setState({
displayedValue: typeof choice === 'string' ? choice : choice.display,
value: typeof choice === 'string' ? choice : choice.value,
valueUpdate: true
})
}
} else {
this.setState({ displayedValue: this.props.value?.toString(), value : this.props.value?.toString(), valueUpdate: true })
}
}
if (
prevStates.value !== this.state.value ||
prevStates.displayedValue !== this.state.displayedValue ||
prevStates.valueUpdate !== this.state.valueUpdate ||
!objectEqual(prevProps.choices ?? [], this.props.choices ?? [])
) {
this.setState({list: this.buildList()})
}
}
/**
* return the real value of the field (depending if you a choices and the display value)
* @returns the value of the field
*/
public value(): string | number | ReadonlyArray<string> | undefined {
return this.state?.value ?? this.state.displayedValue ?? this.props.value ?? undefined
}
public render() {
const props: Props = objectOmit(this.props, 'iconLeft', 'iconRight', 'inputRed', 'helper', 'choices', 'onValue', 'block', 'defaultValue', 'label', 'strictChoices')
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
ref: this.props.inputRef || this.inputRef,
className: buildClassName(
[css.iconLeft, this.props.type === 'number' || this.props.iconLeft],
[css.iconRight, this.props.type === 'number' || this.props.iconRight || this.props.choices]
),
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
onFocus: (ev: FocusEvent<HTMLInputElement, Element>) => {
this.setState({valueUpdate: false})
if (props.onFocus) {
props.onFocus(ev)
}
},
value: this.state.displayedValue ?? this.state.value ?? this.props.value,
onChange: this.onChange
}
let iconRight = this.props.iconRight
let iconLeft = this.props.iconLeft
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = <input
{...props}
{...baseProps}
/>
switch (this.props.type) {
case 'textarea':
delete baseProps.ref
input = (
<textarea
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>}
{...baseProps as any}
ref={this.inputRef}
style={{minHeight: this.state?.textAreaHeight}}
onKeyDown={this.textareaHandler}
onKeyUp={this.textareaHandler}
onFocus={this.textareaHandler}
/>
)
break
case 'number':
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
if (!this.props.disabled && !this.props.disableAutoIcons) {
iconLeft = this.props.iconLeft ?? {icon: MinusSquare, transformer: (v) => {
let value = this.ensureNumber(v)
return (value - this.ensureNumber(this.props.step, 1)).toString()
}}
iconRight = this.props.iconRight ?? {icon: PlusSquare, transformer: (v) => {
let value = this.ensureNumber(v)
return (value + this.ensureNumber(this.props.step, 1)).toString()
}}
}
input = <input
{...props}
{...baseProps}
/>
break
}
return (
<>
{this.props.label && (
<label className={css.label} htmlFor={this.props.id}>{this.props.label}</label>
)}
<div
className={buildClassName(
css.parent,
[css.block, this.props.block]
)}
ref={this.parentRef}
>
{input as any}
{/* Left Icon */}
{this.getIcon(iconLeft, 'left')}
{/* Right Icon */}
{iconRight ?
this.getIcon(iconRight, 'right') :
this.props.choices && !this.props.disabled && !this.props.disableAutoIcons && (
<ChevronDown size="18" className={buildClassName(css.right, css.rotate)} />
)
}
{/* Helper text */}
{this.props.helper && (
<Text>{this.props.helper}</Text>
)}
{/* List when this is an autocomplete */}
{this.props.choices && (
<Menu
outline
hideWhenEmpty
className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}
items={this.state.list ?? []}
onClick={this.listSelection}
/>
)}
</div>
</>
)
}
private ensureNumber(item: string | number | undefined, defaultValue: number = 0): number {
console.log('ensureNumber', item, typeof item)
if (typeof item === 'number') return item
if (typeof item === 'undefined') return defaultValue
const res = parseFloat(item)
if (isNaN(res)) {
return defaultValue
}
return res
}
/**
* event for the menu to detect where on the screen it should be displayed
*/
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})
}
}
/**
* Build the interactive list for the item
* @returns the list
*/
private buildList(): Menu['props']['items'] {
if (!this.props.choices) {
return []
}
const v = this.state.displayedValue?.toLowerCase()
return this.props.choices
.map((item, index) => typeof item === 'string' ? {item: {display: item, value: item}, index} : {item, index})
.filter(
(item) => this.props.displayAllOptions || !this.state.valueUpdate || !v || item.item.display.toLowerCase().includes(v) || item.item.display.toLowerCase().toLowerCase().includes(v)
)
.map((item) => item.item)
}
/**
* handle when an item is selected
* @param key the index of the selected item
*/
private listSelection: Menu['props']['onClick'] = async (_, key) => {
const newValue = this.state.list[key]
if (!newValue) {
return
}
if (typeof newValue === 'string') {
this.onChange(newValue)
return
}
this.onChange(newValue.value)
}
/**
* get the icon duh
* @param icon the icon
* @returns the icon
*/
private getIcon(Icon: Icon | {
icon: Icon
transformer: (value: string) => string
} | undefined, position: 'left' | 'right') {
if (!Icon) {
return undefined
}
if ('icon' in Icon) {
// 18 + 16 of padding
return <Icon.icon size={16*2+18} className={buildClassName(css[position], css.iconClickable)} onClick={async () => {
if (this.props.disabled) {return}
const value = Icon.transformer(this.state.value ?? this.state.displayedValue ?? '')
this.onChange(value)
}} />
}
return <Icon size="18" className={css[position]} />
}
/**
* Handle textarea height changes
*/
private textareaHandler = async () => {
this.setState({textAreaHeight: undefined}, () => {
if (!this.inputRef.current) {return}
this.setState({ textAreaHeight: this.inputRef.current.scrollHeight })
})
}
/**
* handle the change event of the input
* @param event the event
*/
private onChange = async (event: React.ChangeEvent<HTMLInputElement>|string | number) => {
// get the input
let value = typeof event === 'object' ? event.currentTarget.value : event
if (typeof value === 'number') {
value = value.toString()
}
if (this.props.type === 'number') {
const val = this.ensureNumber(value)
const min = typeof this.props.min !== 'undefined' ? typeof this.props.min === 'string' ? this.ensureNumber(this.props.min) : this.props.min : -Infinity
const max = typeof this.props.max !== 'undefined' ? typeof this.props.max === 'string' ? this.ensureNumber(this.props.max) : this.props.max : Infinity
console.log('pouet', val, this.props.min, min, this.props.max, max)
value = Math.min(max, Math.max(min, val)).toString()
}
let displayedValue = value
if (this.props.choices) {
const item = this.props.choices.find((it) => typeof it === 'string' ? it === value : it.value === value)
if (this.props.strictChoices && !item) {
this.setState({ displayedValue: displayedValue, valueUpdate: true })
return
}
if (item && typeof item !== 'string') {
displayedValue = item?.display
}
}
if (this.props.onChange && typeof event === 'object') {
event.currentTarget.value = value
this.props.onChange(event)
}
this.props.onValue?.(value)
this.setState({ value: value, displayedValue: displayedValue, valueUpdate: true })
}
}

View File

@ -1,9 +0,0 @@
@import '../config'
.link
color var(--theme-500)
text-decoration underline
.icon
vertical-align sub
margin 2px

View File

@ -1,14 +0,0 @@
@import '../config'
.div
position fixed
left 0
width 100%
pointer-events none
z-index 200
transition top .5s ease-in-out
top 0
&.hide
top -8px

View File

@ -1,17 +0,0 @@
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap } from 'lucide-react'
import Component from '.'
export default {
title: 'DZEIO/Loader',
component: Component,
parameters: {
layout: 'fullscreen'
},
argTypes: {
percent: { control: 'number'}
},
} as Meta
export const Loader: Story<any> = (args: any) => <Component {...args} />

View File

@ -1,50 +0,0 @@
@import "../config"
.menu
&.outline
border 2px solid var(--theme-500)
&.hidden
display none
ul
padding 0
margin 0
display flex
flex-direction column
li
svg
margin-right 8px
vertical-align sub
&:last-child
margin 0
padding 8px
&.link
padding 0
overflow-x hidden
text-overflow: ellipsis;
margin-bottom 8px
color black
cursor pointer
transition all $transition
font-weight bold
border-radius 8px
&:hover
color black
background var(--theme-100)
@media (prefers-color-scheme dark)
color white
&:hover
color white
background var(--gray-700)
&.selected
&:active
background var(--theme-500)
color white
.link
padding 0
.linkInternal
padding 8px
display inline-block
width 100%
.icon
vertical-align sub

View File

@ -1,26 +0,0 @@
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 with link', icon: XOctagon, href: '/'},
{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 Menu = (args: any) => (
<Component outline {...args} items={list} onClick={(_, index) => list[index].selected = !list[index].selected} />
)

View File

@ -1,46 +0,0 @@
import React from 'react'
import Link from '../Link'
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, href?: string}>
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) => {
const content = (
<>
{item.icon && (
<item.icon size="24" />
)}
{item.display ?? item.value}
</>
)
if (item.href) {
return (
<li key={key} className={buildClassName([css.selected, item.selected], css.link)}>
<Link noStyle href={item.href} className={css.linkInternal}>{content}</Link>
</li>
)
}
return (
<li key={key} className={buildClassName([css.selected, item.selected])} onClick={() => this.props.onClick?.(item.value, key)}>
{content}
</li>
)
})}
</ul>
</Box>
)
}

View File

@ -1,44 +0,0 @@
@import '../config'
$height = 76px
.body-navbar
margin-top $height
.navbar
position fixed
left 0
background var(--theme-50)
@media (prefers-color-scheme dark)
background var(--gray-800)
top 0
height $height
width 100%
z-index 100
display flex
padding 16px
> ul
.userSpaceParent ul
display flex
.navbar
ul
list-style none
margin 0
padding 0
.mobileMenu
opacity 0
transition opacity $transition
pointer-events none
&.shown
opacity 1
pointer-events initial
.header
margin-right 16px
.menu
opacity 1

View File

@ -1,55 +0,0 @@
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap, ZapOff } from 'lucide-react'
import Component from '.'
import Text from '../Text'
import Col from '../Col'
import Row from '../Row'
export default {
title: 'DZEIO/Navbar',
component: Component,
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Navbar: Story<any> = (args: any) => <Component {...args} />
Navbar.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: 'Child 1'
}, {
name: 'Child with link',
path: '/dashboard'
}]
}, {
path: '/dashboard',
name: 'Link',
icon: ZapOff
}],
children: (
<Row align='center'>
<Col><Text>Test</Text></Col>
<Col><Text>Test</Text></Col>
<Col><Text>Test</Text></Col>
<Col><Text>Test</Text></Col>
</Row>
)
}

View File

@ -1,201 +0,0 @@
import Router from 'next/router'
import { ChevronDown, Menu as LucideMenu } from 'lucide-react'
import Image, { ImageProps } from 'next/image'
import React from 'react'
import Col from '../Col'
import Link from '../Link'
import Menu from '../Menu'
import Row from '../Row'
import Sidebar from '../Sidebar'
import Text from '../Text'
import { buildClassName } from '../Util'
import Button from '../Button'
import { Icon } from '../interfaces'
import css from './Navbar.module.styl'
interface MenuItem {
path?: string
icon?: Icon
name: string
subMenu?: Array<MenuItem>
mobileOnly?: boolean
}
interface Props {
/**
* Logo to display
*/
logo?: ImageProps & {height: number, width: number, name?: string}
/**
* Login URL
*/
loginUrl?: string
/**
* Login URL
*/
registerUrl?: string
/**
* User Informations if loggedin
*/
user?: {
/**
* Username
*/
name: string
/**
* User Menu
*/
menu?: Array<MenuItem>
}
/**
* Links to display
*/
menu: Array<MenuItem>
children?: React.ReactNode
}
interface State {
path?: string
short: boolean
isMobile: boolean
menuActive: boolean
subMenu?: {
x: number
menu: Menu['props']['items']
}
}
/**
* Navbar Component
* @version 1.0.4
*/
export default class Navbar extends React.Component<Props, State> {
public state: State = {
short: false,
isMobile: false,
menuActive: false
}
public componentDidMount() {
this.setState({
path: Router.asPath,
menuActive: false
})
Router.events.on('routeChangeComplete', () => {
this.setState({path: Router.asPath, menuActive: false})
})
Router.events.on('routeChangeError', () => {
this.setState({path: Router.asPath, menuActive: false})
})
document.body.classList.add(css['body-navbar'])
document.body.addEventListener('click', this.onBodyClick)
window.addEventListener('resize', this.onResize)
this.onResize()
}
public onResize = () => {
const isMobile = window.innerWidth <= 768
if (this.state.isMobile !== isMobile) {
this.setState({isMobile})
}
}
public componentWillUnmount() {
document.body.classList.remove(css['body-sidebar'])
document.body.removeEventListener('click', this.onBodyClick)
window.removeEventListener('resize', this.onResize)
}
public menuCloseCallback = () => {
this.setState({menuActive: false})
return true
}
public render = () => (
<>
<nav className={css.navbar}>
<Row nowrap className={css.header} align="center">
{this.props.logo && (
<Col className={css.imgContainer}>
<Link href="/" linkProps={{"aria-label": this.props.logo.name}}>
<Image {...this.props.logo} height={34} width={this.props.logo.width*34/this.props.logo.height} />
</Link>
</Col>
)}
</Row>
{this.props.children}
{/* Spacer */}
<div style={{flex: 1}}></div>
{/* Menu */}
{!this.state.isMobile && (
<ul>
{this.props.menu.filter((it) => !it.mobileOnly).map((item) => (
<li key={item.path}><Button type="ghost" href={item.path} icon={item.icon} onClick={item.subMenu ? this.onClick(item.subMenu) : undefined}>{item.name}</Button></li>
))}
{this.props.user && (
<li>
<Button type="ghost" iconLeft={ChevronDown} onClick={this.props.user.menu ? this.onClick(this.props.user.menu) : undefined}>{this.props.user.name}</Button>
</li>
)}
</ul>
)}
{/* Menu Icon */}
{this.state.isMobile && (
<div className={css.userSpaceParent}>
<div onClick={() => this.setState({menuActive: !this.state.menuActive})} className={css.userSpace}>
<Text>
<LucideMenu size={38} className={css.mainGradient} />
</Text>
</div>
</div>
)}
</nav>
{this.state.isMobile && (
<div className={buildClassName(css.mobileMenu, [css.shown, this.state.menuActive])}>
<Sidebar fullWidth {...this.props} onClose={this.menuCloseCallback} menu={this.props.menu} />
</div>
)}
{this.state.subMenu && (
<div style={{position: 'fixed', top: 76, right: this.state.subMenu.x, zIndex: 1}}>
<Menu className={css.menu} outline items={this.state.subMenu.menu} />
</div>
)}
</>
)
private onBodyClick = (ev: MouseEvent) => {
let target = ev.target as HTMLElement | null
do {
if (target && target.classList.contains(css.menu)) {
return
}
target = target?.parentElement as HTMLElement | null
} while (target)
this.setState({subMenu: undefined})
}
private onClick = (subMenu?: Array<MenuItem>) => (ev: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => {
ev.stopPropagation()
const x = window.innerWidth - (ev.currentTarget.offsetLeft + ev.currentTarget.offsetWidth)
if (subMenu && (!this.state.subMenu || x !== this.state.subMenu?.x)) {
this.setState({
subMenu: {
x,
menu: subMenu.map((v) => ({
display: v.name,
value: v.path,
href: v.path
}))
}
})
} else {
this.setState({subMenu: undefined})
}
}
}

View File

@ -1,13 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
import Text from '../Text'
export default {
title: 'DZEIO/Popup',
component: Component
} as Meta
export const Popup = (args: any) => (
<Component><Text>Test</Text></Component>
)

View File

@ -1,25 +0,0 @@
@import '../config'
.bar
width 100%
background nativeRGBA(var(--theme-500), 0.15)
height 8px
border-radius 8px
&.noBorder
border-radius 0
div
border-radius 0px 8px 8px 0px
&[style="width: 100%;"]
border-radius 0
div
transition width,border-radius
transition-duration $transitionTime
transition-timing-function $transitionFunction
height 100%
max-width 100%
width 0
background var(--theme-500)
border-radius 8px

View File

@ -1,19 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Progress Bar',
component: Component,
argTypes: {
progress: { control: 'number', defaultValue: 0},
noRoundBorders: { control: 'boolean'},
},
parameters: {
layout: 'fullscreen'
}
} as Meta
export const ProgressBar = (args: any) => (
<Component {...args} />
)

View File

@ -1,29 +0,0 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './ProgressBar.module.styl'
interface Props {
/**
* Number between 0 and 100%
*/
progress: number
/**
* disable the round borders
*/
noRoundBorders?: boolean
className?: string
}
/**
* Display a simple customizable Progress bar
*
* @version 1.0.0
*/
export default class extends React.Component<Props> {
public render = () => (
<div className={buildClassName(css.bar, [css.noBorder, this.props.noRoundBorders], this.props.className)}>
<div style={{ width: `${this.props.progress}%`}}></div>
</div>
)
}

View File

@ -1,4 +0,0 @@
@import '../config'
.padding
padding 24px

View File

@ -1,20 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import Component from '.'
export default {
title: 'DZEIO/Progress Box',
component: Component,
argTypes: {
progress: { control: 'number', defaultValue: 0},
text: { control: 'text'},
textProgress: { control: 'text'},
},
parameters: {
layout: 'fullscreen'
}
} as Meta
export const ProgressBox = (args: any) => (
<Component {...args} />
)

View File

@ -1,49 +0,0 @@
import React from 'react'
import Box from '../Box'
import Col from '../Col'
import ProgressBar from '../ProgressBar'
import Row from '../Row'
import Text from '../Text'
import css from './ProgressBox.module.styl'
interface Props {
/**
* Number between 0 and 100%
*/
progress: number
/**
* Text displayed in the middle
*/
text: string
/**
* text displayed in the right of the box
*/
textProgress: string
}
/**
* Display a simple Progress box that can be used for multiple things
*
* @version 1.0.0
*/
export default class extends React.Component<Props> {
public render = () => (
<Box noPadding noBottomBorder>
<Row className={css.padding}>
<Col nogrow>
<Text color="main">{this.props.progress}%</Text>
</Col>
<Col>
<Text weight="bold" align="center">{this.props.text}</Text>
</Col>
<Col nogrow>
<Text color="main">{this.props.textProgress}</Text>
</Col>
</Row>
<ProgressBar noRoundBorders progress={this.props.progress} />
</Box>
)
}

View File

@ -1,11 +0,0 @@
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
export default {
title: 'DZEIO/Scrollbar',
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Scrollbar: Story<any> = (args: any) => <div style={{height: '1000vh'}} />

View File

@ -1,194 +0,0 @@
@import '../config'
// $transition = 10s linear
// $transitionTime = 10s
// $transitionFunction = linear
.sidebarBody
margin-left 300px
transition margin-left $transition
&.short
margin-left 56px
.sidebar
background var(--theme-50)
@media (prefers-color-scheme dark)
background var(--gray-800)
position fixed
left 0
top 0
padding 24px
height 100vh
width 300px
&.fullWidth
width 100%
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
cursor pointer
div, p
transition all $transition
p
overflow hidden
white-space nowrap
>div:last-child p
padding 8px 8px 4px
border-radius 8px
&:hover
color black
background var(--theme-100)
@media (prefers-color-scheme dark)
color white
background var(--gray-700)
&:active
color white
background var(--theme-500)
&.short
width 88px
.userSpace > div:not(:last-child)
.header > div:first-child
width 0
max-width 0
padding-left 0
.header
margin-left -16px
> div:first-child
padding-left 0 !important
.header > div svg
.header .imgContainer
> ul
span
svg:last-child
width 0
padding-left 0
padding-right 0
margin 0 !important
max-width 0
svg line:first-child
transform rotateX(0) !important
.header
margin-left -8px
//min-height 70px
svg
margin-right 8px
line:first-child
transition transform $transition
transform rotateZ(90deg)
min-height 24px
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 black
@media (prefers-color-scheme dark)
background white
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 black
@media (prefers-color-scheme dark)
color white
&:first-child
margin-top 0
&:hover
color black
background var(--theme-100)
@media (prefers-color-scheme dark)
color white
background var(--gray-700)
&:active
color white
background var(--theme-500)
padding 8px
display flex
align-items center
z-index 111
position relative
svg
transition color $transition, transform $transition
&:first-child + span
margin-left 16px
span
display inline-block
white-space nowrap
a
width 100%
display flex
//max-height 24px
&.link
padding 0
a
padding 8px
&.active > div
color white
background var(--theme-500)
ul
list-style none
margin 0
padding 0
.userMenu
position fixed
bottom 16px
left 316px
z-index 200
&.short
left 104px
&.fullWidth
left 16px
width calc(100% - 32px)

View File

@ -1,54 +0,0 @@
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: {
picture: '/16-16.svg',
name: 'Username',
menu: [{
path: '/logout',
name: 'Logout'
}, {
path: '/mock-route',
name: 'Mock Route'
}]
},
menu: [{
name: 'Dasboard',
icon: Zap
}, {
name: 'With Childs',
icon: Zap,
subMenu: [{
name: 'Child 1'
}, {
name: 'Mock Route',
path: '/mock-route'
}]
}, {
name: 'With Childs2',
icon: Zap,
subMenu: [{
name: 'Child 1'
}, {
name: 'Child with link',
path: '/mock-route2'
}]
}, {
path: '/dashboard',
name: 'Link',
icon: ZapOff
}],
// fullWidth: true
}

View File

@ -1,248 +0,0 @@
import React, { MouseEvent } from 'react'
import Router from 'next/router'
import { ChevronDown, MoreHorizontal, Plus } from 'lucide-react'
import Text from '../Text'
import Col from '../Col'
import Row from '../Row'
import Link from '../Link'
import Image from '../Image'
import { buildClassName } from '../Util'
import css from './Sidebar.module.styl'
import { Icon } from '../interfaces'
import Menu from '../Menu'
interface MenuItem {
path?: string
icon?: Icon
name: string
}
interface Props {
/**
* Logo to display
*/
logo?: Image['props']['imageProps'] & {height: number, width: number}
/**
* User Informations if loggedin
*/
user?: {
picture?: string
/**
* Username
*/
name: string
/**
* User Menu
*/
menu?: Array<MenuItem>
}
/**
* Links to display
*/
menu: Array<MenuItem & {subMenu?: Array<MenuItem>}>
onClose?: () => boolean
fullWidth?: boolean
}
interface State {
path?: string
/**
* Define if the menu is open or closed
*
* in mobile it will be hidden when closed
*/
open: boolean
activeMenu?: string
userMenu?: boolean
subMenu?: {
y: number
menu: Menu['props']['items']
}
}
/**
* Sidebar Component
* @version 1.0.0
*/
export default class Sidebar extends React.Component<Props, State> {
public state: State = {
open: true
}
public componentDidMount() {
this.onRouteChange(Router.asPath)
Router.events.on('routeChangeComplete', () => {
this.onRouteChange(Router.asPath)
})
Router.events.on('routeChangeError', () => {
this.onRouteChange(Router.asPath)
})
if (!this.props.fullWidth) {
document.body.classList.add(css.sidebarBody)
}
document.body.addEventListener('click', this.onBodyClick)
}
private onRouteChange(newRoute: string) {
let activeMenu = undefined
for (const menu of this.props.menu) {
if (newRoute === menu.path || menu.subMenu?.find((it) => newRoute === it.path)) {
activeMenu = menu.name + (menu.path ?? '')
}
}
this.setState({path: newRoute, subMenu: undefined, userMenu: false, activeMenu})
}
public componentDidUpdate() {
//console.log(this.state.path)
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,
href: v.path,
selected: this.state.path === 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],
[css.fullWidth, this.props.fullWidth]
)}>
<Row nowrap justify="space-between" className={css.header} align="center">
<Col>
{this.props.logo && (
<Link href="/">
<Image imageProps={{ ...this.props.logo, height: 34, width: this.props.logo.width * 34 / this.props.logo.height }} />
</Link>
)}
</Col>
<Col nogrow><Text tag="div">
<Plus size={24} onClick={this.onCloseOpenClick} />
</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} align="center" onClick={(ev) => {ev.stopPropagation(); this.setState({userMenu: !this.state.userMenu})}}>
{this.props.user.picture && (
<Col nogrow><Image imageProps={{ src: this.props.user.picture, width: 38, height: 38 }} /></Col>
)}
<Col><Text>{this.props.user.name}</Text></Col>
<Col nogrow><Text><MoreHorizontal size={24} /></Text></Col>
</Row>
)}
</nav>
{this.props.user?.menu && this.state.userMenu && (
<div className={buildClassName(css.userMenu, [css.fullWidth, this.props.fullWidth], [css.short, !this.state.open])}>
<Menu onClick={this.onMenuClick} outline items={this.props.user.menu.map((v) => ({
display: v.name,
value: v.path,
href: v.path,
selected: this.state.path === v.path
}))} />
</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 onCloseOpenClick = () => {
let willBeOpen = !this.state.open
if (this.props.onClose && !willBeOpen) {
willBeOpen = this.props.onClose()
}
this.setState(!willBeOpen ? {open: false, activeMenu: undefined} : {open: true})
}
private onMenuClick = (value?: string) => {
this.setState({userMenu: false, subMenu: undefined})
if (value) {
Router.push(value)
}
}
private makeMenuItem(obj: MenuItem & {subMenu?: Array<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} />
)}
</>
)
const isActive = this.state.path === obj.path || obj.subMenu?.find((it) => this.state.path === it.path)
return <li
key={id}
className={buildClassName(
[css.active, isActive],
[css.activeMenu, id === this.state.activeMenu && this.state.open]
)}
onClick={isSub ? undefined : this.onClick(id, obj.subMenu)}
>
<div
className={buildClassName([css.link, obj.path])}
>
{obj.path ? (
<Link noStyle href={obj.path}>
{content}
</Link>
) : content}
</div>
{obj.subMenu && (
<ul>
{obj.subMenu.map((it) => this.makeMenuItem(it, true))}
</ul>
)}
</li>
}
}

View File

@ -1,29 +0,0 @@
@import '../config'
.table
border-spacing 0
width 100%
tr:not(:last-child) td
border-bottom 1px solid var(--gray-600)
tr th
border-bottom 2px solid var(--gray-600)
tr td:not(:first-child)
tr th:not(:first-child)
border-left 1px solid var(--gray-600)
th
font-weight bold
font-size rem(18)
th
td
padding 16px
text-align left
color black
@media (prefers-color-scheme dark)
color white
.parent
overflow-x auto

View File

@ -1,49 +0,0 @@
import { Meta } from '@storybook/react/types-6-0'
import { Settings } from 'lucide-react'
import React from 'react'
import Component from '.'
import Box from '../Box'
export default {
title: 'DZEIO/Table',
component: Component
} as Meta
export const Table = (args: any) => (
<Box icon={Settings} title="Table">
<Component {...args}>
<thead>
<tr>
<th>item1</th>
<th>item1</th>
<th>item1</th>
<th>item1</th>
<th>item1</th>
</tr>
</thead>
<tbody>
<tr>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
</tr>
<tr>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
</tr>
<tr>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
<td>item1</td>
</tr>
</tbody>
</Component>
</Box>
)

View File

@ -1,30 +0,0 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './Table.module.styl'
interface Props {
children: React.ReactNode
parentClassName?: string
className?: string
horizontalBorders?: boolean
verticalBorders?: boolean
}
export default class Table extends React.Component<Props> {
public render = () => (
<div className={buildClassName(
css.parent,
this.props.parentClassName
)}>
<table className={buildClassName(
css.table,
this.props.className,
[css.horizontalBorders, this.props.horizontalBorders],
[css.verticalBorders, this.props.verticalBorders]
)}>{this.props.children}</table>
</div>
)
}

View File

@ -1,44 +0,0 @@
@import "../config"
.text
margin 0
font-size rem(16)
font-weight normal
white-space pre-wrap
+ .text
margin-top 8px
.white
color white
.black
color black
.main
color var(--theme-500)
for size in 36 28 24 20 18 16 14
.size-{size}
font-size rem(size)
for weight in 'normal' 'bold'
.weight-{weight}
font-weight unquote(weight)
.weight-light
font-weight 300
.align-center
text-align center
.align-right
text-align right
.align-left
text-align left
@media (prefers-color-scheme dark)
.white:not(.noDarkTheme)
color black
.black:not(.noDarkTheme)
color white

View File

@ -1,93 +0,0 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './Text.module.styl'
type Types = 'hero' | 'h1' | 'h2' | 'h3' | 'h4' | 'text' | 'light' | 'bold'
interface Props {
color?: 'black' | 'white' | 'none' | 'main'
type?: Types
tag?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'em' | 'span' | 'div'
weight?: 'normal' | 'bold' | 'light'
size?: 36 | 28 | 24 | 20 | 18 | 16 | 14
className?: string
noDarkTheme?: boolean
align?: 'left' | 'right' | 'center'
children: React.ReactNode
textProps?: React.DetailedHTMLProps<React.AnchorHTMLAttributes<HTMLElement>, HTMLElement>
}
const types: Record<Types, {
tag?: Props['tag']
size?: Props['size']
weight?: Props['weight']
}> = {
hero: {
size: 36,
weight: 'bold'
},
h1: {
size: 28,
weight: 'bold',
tag: 'h1'
},
h2: {
size: 24,
weight: 'bold',
tag: 'h2'
},
h3: {
size: 20,
weight: 'bold',
tag: 'h3'
},
h4: {
size: 18,
weight: 'bold',
tag: 'h4'
},
text: {},
bold: {
weight: 'bold'
},
light: {
weight: 'light',
size: 14
}
}
/**
* Display Text lol
*
* @version 1.0.1
*/
export default class Text extends React.Component<Props> {
public render() {
let data: { weight: Props['weight'], size: Props['size'], tag?: Props['tag'] } = Object.assign({
size: 16,
weight: 'normal'
}, this.props.type ? types[this.props.type] : {})
if (this.props.size) {
data.size = this.props.size
}
if (this.props.weight) {
data.weight = this.props.weight
}
const classes = buildClassName(
css.text,
css[this.props.color ?? 'black'],
[css[`weight-${data.weight}`], data.weight !== 'normal'],
[css[`size-${data.size}`], data.size !== 16],
[css.noDarkTheme, this.props.noDarkTheme],
[css[`align-${this.props.align}`], this.props.align],
this.props.className
)
if (this.props.tag === 'em') {
return <p className={classes}><em>{this.props.children}</em></p>
}
return React.createElement(this.props.tag ?? data.tag ?? 'p', {...this.props.textProps, className: classes, children: this.props.children})
}
}

View File

@ -1,355 +0,0 @@
// quick function to transform hex to rgb
hextorgb(color)
red(color) unquote(', ') green(color) unquote(', ') blue(color)
generateTheme($name, c50, c100, c200, c300, c400, c500, c600, c700, c800, c900)
{"--" + $name + "-50"}: c50
{"--" + $name + "-100"}: c100
{"--" + $name + "-200"}: c200
{"--" + $name + "-300"}: c300
{"--" + $name + "-400"}: c400
{"--" + $name + "-500"}: c500
{"--" + $name + "-600"}: c600
{"--" + $name + "-700"}: c700
{"--" + $name + "-800"}: c800
{"--" + $name + "-900"}: c900
{"--" + $name + "-50-rgb"}: hextorgb(c50)
{"--" + $name + "-100-rgb"}: hextorgb(c100)
{"--" + $name + "-200-rgb"}: hextorgb(c200)
{"--" + $name + "-300-rgb"}: hextorgb(c300)
{"--" + $name + "-400-rgb"}: hextorgb(c400)
{"--" + $name + "-500-rgb"}: hextorgb(c500)
{"--" + $name + "-600-rgb"}: hextorgb(c600)
{"--" + $name + "-700-rgb"}: hextorgb(c700)
{"--" + $name + "-800-rgb"}: hextorgb(c800)
{"--" + $name + "-900-rgb"}: hextorgb(c900)
{"--" + $name + "-50-text"}: light(c50) ? black : white
{"--" + $name + "-100-text"}: light(c100) ? black : white
{"--" + $name + "-200-text"}: light(c200) ? black : white
{"--" + $name + "-300-text"}: light(c300) ? black : white
{"--" + $name + "-400-text"}: light(c400) ? black : white
{"--" + $name + "-500-text"}: light(c500) ? black : white
{"--" + $name + "-600-text"}: light(c600) ? black : white
{"--" + $name + "-700-text"}: light(c700) ? black : white
{"--" + $name + "-800-text"}: light(c800) ? black : white
{"--" + $name + "-900-text"}: light(c900) ? black : white
:root
// gray is not a theme
generateTheme(
'gray',
#FAFAFA,
#F5F5F5,
#EEEEEE,
#E0E0E0,
#BDBDBD,
#9E9E9E,
#757575,
#616161,
#424242,
#212121
)
:root
:root.theme-base-red
.red
.red *
generateTheme(
'theme',
#FFEBEE,
#FFCDD2,
#EF9A9A,
#E57373,
#EF5350,
#F44336,
#E53935,
#D32F2F,
#C62828,
#D50000
)
:root.theme-base-pink
.pink
.pink *
generateTheme(
'theme',
#FCE4EC,
#F8BBD0,
#F48FB1,
#F06292,
#EC407A,
#E91E63,
#D81B60,
#C2185B,
#AD1457,
#880E4F
)
:root.theme-base-purple
.purple
.purple *
generateTheme(
'theme',
#F3E5F5,
#E1BEE7,
#CE93D8,
#BA68C8,
#AB47BC,
#9C27B0,
#8E24AA,
#7B1FA2,
#6A1B9A,
#4A148C
)
.deep-purple
.deep-purple *
generateTheme(
'theme',
#EDE7F6,
#D1C4E9,
#B39DDB,
#9575CD,
#7E57C2,
#673AB7,
#5E35B1,
#512DA8,
#4527A0,
#311B92
)
:root.theme-base-indigo
.indigo
.indigo *
generateTheme(
'theme',
#E8EAF6,
#C5CAE9,
#9FA8DA,
#7986CB,
#5C6BC0,
#3F51B5,
#3949AB,
#303F9F,
#283593,
#1A237E
)
:root.theme-base-blue
.blue
.blue *
generateTheme(
'theme',
#E3F2FD,
#BBDEFB,
#90CAF9,
#64B5F6,
#42A5F5,
#2196F3,
#1E88E5,
#1976D2,
#1565C0,
#0D47A1
)
.light-blue
.light-blue *
generateTheme(
'theme',
#E1F5FE,
#B3E5FC,
#81D4FA,
#4FC3F7,
#29B6F6,
#03A9F4,
#039BE5,
#0288D1,
#0277BD,
#01579B
)
:root.theme-base-cyan
.cyan
.cyan *
generateTheme(
'theme',
#E0F7FA,
#B2EBF2,
#80DEEA,
#4DD0E1,
#26C6DA,
#00BCD4,
#00ACC1,
#0097A7,
#00838F,
#006064
)
:root.theme-base-teal
.teal
.teal *
generateTheme(
'theme',
#E0F2F1,
#B2DFDB,
#80CBC4,
#4DB6AC,
#26A69A,
#009688,
#00897B,
#00796B,
#00695C,
#004D40
)
:root.theme-base-green
.green
.green *
generateTheme(
'theme',
#E8F5E9,
#C8E6C9,
#A5D6A7,
#81C784,
#66BB6A,
#4CAF50,
#43A047,
#388E3C,
#2E7D32,
#1B5E20
)
:root.theme-base-light-green
.light-green
.light-green *
generateTheme(
'theme',
#F1F8E9,
#DCEDC8,
#C5E1A5,
#AED581,
#9CCC65,
#8BC34A,
#7CB342,
#689F38,
#558B2F,
#33691E
)
:root.theme-base-lime
.lime
.lime *
generateTheme(
'theme',
#F9FBE7,
#F0F4C3,
#E6EE9C,
#DCE775,
#D4E157,
#CDDC39,
#C0CA33,
#AFB42B,
#9E9D24,
#827717
)
:root.theme-base-yellow
.yellow
.yellow *
generateTheme(
'theme',
#FFFDE7,
#FFF9C4,
#FFF59D,
#FFF176,
#FFEE58,
#FFEB3B,
#FDD835,
#FBC02D,
#F9A825,
#F57F17
)
:root.theme-base-amber
.amber
.amber *
generateTheme(
'theme',
#FFF8E1,
#FFECB3,
#FFE082,
#FFD54F,
#FFCA28,
#FFC107,
#FFB300,
#FFA000,
#FF8F00,
#FF6F00
)
:root.theme-base-orange
.orange
.orange *
generateTheme(
'theme',
#FFF3E0,
#FFE0B2,
#FFCC80,
#FFB74D,
#FFA726,
#FF9800,
#FB8C00,
#F57C00,
#EF6C00,
#E65100
)
:root.theme-base-deep-orange
.deep-orange
.deep-orange *
generateTheme(
'theme',
#FBE9E7,
#FFCCBC,
#FFAB91,
#FF8A65,
#FF7043,
#FF5722,
#F4511E,
#E64A19,
#D84315,
#BF360C
)
:root.theme-base-brown
.brown
.brown *
generateTheme(
'theme',
#EFEBE9,
#D7CCC8,
#BCAAA4,
#A1887F,
#8D6E63,
#795548,
#6D4C41,
#5D4037,
#4E342E,
#3E2723
)
:root.theme-base-blue-gray
.blue-gray
.blue-gray *
generateTheme(
'theme',
#ECEFF1,
#CFD8DC,
#B0BEC5,
#90A4AE,
#78909C,
#607D8B,
#546E7A,
#455A64,
#37474F,
#263238
)
:root.theme-base-gray
.gray
.gray *
generateTheme(
'theme',
#FAFAFA,
#F5F5F5,
#EEEEEE,
#E0E0E0,
#BDBDBD,
#9E9E9E,
#757575,
#616161,
#424242,
#212121
)

View File

@ -1,26 +0,0 @@
// native RGBA function to replace Stylus rgba function
nativeRGBA(color, opacity)
unquote('rgba(') color unquote(', ') opacity unquote(');')
nativeRGB(color)
unquote('rgb(') color unquote(');')
$transitionTime = .15s
$transitionFunction = ease-in-out
$transition = $transitionTime $transitionFunction
// Breakpoints
$mobile = 768px
$tablet = 1200px
// Row/Col
$totalGapSize = 10%
$colCount = 12
$colCountTablet = 8
$colCountMobile = 4
$gapSize = 16px
rem($a)
($a / 16)rem

View File

@ -0,0 +1,33 @@
@import "../config"
.box
background $foregroundLight
@media (prefers-color-scheme dark)
background $foregroundDark
border-radius 8px
.outline
border 2px solid $grayDark
@media (prefers-color-scheme dark)
border-color $grayLight
.header
padding 16px
+ .body
padding-top 0
.title
font-weight bold
font-size rem(18)
margin 0 0 8px
.subtitle
font-size rem(16)
margin 0
// BODY
.body
padding 16px

71
src/dzeio/Box/index.tsx Normal file
View File

@ -0,0 +1,71 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './Box.module.styl'
import Row from '../Row'
import Col from '../Col'
import Text from '../Text'
interface Props {
// Wrapper
wrapperProps?: Omit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, 'className'>
outline?: boolean
/**
* @deprecated use wrapperProps.onClick
*/
onClick?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>['onClick']
className?: string
// Header
title?: string
titleColSize?: number
subtitle?: string
delimiter?: boolean
titleClassName?: string
headerButtons?: React.ReactNode
// Body
noPadding?: boolean
}
export default class Box extends React.Component<Props> {
public render = () => (
<div
{...this.props.wrapperProps}
onClick={this.props.onClick}
className={buildClassName(css.box, this.props.className, [css.outline, this.props.outline])}
>
{(this.props.headerButtons || this.props.title || this.props.titleColSize || this.props.subtitle || this.props.delimiter || this.props.titleClassName) && (
<div className={buildClassName(
css.header
)}>
<Row nomargin justify="space-between">
<Col>
{this.props.title && (
<Text className={buildClassName(css.title, this.props.titleClassName)}>{this.props.title}</Text>
)}
{this.props.subtitle && (
<Text className={css.subtitle}>{this.props.subtitle}</Text>
)}
</Col>
{this.props.children && (
<Col nogrow>
<Row justify="flex-end">
{this.props.headerButtons}
</Row>
</Col>
)}
</Row>
</div>
)}
{this.props.children && (
<div className={buildClassName([css.body, !this.props.noPadding])}>
{this.props.children}
</div>
)}
</div>
)
}

View File

@ -0,0 +1,184 @@
@import '../config'
.button
font-size rem(16)
position relative
transition all $transition
font-weight 600
line-height 1.5
display inline-flex
padding 10px 20px
margin 8px 8px 0 8px
cursor pointer
align-items center
text-align center
border-radius 4px
border none
justify-content center
align-items center
color $textOnMain
background-color $main
// Chrome Specific
outline none
// Link specific
text-decoration none
&.outline
border 2px solid @background-color
padding 8px 18px // @padding - @border
background transparent
color @background-color
&:hover
&:active
&:focus
color $textOnMain
&:hover
background-color @background-color
transform translateY(-2px)
box-shadow 0 4px 4px rgba(@background-color,.2)
&:active
&:focus
background-color darken(@background-color, 30%)
&.block
display flex
width 100%
margin 0
margin-top 8px
&.large
padding 15px 30px
font-size rem(20)
&.outline
padding 13px 28px // @padding - @border
&.small
padding 5px 10px
font-size rem(14)
&.outline
padding 3px 8px // @padding - @border
&.nomargintop
margin-top 0
&:disabled
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
position relative
pointer-events none
&::after
content ""
display block
border $textOnMain 2px solid
border-color transparent transparent $textOnMain $textOnMain
width 1em
position absolute
top calc(50% - (1em / 2))
left calc(50% - (1em / 2))
border-radius 100%
height 1em
box-sizing inherit
animation ButtonLoading 1s infinite linear
.textInner
margin-left 8px
/**
* $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
color @background-color
border-color @background-color
// background none
&:hover
&:active
&:focus
color $textColor
if $theme is 'darken'
&:active
&:focus
background-color darken(@color, 30%)
&:hover
background-color @background-color
box-shadow 0 4px 4px rgba(@background-color,.2)
&:active
&:focus
if $theme is 'darken'
background-color darken(@background-color, 30%)
else
background-color lighten(@background-color, 30%)
&.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%
transform rotate(0)
100%
transform rotate(365deg)
.img
min-width 16px

View File

@ -1,7 +1,6 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap } from 'lucide-react'
import Box from '../Box'
import Component from '.'
export default {
@ -9,24 +8,25 @@ export default {
component: Component
} as Meta
export const Button = (args: any) => <Box><Component {...args}>Button</Component></Box>
Button.args = {
export const Basic = (args: any) => <Component {...args}>Button</Component>
Basic.args = {
nomargintop: true,
icon: Zap,
mobileBlock: true,
iconLeft: Zap,
size: 'small'
size: 'small',
href: '/pouet',
block: true
}
export const WithImg = (args: any) => <Box><Component {...args}>Button</Component></Box>
export const WithImg = (args: any) => <Component {...args}>Button</Component>
WithImg.args = {
nomargintop: true,
icon: '/16-16.svg',
size: 'small',
href: '/pouet',
block: true
}
export const ExternalLinkButton = (args: any) => <Box><Component {...args}>Button</Component></Box>
export const ExternalLinkButton = (args: any) => <Component {...args}>Button</Component>
ExternalLinkButton.args = {
nomargintop: true,
href: 'https://example.com',

View File

@ -0,0 +1,71 @@
import React, { FC } from 'react'
import Link from '../Link'
import { ColorType, IconProps } from '../interfaces'
import { buildClassName } from '../Util'
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
color?: ColorType
children?: React.ReactNode
icon?: FC<IconProps> | string
size?: 'large' | 'small'
block?: boolean
href?: string
as?: string
disabled?: boolean
loading?: boolean
onClick?: (event: React.MouseEvent<HTMLButtonElement|HTMLAnchorElement, MouseEvent>) => void
}
export default class Button extends React.Component<Props> {
public render = () => {
let inner: any = this.props.children
if (!this.props.loading && this.props.icon) {
const Icon = this.props.icon
inner = (
<>
{typeof Icon === 'string' ? (
<Image imageProps={{src: Icon, width: 16, height: 16}} />
) : (
<Icon size={this.props.size === 'large' ? 20 : this.props.size === 'small' ? 14 : 16} />
)}
{this.props.children && (
<span className={css.textInner}>{this.props.children}</span>
)}
</>
)
}
const classes = buildClassName(
[css.button],
[css[this.props.color as string], this.props.color],
[css.outline, this.props.outline],
[css.block, this.props.block],
[css[this.props.size as string], this.props.size],
[css.nomargintop, this.props.nomargintop],
[css.loading, this.props.loading]
)
if (this.props.href) {
return (
<Link linkProps={{onClick: this.props.onClick}} hideIcon noStyle href={this.props.href} className={buildClassName([classes], [css.disabled, this.props.disabled])}>
{inner}
</Link>
)
}
return (
<button onClick={this.props.onClick} disabled={this.props.disabled} className={classes}>{inner}</button>
)
}
}

View File

@ -0,0 +1,146 @@
@import "../config.styl"
$backColor = #757575
.label
position relative
display flex
user-select none
align-items center
+ .label
margin-top 8px
p
margin-left 8px
span
top 0
left 0
width 20px
height @width
position relative
box-shadow inset 0 0 0 2px $grayDark
border-radius 2px
transition all $transition
@media (prefers-color-scheme dark)
box-shadow inset 0 0 0 2px $grayLight
&::after
border-radius 20px
position absolute
transition all $transition
background $main
svg
transition $transition
transform scale(0)
color transparent
margin 2px
input
// visibility hidden
position absolute
top 0
left 0
opacity 0
&:checked + span
background rgba($main, .5)
box-shadow inset 0 0 0 2px $main
svg
color white
transform scale(1)
&:hover
span
box-shadow inset 0 0 0 2px black
@media (prefers-color-scheme dark)
box-shadow inset 0 0 0 2px white
.radio
span
border-radius 20px
&::after
content " "
top 5px
left 5px
width 10px
height @width
transform scale(0)
input:checked + span::after
transform scale(1)
background white
.switch
padding 2px 0 2px 10px // 2px base padding 10px circle padding
&:hover span::after
background black
@media (prefers-color-scheme dark)
background white
span
width 28px
height 14px
border-radius 20px
top 50%
margin-right 10px
box-shadow none
background rgba($backColor, .5)
&::after
content " "
top 50%
transform translate(-50%, -50%)
left 0
background $backColor
width 20px
height @width
input
margin 0 8px
width 20px
&:checked + span
&::after
left 100%
transform translate(-50%, -50%)
background $main
checkBox($color)
input:checked + span
background rgba($color, .5)
box-shadow inset 0 0 0 2px $color
&::after
background $color
input:focus:checked + span
box-shadow inset 0 0 0 2px $color, 0 0 0 2px rgba($color,.3)
&.switch
input:checked + span
box-shadow none
.info
checkBox($infoLight)
@media (prefers-color-scheme dark)
checkBox($infoDark)
.success
checkBox($successLight)
@media (prefers-color-scheme dark)
checkBox($successDark)
.error
checkBox($errorLight)
@media (prefers-color-scheme dark)
checkBox($errorDark)
.warning
checkBox($warningLight)
@media (prefers-color-scheme dark)
checkBox($warningDark)

View File

@ -1,10 +1,10 @@
import { Meta } from '@storybook/react/types-6-0'
import React from 'react'
import CheckboxReact from '.'
import Checkbox from '.'
export default {
title: 'DZEIO/Checkbox',
component: CheckboxReact
component: Checkbox
} as Meta
export const Checkbox = (args: any) => <CheckboxReact {...args} />
export const Basic = (args: any) => <Checkbox {...args} />

View File

@ -1,43 +1,43 @@
import React from 'react'
import { Check } from 'lucide-react'
import { buildClassName } from '../Util'
import css from './Checkbox.module.styl'
import Text from '../Text'
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
label: string
type?: 'checkbox' | 'radio' | 'switch'
}
export default class Checkbox extends React.Component<Props> {
public render() {
const props: Partial<Props> = Object.assign({}, this.props)
delete props.label
delete props.type
const realType = this.props.type ?? 'checkbox'
return (
<label htmlFor={this.props.id ?? this.props.label} className={buildClassName(
[css.label],
[css.radio, realType === 'radio'],
[css.switch, realType === 'switch'],
[css[this.props.color as string], this.props.color]
)}>
<input {...props}
id={this.props.id ?? this.props.label}
type={realType === 'switch' ? 'checkbox' : realType}
/>
<span>
{realType === 'checkbox' && (
<Check strokeWidth={4} size={16}/>
)}
</span>
<Text>{this.props.label}</Text>
</label>
)
}
}
import React from 'react'
import { Check } from 'lucide-react'
import { buildClassName } from '../Util'
import css from './Checkbox.module.styl'
import Text from '../Text'
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
label: string
type?: 'checkbox' | 'radio' | 'switch'
}
export default class Checkbox extends React.Component<Props> {
public render() {
const props: Partial<Props> = Object.assign({}, this.props)
delete props.label
delete props.type
const realType = this.props.type ?? 'checkbox'
return (
<label htmlFor={this.props.id ?? this.props.label} className={buildClassName(
[css.label],
[css.radio, realType === 'radio'],
[css.switch, realType === 'switch'],
[css[this.props.color as string], this.props.color]
)}>
<input {...props}
id={this.props.id ?? this.props.label}
type={realType === 'switch' ? 'checkbox' : realType}
/>
<span>
{realType === 'checkbox' && (
<Check strokeWidth={4} size={16}/>
)}
</span>
<Text>{this.props.label}</Text>
</label>
)
}
}

View File

@ -0,0 +1,13 @@
.code
font-family 'source code pro', monospace
background #E8EAF6
padding 4px 8px
border-radius 8px
.pre
border-radius 8px
padding 4px 8px
background #E8EAF6
display block
.code
padding 0

View File

@ -10,7 +10,7 @@ export default {
}
} as Meta
export const Code = (args: any) => {
export const Basic = (args: any) => {
const content = args.content
delete args.content

View File

@ -4,7 +4,6 @@ import css from './Code.module.styl'
interface Props {
block?: boolean
children?: React.ReactNode
}
export default class Code extends React.Component<Props> {

View File

@ -4,6 +4,7 @@
max-width 100%
flex-basis 0
flex-grow 1
padding $gapSize 0 0 $gapSize
&.nogrow
max-width initial
@ -15,13 +16,12 @@ for i in (0...$colCount+1)
if i == 0
display none
else
$tmp = ((i / $colCount) * 100)%
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
min-width "calc(%s - %s)" % ($tmp $gapSize)
flex 0 0 ((i / 12) * 100)%
min-width ((i / 12) * 100)%
if i != $colCount and i != 0
.offset-{i}
margin-left ((i / $colCount) * 100)% + (i * $gapSize + $gapSize)px
margin-left ((i / 12) * 100)% + (i * $gapSize + $gapSize)px
@media (max-width $tablet)
.col.tabletGrow
@ -32,9 +32,8 @@ for i in (0...$colCount+1)
if i == 0
display none
else
$tmp = ((i / $colCountTablet) * 100)%
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
min-width "calc(%s - %s)" % ($tmp $gapSize)
flex 0 0 ((i / $colCountTablet) * 100)%
min-width ((i / $colCountTablet) * 100)%
if i != $colCountTablet and i != 0
.offset-tablet-{i}
@ -49,9 +48,8 @@ for i in (0...$colCount+1)
if i == 0
display none
else
$tmp = ((i / $colCountMobile) * 100)%
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
min-width "calc(%s - %s)" % ($tmp $gapSize)
flex 0 0 ((i / $colCountMobile) * 100)%
min-width ((i / $colCountMobile) * 100)%
if i != $colCountMobile and i != 0
.offset-tablet-{i}
margin-left ((i / $colCountMobile) * 100)% + (i * $gapSize - $gapSize)

View File

@ -1,9 +1,8 @@
import { objectOmit } from '@dzeio/object-util'
import React from 'react'
import { buildClassName } from '../Util'
import css from './Col.module.styl'
interface Props extends React.HTMLAttributes<HTMLDivElement> {
interface Props {
size?: 0|1|2|3|4|5|6|7|8|9|10|11|12
offset?: 1|2|3|4|5|6|7|8|9|10|11
children?: React.ReactNode
@ -22,25 +21,10 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
mobileGrow?: boolean
}
export default class Col extends React.Component<Props> {
public render = () => (
<div {...objectOmit(
this.props,
"size",
"offset",
"children",
"className",
"nogrow",
"tabletSize",
"tabletoffset",
"tabletGrow",
"mobileSize",
"mobileoffset",
"mobileGrow"
)} className={buildClassName(
<div className={buildClassName(
css.col,
// Normal

View File

@ -0,0 +1,2 @@
.container
padding 16px

View File

@ -0,0 +1,19 @@
import React from 'react'
import { buildClassName } from '../Util'
import css from './Container.module.styl'
interface Props {
children: React.ReactNode
className?: string
}
export default class Container extends React.Component<Props> {
public render = () => (
<div className={buildClassName(css.container, this.props.className)}>
{this.props.children}
</div>
)
}

View File

@ -1,8 +1,8 @@
@import '../config.styl'
.fieldset
border-radius 8px
border 2px solid var(--gray-500)
border-radius 4px
border 2px solid $grayDark
transition all $transition
margin 0
@ -12,3 +12,8 @@
padding 0 4px
@media (prefers-color-scheme dark)
color white
&:hover
border-color black
@media (prefers-color-scheme dark)
border-color white

View File

@ -1,33 +1,26 @@
@import '../config.styl'
.footer
padding 24px 0
background var(--theme-50)
padding 24px 16px
background $foregroundLight
@media (prefers-color-scheme dark)
background var(--gray-800)
background $foregroundDark
ul
padding 0
margin 0
display flex
justify-content right
justify-content center
li
display inline-block
padding-left 24px
+ .icon
padding-left 16px
&:not(.icon) + .icon
padding-left 48px
&.socials a
padding 0 8px
.animation
animation grow 1s linear infinite
display inline-block
vertical-align middle
margin 0 2px
width 16px
height @width
vertical-align sub
@keyframes grow
0%

View File

@ -1,22 +1,19 @@
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap } from 'lucide-react'
import Component from '.'
export default {
title: 'DZEIO/Footer',
component: Component,
parameters: {
layout: 'fullscreen'
}
} as Meta
export const Basic: Story<any> = (args: any) => <Component {...args} />
let tmp = Basic.bind({})
tmp.args = {
links: [{name: 'test1', path: '/'}, {name: 'test2', path: '/'}, {name: 'test3', path: '/'}],
socials: [{icon: Zap, href: '/'}, {icon: '/16-16.svg', href: '/'}, {icon: Zap, href: '/'}]
}
export const Normal = tmp
import { Meta, Story } from '@storybook/react/types-6-0'
import React from 'react'
import { Zap } from 'lucide-react'
import Component from '.'
export default {
title: 'DZEIO/Footer',
component: Component,
} as Meta
export const Basic: Story<any> = (args: any) => <Component {...args} />
let tmp = Basic.bind({})
tmp.args = {
links: [{name: 'test1', path: '/'}, {name: 'test2', path: '/'}, {name: 'test3', path: '/'}],
socials: [{icon: Zap, href: '/'}, {icon: '/16-16.svg', href: '/'}, {icon: Zap, href: '/'}]
}
export const Normal = tmp

View File

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

View File

@ -0,0 +1,30 @@
@import "../config"
$percent = 15%
.back
transition all $transition
background $mainGradient
&.fullscreen > :first-child
min-height 100vh
.info
background linear-gradient(to right, $infoLight, lighten($infoLight, $percent))
@media (prefers-color-scheme dark)
background linear-gradient(to right, $infoDark, darken($infoDark, $percent))
.success
background linear-gradient(to right, $successLight, lighten($successLight, $percent))
@media (prefers-color-scheme dark)
background linear-gradient(to right, $successDark, darken($successDark, $percent))
.error
background linear-gradient(to right, $errorLight, lighten($errorLight, $percent))
@media (prefers-color-scheme dark)
background linear-gradient(to right, $errorDark, darken($errorDark, $percent))
.warning
background linear-gradient(to right, $warningLight, lighten($warningLight, $percent))
@media (prefers-color-scheme dark)
background linear-gradient(to right, $warningDark, darken($warningDark, $percent))

View File

@ -0,0 +1,27 @@
import React from 'react'
import { ColorType } from '../interfaces'
import { buildClassName } from '../Util'
import css from './GradientBackground.module.styl'
interface Props {
color?: ColorType
className?: string
children: React.ReactNode
fullscreen?: boolean
}
/**
* Make the background a linear-gradient
*
* @version 1.0.2
*/
export default class GradientBackground extends React.Component<Props> {
public render = () => (
<div className={buildClassName(css.back, [css[this.props.color as string], this.props.color], this.props.className, [css.fullscreen, this.props.fullscreen])}>
{this.props.children}
</div>
)
}

View File

@ -69,7 +69,6 @@ export default class Image extends React.Component<Props, States> {
priority
quality={100}
{...this.props.imageProps}
layout={this.state.image ? 'fill' : this.props.imageProps.layout}
objectFit="contain"
/>
</div>

View File

@ -0,0 +1,265 @@
@import '../config'
.parent
position relative
margin 16px 0
max-width 100%
display inline-block
&:not(.block) + .parent:not(.block)
margin-left 16px
label
font-size 1rem
font-weight 600
color black
display inline-block
margin-bottom .5rem
transition all $transition
position absolute
top 16px
pointer-events none
left 16px
@media (prefers-color-scheme dark)
color white
svg
position absolute
color #AAA
transition color $transition
pointer-events none
top 14px
&.left
left 16px // input padding-left
~ label
left 16px + 24px + 10px
&.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
overflow-x hidden
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%)
div + .autocomplete
top calc(100% - 4px - .9em)
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 $grayDark
border-radius 4px
max-width 100%
box-sizing border-box
font-size .875rem
outline none
background transparent
transition all $transition
color black
@media (prefers-color-scheme dark)
border-color $grayLight
color white
&:not(:placeholder-shown)
&:focus
&:not([placeholder=" "])
~ label
top -8px
left 16px - 4px // .input/padding-left label/padding-left
background white
padding 0 4px
@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
&:not(:disabled)
&:hover
border-color black
@media (prefers-color-scheme dark)
border-color white
+ svg
color black
@media (prefers-color-scheme dark)
color white
&:focus
border-color $main
~ label
color @border-color
~ svg
color @border-color
&:invalid
border-color $errorDark
~ label
color @border-color
~ svg
color @border-color
@media (prefers-color-scheme dark)
border-color $errorLight
~ label
color @border-color
~ svg
color @border-color
&.iconLeft
padding-left 16px + 24px + 10px
&.iconRight
padding-right 16 + 24 + 10px
&.filled
border none
background rgba(gray, .1)
border-radius @border-radius @border-radius 0 0
border-bottom 2px solid rgba(black,.4)
@media (prefers-color-scheme dark)
background rgba(white, .1)
border-bottom 2px solid rgba(white,.4)
&.opaque
background white
@media (prefers-color-scheme dark)
background #202020
&:hover:not(:disabled)
background rgba(gray, .2)
@media (prefers-color-scheme dark)
background rgba(white, .2)
&.opaque
background darken(white, 5%)
@media (prefers-color-scheme dark)
background #1c1c1c
&:focus
background rgba(gray, .3)
border-bottom 2px solid $main
&.opaque
background white
@media (prefers-color-scheme dark)
background #202020
&:not(:placeholder-shown)
&:focus
&:not([placeholder=" "])
~ label
top 3px
left 16px - 4px // .input/padding-left label/padding-left
background transparent
padding 0
font-size .75rem
~ svg.left ~ label
left 16px + 24px + 10px // .input/padding-left label/padding-left
~ svg.rotate
transform rotateX(0)
transition $transition
&:focus ~ svg.rotate, ~.autocomplete:hover ~ svg.rotate
transform rotateX(180deg)
div
display flex
justify-content space-between
padding 0 16px
font-size .9em
&.block, &.block input, &.block select, &.block textarea
width 100%
display block

View File

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

247
src/dzeio/Input/index.tsx Normal file
View File

@ -0,0 +1,247 @@
import React, { FC } from 'react'
import { ChevronDown } from 'lucide-react'
import Text from '../Text'
import { IconProps } from '../interfaces'
import { buildClassName } from '../Util'
import css from './Input.module.styl'
interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> {
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' |
// Custom Types
'select' | 'textarea'
autocomplete?: Array<string>
infinityText?: string
filled?: boolean
opaque?: boolean
block?: boolean
children?: React.ReactNode
}
interface States {
charCount?: string
textAreaHeight?: number
value?: string
isInFirstPartOfScreen?: boolean
}
export default class Input extends React.Component<Props, States> {
public state: States = {}
// any because f*ck types
private inputRef: React.RefObject<any> = React.createRef()
private parentRef: React.RefObject<HTMLDivElement> = React.createRef()
public componentDidMount() {
if (this.props.characterCount) {
this.onChange()
}
if (this.props.type === 'textarea') {
this.textareaHandler()
}
if (this.props.autocomplete) {
window.addEventListener('scroll', this.parentScroll)
this.parentScroll()
}
}
public componentWillUnmount() {
if (this.props.autocomplete) {
window.removeEventListener('scroll', this.parentScroll)
}
}
public render() {
const props: Props = Object.assign({}, this.props)
delete props.label
delete props.children
delete props.icon
delete props.opaque
delete props.helper
delete props.infinityText
delete props.autocomplete
delete props.filled
delete props.iconRight
delete props.inputRef
delete props.selectRef
delete props.block
delete props.color
delete props.characterCount
const baseProps: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement> = {
placeholder: this.props.placeholder || ' ',
ref: this.props.inputRef || this.inputRef,
className: buildClassName(
[css.iconLeft, this.props.icon],
[css.iconRight, this.props.iconRight || this.props.autocomplete],
[css.filled, this.props.filled],
[css.opaque, this.props.opaque]
),
onInvalid: (ev: React.FormEvent<HTMLInputElement>) => ev.preventDefault(),
}
let input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>
if (this.props.type === 'number') {
baseProps.onWheel = (ev: React.WheelEvent<HTMLInputElement>) => ev.currentTarget.blur()
}
if (this.props.type === 'select' && !this.props.readOnly) {
input = (
<select
ref={this.props.selectRef || this.inputRef}
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLSelectElement>, HTMLSelectElement>}
className={buildClassName(
[css.iconLeft, this.props.icon],
[css.iconRight, !this.props.disabled || this.props.iconRight],
[css.filled, this.props.filled],
[css[this.props.color as string], this.props.color]
)}
>
{this.props.children}
</select>
)
// select is readonly
} else if (this.props.type === 'select') {
input = (
<input
{...props}
{...baseProps}
type="text"
/>
)
} else if (this.props.type === 'textarea') {
delete baseProps.ref
input = (
<textarea
{...props as React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>}
{...baseProps as any}
ref={this.inputRef}
style={{minHeight: this.state?.textAreaHeight}}
onKeyDown={this.textareaHandler}
onKeyUp={this.textareaHandler}
onFocus={this.textareaHandler}
/>
)
} else {
input = (
<input
{...props}
{...baseProps}
/>
)
}
return (
<div
className={buildClassName(
[css.parent],
[css.block, this.props.block]
)}
onChangeCapture={this.onChange}
ref={this.parentRef}
>
{input}
{/* 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>
<Text type="span">{this.props.helper}</Text>
{this.props.characterCount && (
<Text type="span">{this.state?.charCount}</Text>
)}
</div>
)}
{this.props.autocomplete && this.props.autocomplete.indexOf(this.state?.value ?? this.props.value?.toString() ?? '') === -1 && (
<ul className={buildClassName(css.autocomplete, [css.reverse, !this.state.isInFirstPartOfScreen])}>
{this.props.autocomplete.filter((item) => item.toLowerCase().includes(this.state?.value?.toLowerCase() ?? this.props.value?.toString().toLowerCase() ?? '')).map((item) => (<li key={item} onClick={this.onAutoCompleteClick(item)}><Text>{item}</Text></li>))}
</ul>
)}
</div>
)
}
private parentScroll = async () => {
const div = this.parentRef.current
if (!div) {return}
const result = !(div.offsetTop - window.scrollY >= window.innerHeight / 2)
// console.log(result, div, this.state.isInFirstPartOfScreen)
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) => async () => {
// 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}))
if (this.props.type === 'textarea') {
await this.parentScroll()
}
}
private onChange = async (event?: React.FormEvent<HTMLDivElement>) => {
if (this.props.characterCount) {
const max = this.props.maxLength || this.props.infinityText || 'Infinity'
const baseItem = this.props.value || this.props.defaultValue || ''
let currentCount = baseItem.toString().length
if (event) {
currentCount = (event.target as HTMLInputElement).value.length
}
this.setState({charCount: `${currentCount}/${max}`})
}
if (event) {
this.setState({value: (event.target as HTMLInputElement).value })
}
}
}

View File

@ -0,0 +1,12 @@
@import '../config'
.link
color $infoDark
@media (prefers-color-scheme dark)
color $infoLight
&:hover
text-decoration underline
.icon
vertical-align sub
margin 2px

View File

@ -13,4 +13,4 @@ export default {
}
} as Meta
export const Link = (args: any) => <Component {...args}>{args.text}</Component>
export const Basic = (args: any) => <Component {...args}>{args.text}</Component>

View File

@ -0,0 +1,19 @@
@import '../config'
.div
position fixed
left 0
width 100%
height 7px
pointer-events none
z-index 200
top 0
&.hide
transition top .5s ease-in-out
top -7px
div
&:not([style="width: 0px;"])
transition width 50ms ease-in-out
background rgba($main, .7)
height 100%

Some files were not shown because too many files have changed in this diff Show More