mirror of
https://github.com/dzeiocom/components.git
synced 2025-06-17 21:19:20 +00:00
Compare commits
1 Commits
v1-alpha
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
35a69f1a0d |
@ -1,5 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
"./node_modules/@dzeio/config/eslint/react-typescript"
|
|
||||||
]
|
|
||||||
}
|
|
5
.gitattributes
vendored
5
.gitattributes
vendored
@ -1,4 +1 @@
|
|||||||
* text=auto eol=lf
|
* text=lf
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
*.jpeg binary
|
|
||||||
|
17
.github/dependabot.yml
vendored
17
.github/dependabot.yml
vendored
@ -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"
|
|
29
.github/workflows/build.yml
vendored
29
.github/workflows/build.yml
vendored
@ -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
|
|
65
.github/workflows/codeql-analysis.yml
vendored
65
.github/workflows/codeql-analysis.yml
vendored
@ -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
|
|
37
.github/workflows/ossar-analysis.yml
vendored
37
.github/workflows/ossar-analysis.yml
vendored
@ -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 }}
|
|
28
.github/workflows/publish.yml
vendored
28
.github/workflows/publish.yml
vendored
@ -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
12
.gitignore
vendored
@ -1,7 +1,11 @@
|
|||||||
|
module/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
*.mjs
|
||||||
|
*.js
|
||||||
|
!src/dzeio/stylusUtils.js
|
||||||
|
*.d.ts
|
||||||
types/
|
types/
|
||||||
|
!rollup.config.js
|
||||||
index.es.js
|
!src/stylus.d.ts
|
||||||
index.umd.js
|
!.storybook/*.js
|
||||||
style.css
|
style.css
|
||||||
*.tgz
|
|
||||||
|
11
.npmignore
11
.npmignore
@ -1,13 +1,8 @@
|
|||||||
.github/
|
|
||||||
.storybook/
|
.storybook/
|
||||||
node_modules/
|
node_modules/
|
||||||
src/
|
*.stories.js
|
||||||
.editorconfig
|
|
||||||
.gitattributes
|
.gitattributes
|
||||||
.gitignore
|
.gitignore
|
||||||
.npmignore
|
.npmignore
|
||||||
LICENSE
|
yarn.lock
|
||||||
vite.config.js
|
yarn-error.log
|
||||||
package-lock.json
|
|
||||||
tsconfig.json
|
|
||||||
README.md
|
|
@ -1,17 +1,13 @@
|
|||||||
|
const path = require("path");
|
||||||
|
const webpack = require('webpack')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
"stories": [
|
"stories": [
|
||||||
"../src/**/*.stories.tsx",
|
"../src/dzeio/**/*.stories.tsx",
|
||||||
],
|
],
|
||||||
core: {
|
|
||||||
builder: "@storybook/builder-vite"
|
|
||||||
},
|
|
||||||
staticDirs: ["./public"],
|
|
||||||
"addons": [
|
"addons": [
|
||||||
"@storybook/addon-essentials"
|
"@storybook/addon-essentials"
|
||||||
],
|
],
|
||||||
reactOptions: {
|
|
||||||
strictMode: true
|
|
||||||
},
|
|
||||||
typescript: {
|
typescript: {
|
||||||
check: false,
|
check: false,
|
||||||
checkOptions: {},
|
checkOptions: {},
|
||||||
@ -20,5 +16,19 @@ module.exports = {
|
|||||||
shouldExtractLiteralValuesFromEnum: true,
|
shouldExtractLiteralValuesFromEnum: true,
|
||||||
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
17
.storybook/mockNextImage.js
Normal file
17
.storybook/mockNextImage.js
Normal 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>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -1,12 +1,8 @@
|
|||||||
import Router from 'next/router'
|
import Router from 'next/router';
|
||||||
|
|
||||||
Router.router = {
|
Router.router = {
|
||||||
push: async (route) => {
|
push: async () => {},
|
||||||
console.log('Pushing router to', route)
|
|
||||||
},
|
|
||||||
replace: async () => {},
|
replace: async () => {},
|
||||||
prefetch: () => {},
|
prefetch: () => {},
|
||||||
route: '/mock-route',
|
route: '/mock-route',
|
||||||
asPath: '/mock-route',
|
pathname: 'mock-path',};
|
||||||
pathname: 'mock-path',
|
|
||||||
}
|
|
37
.storybook/next.js
Normal file
37
.storybook/next.js
Normal 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;
|
||||||
|
},
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import '../src/general.styl'
|
import '../src/dzeio/general.styl'
|
||||||
import './mockNextRouter'
|
import './mockNextRouter'
|
||||||
|
import './mockNextImage'
|
||||||
|
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
layout: 'centered',
|
layout: 'centered'
|
||||||
actions: { argTypesRegex: '^on.*' }
|
|
||||||
}
|
}
|
21
LICENSE
21
LICENSE
@ -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.
|
|
46872
package-lock.json
generated
46872
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -1,40 +1,50 @@
|
|||||||
{
|
{
|
||||||
"name": "@dzeio/components",
|
"name": "@dzeio/components",
|
||||||
"version": "1.0.0-beta.23",
|
"version": "0.11.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "./index.umd.js",
|
"main": "./index.js",
|
||||||
"module": "./index.es.js",
|
|
||||||
"types": "./types/index.d.ts",
|
"types": "./types/index.d.ts",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@storybook/addon-essentials": "^6",
|
"@babel/core": "^7.12.16",
|
||||||
"@storybook/builder-vite": "^0.2.2",
|
"@babel/preset-env": "^7.12.16",
|
||||||
"@storybook/cli": "^6",
|
"@babel/preset-react": "^7.12.13",
|
||||||
"@storybook/react": "^6",
|
"@storybook/addon-essentials": "^6.1.14",
|
||||||
"@types/json5": "^2",
|
"@storybook/cli": "^6.1.14",
|
||||||
"@types/node": "^18",
|
"@storybook/react": "^6.1.14",
|
||||||
"@types/react": "^18",
|
"@types/node": "^15.12.1",
|
||||||
"@types/react-dom": "^18",
|
"@types/react": "^17.0.2",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"@types/react-dom": "^17.0.1",
|
||||||
"next": "^12",
|
"babel-loader": "^8.2.2",
|
||||||
"react": "^18",
|
"css-loader": "^5.0.2",
|
||||||
"react-dom": "^18",
|
"lucide-react": "^0.15.19",
|
||||||
"style-loader": "^3",
|
"next": "^10.0.0",
|
||||||
"stylus": "^0.59.0",
|
"react": "^16.0.0",
|
||||||
"typescript": "^4",
|
"react-dom": "^16.0.0",
|
||||||
"vite": "^3"
|
"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": {
|
"peerDependencies": {
|
||||||
"next": ">=11.0.0",
|
"lucide-react": "^0.15.19",
|
||||||
"react": ">=17.0.0",
|
"next": "^10.0.0 || ^11.0.0",
|
||||||
"react-dom": ">=17.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": {
|
"scripts": {
|
||||||
"dev": "start-storybook --port 6006 --no-version-updates --disable-telemetry --no-manager-cache --modern --no-open",
|
"dev": "start-storybook -s ./.storybook/public -p 6006",
|
||||||
"build": "npx vite build && npx tsc --emitDeclarationOnly",
|
"build": "rollup --config",
|
||||||
"prepublishOnly": "npx vite build && npx tsc --emitDeclarationOnly"
|
"prepublishOnly": "npm run build",
|
||||||
|
"postinstall": "rollup --config"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dzeio/object-util": "^1",
|
"rollup": "^2.44.0",
|
||||||
"lucide-react": "^0.104.1"
|
"rollup-plugin-styles": "^3.14.1",
|
||||||
|
"rollup-plugin-typescript2": "^0.30.0",
|
||||||
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
29
rollup.config.js
Normal file
29
rollup.config.js
Normal 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'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
@ -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
|
|
@ -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>
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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
|
|
@ -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",
|
|
||||||
}]
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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)
|
|
@ -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
|
|
@ -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]
|
|
@ -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>
|
|
||||||
)
|
|
@ -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
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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} />
|
|
@ -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
|
|
@ -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
|
|
@ -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 })
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,9 +0,0 @@
|
|||||||
@import '../config'
|
|
||||||
|
|
||||||
.link
|
|
||||||
color var(--theme-500)
|
|
||||||
text-decoration underline
|
|
||||||
|
|
||||||
.icon
|
|
||||||
vertical-align sub
|
|
||||||
margin 2px
|
|
@ -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
|
|
@ -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} />
|
|
@ -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
|
|
@ -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} />
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
|
||||||
)
|
|
@ -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
|
|
@ -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} />
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
@import '../config'
|
|
||||||
|
|
||||||
.padding
|
|
||||||
padding 24px
|
|
@ -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} />
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
@ -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'}} />
|
|
@ -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)
|
|
@ -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
|
|
||||||
}
|
|
@ -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>
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
@ -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>
|
|
||||||
)
|
|
@ -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>
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
@ -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
|
|
@ -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})
|
|
||||||
}
|
|
||||||
}
|
|
355
src/colors.styl
355
src/colors.styl
@ -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
|
|
||||||
)
|
|
@ -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
|
|
33
src/dzeio/Box/Box.module.styl
Normal file
33
src/dzeio/Box/Box.module.styl
Normal 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
71
src/dzeio/Box/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
184
src/dzeio/Button/Button.module.styl
Normal file
184
src/dzeio/Button/Button.module.styl
Normal 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
|
@ -1,7 +1,6 @@
|
|||||||
import { Meta } from '@storybook/react/types-6-0'
|
import { Meta } from '@storybook/react/types-6-0'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Zap } from 'lucide-react'
|
import { Zap } from 'lucide-react'
|
||||||
import Box from '../Box'
|
|
||||||
import Component from '.'
|
import Component from '.'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -9,24 +8,25 @@ export default {
|
|||||||
component: Component
|
component: Component
|
||||||
} as Meta
|
} as Meta
|
||||||
|
|
||||||
export const Button = (args: any) => <Box><Component {...args}>Button</Component></Box>
|
export const Basic = (args: any) => <Component {...args}>Button</Component>
|
||||||
Button.args = {
|
Basic.args = {
|
||||||
nomargintop: true,
|
nomargintop: true,
|
||||||
icon: Zap,
|
icon: Zap,
|
||||||
mobileBlock: true,
|
size: 'small',
|
||||||
iconLeft: Zap,
|
href: '/pouet',
|
||||||
size: 'small'
|
block: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithImg = (args: any) => <Box><Component {...args}>Button</Component></Box>
|
export const WithImg = (args: any) => <Component {...args}>Button</Component>
|
||||||
WithImg.args = {
|
WithImg.args = {
|
||||||
nomargintop: true,
|
nomargintop: true,
|
||||||
icon: '/16-16.svg',
|
icon: '/16-16.svg',
|
||||||
size: 'small',
|
size: 'small',
|
||||||
|
href: '/pouet',
|
||||||
block: true
|
block: true
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExternalLinkButton = (args: any) => <Box><Component {...args}>Button</Component></Box>
|
export const ExternalLinkButton = (args: any) => <Component {...args}>Button</Component>
|
||||||
ExternalLinkButton.args = {
|
ExternalLinkButton.args = {
|
||||||
nomargintop: true,
|
nomargintop: true,
|
||||||
href: 'https://example.com',
|
href: 'https://example.com',
|
71
src/dzeio/Button/index.tsx
Normal file
71
src/dzeio/Button/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
146
src/dzeio/Checkbox/Checkbox.module.styl
Normal file
146
src/dzeio/Checkbox/Checkbox.module.styl
Normal 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)
|
@ -1,10 +1,10 @@
|
|||||||
import { Meta } from '@storybook/react/types-6-0'
|
import { Meta } from '@storybook/react/types-6-0'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import CheckboxReact from '.'
|
import Checkbox from '.'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'DZEIO/Checkbox',
|
title: 'DZEIO/Checkbox',
|
||||||
component: CheckboxReact
|
component: Checkbox
|
||||||
} as Meta
|
} as Meta
|
||||||
|
|
||||||
export const Checkbox = (args: any) => <CheckboxReact {...args} />
|
export const Basic = (args: any) => <Checkbox {...args} />
|
13
src/dzeio/Code/Code.module.styl
Normal file
13
src/dzeio/Code/Code.module.styl
Normal 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
|
@ -10,7 +10,7 @@ export default {
|
|||||||
}
|
}
|
||||||
} as Meta
|
} as Meta
|
||||||
|
|
||||||
export const Code = (args: any) => {
|
export const Basic = (args: any) => {
|
||||||
const content = args.content
|
const content = args.content
|
||||||
delete args.content
|
delete args.content
|
||||||
|
|
@ -4,7 +4,6 @@ import css from './Code.module.styl'
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
block?: boolean
|
block?: boolean
|
||||||
children?: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Code extends React.Component<Props> {
|
export default class Code extends React.Component<Props> {
|
@ -4,6 +4,7 @@
|
|||||||
max-width 100%
|
max-width 100%
|
||||||
flex-basis 0
|
flex-basis 0
|
||||||
flex-grow 1
|
flex-grow 1
|
||||||
|
padding $gapSize 0 0 $gapSize
|
||||||
|
|
||||||
&.nogrow
|
&.nogrow
|
||||||
max-width initial
|
max-width initial
|
||||||
@ -15,13 +16,12 @@ for i in (0...$colCount+1)
|
|||||||
if i == 0
|
if i == 0
|
||||||
display none
|
display none
|
||||||
else
|
else
|
||||||
$tmp = ((i / $colCount) * 100)%
|
flex 0 0 ((i / 12) * 100)%
|
||||||
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
|
min-width ((i / 12) * 100)%
|
||||||
min-width "calc(%s - %s)" % ($tmp $gapSize)
|
|
||||||
|
|
||||||
if i != $colCount and i != 0
|
if i != $colCount and i != 0
|
||||||
.offset-{i}
|
.offset-{i}
|
||||||
margin-left ((i / $colCount) * 100)% + (i * $gapSize + $gapSize)px
|
margin-left ((i / 12) * 100)% + (i * $gapSize + $gapSize)px
|
||||||
|
|
||||||
@media (max-width $tablet)
|
@media (max-width $tablet)
|
||||||
.col.tabletGrow
|
.col.tabletGrow
|
||||||
@ -32,9 +32,8 @@ for i in (0...$colCount+1)
|
|||||||
if i == 0
|
if i == 0
|
||||||
display none
|
display none
|
||||||
else
|
else
|
||||||
$tmp = ((i / $colCountTablet) * 100)%
|
flex 0 0 ((i / $colCountTablet) * 100)%
|
||||||
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
|
min-width ((i / $colCountTablet) * 100)%
|
||||||
min-width "calc(%s - %s)" % ($tmp $gapSize)
|
|
||||||
|
|
||||||
if i != $colCountTablet and i != 0
|
if i != $colCountTablet and i != 0
|
||||||
.offset-tablet-{i}
|
.offset-tablet-{i}
|
||||||
@ -49,9 +48,8 @@ for i in (0...$colCount+1)
|
|||||||
if i == 0
|
if i == 0
|
||||||
display none
|
display none
|
||||||
else
|
else
|
||||||
$tmp = ((i / $colCountMobile) * 100)%
|
flex 0 0 ((i / $colCountMobile) * 100)%
|
||||||
flex "0 0 calc(%s - %s)" % ($tmp $gapSize)
|
min-width ((i / $colCountMobile) * 100)%
|
||||||
min-width "calc(%s - %s)" % ($tmp $gapSize)
|
|
||||||
if i != $colCountMobile and i != 0
|
if i != $colCountMobile and i != 0
|
||||||
.offset-tablet-{i}
|
.offset-tablet-{i}
|
||||||
margin-left ((i / $colCountMobile) * 100)% + (i * $gapSize - $gapSize)
|
margin-left ((i / $colCountMobile) * 100)% + (i * $gapSize - $gapSize)
|
@ -1,9 +1,8 @@
|
|||||||
import { objectOmit } from '@dzeio/object-util'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { buildClassName } from '../Util'
|
import { buildClassName } from '../Util'
|
||||||
import css from './Col.module.styl'
|
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
|
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
|
offset?: 1|2|3|4|5|6|7|8|9|10|11
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
@ -22,25 +21,10 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
|||||||
mobileGrow?: boolean
|
mobileGrow?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default class Col extends React.Component<Props> {
|
export default class Col extends React.Component<Props> {
|
||||||
|
|
||||||
public render = () => (
|
public render = () => (
|
||||||
<div {...objectOmit(
|
<div className={buildClassName(
|
||||||
this.props,
|
|
||||||
"size",
|
|
||||||
"offset",
|
|
||||||
"children",
|
|
||||||
"className",
|
|
||||||
"nogrow",
|
|
||||||
"tabletSize",
|
|
||||||
"tabletoffset",
|
|
||||||
"tabletGrow",
|
|
||||||
"mobileSize",
|
|
||||||
"mobileoffset",
|
|
||||||
"mobileGrow"
|
|
||||||
)} className={buildClassName(
|
|
||||||
css.col,
|
css.col,
|
||||||
|
|
||||||
// Normal
|
// Normal
|
2
src/dzeio/Container/Container.module.styl
Normal file
2
src/dzeio/Container/Container.module.styl
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.container
|
||||||
|
padding 16px
|
19
src/dzeio/Container/index.tsx
Normal file
19
src/dzeio/Container/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
@import '../config.styl'
|
@import '../config.styl'
|
||||||
|
|
||||||
.fieldset
|
.fieldset
|
||||||
border-radius 8px
|
border-radius 4px
|
||||||
border 2px solid var(--gray-500)
|
border 2px solid $grayDark
|
||||||
transition all $transition
|
transition all $transition
|
||||||
margin 0
|
margin 0
|
||||||
|
|
||||||
@ -12,3 +12,8 @@
|
|||||||
padding 0 4px
|
padding 0 4px
|
||||||
@media (prefers-color-scheme dark)
|
@media (prefers-color-scheme dark)
|
||||||
color white
|
color white
|
||||||
|
&:hover
|
||||||
|
border-color black
|
||||||
|
|
||||||
|
@media (prefers-color-scheme dark)
|
||||||
|
border-color white
|
@ -1,33 +1,26 @@
|
|||||||
@import '../config.styl'
|
@import '../config.styl'
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
padding 24px 0
|
padding 24px 16px
|
||||||
background var(--theme-50)
|
background $foregroundLight
|
||||||
@media (prefers-color-scheme dark)
|
@media (prefers-color-scheme dark)
|
||||||
background var(--gray-800)
|
background $foregroundDark
|
||||||
|
|
||||||
ul
|
ul
|
||||||
padding 0
|
padding 0
|
||||||
margin 0
|
|
||||||
display flex
|
display flex
|
||||||
justify-content right
|
justify-content center
|
||||||
li
|
li
|
||||||
display inline-block
|
display inline-block
|
||||||
padding-left 24px
|
|
||||||
+ .icon
|
|
||||||
padding-left 16px
|
|
||||||
&:not(.icon) + .icon
|
|
||||||
padding-left 48px
|
|
||||||
|
|
||||||
|
&.socials a
|
||||||
|
padding 0 8px
|
||||||
|
|
||||||
.animation
|
.animation
|
||||||
animation grow 1s linear infinite
|
animation grow 1s linear infinite
|
||||||
display inline-block
|
display inline-block
|
||||||
vertical-align middle
|
vertical-align middle
|
||||||
margin 0 2px
|
margin 0 2px
|
||||||
width 16px
|
|
||||||
height @width
|
|
||||||
vertical-align sub
|
|
||||||
|
|
||||||
@keyframes grow
|
@keyframes grow
|
||||||
0%
|
0%
|
@ -6,9 +6,6 @@ import Component from '.'
|
|||||||
export default {
|
export default {
|
||||||
title: 'DZEIO/Footer',
|
title: 'DZEIO/Footer',
|
||||||
component: Component,
|
component: Component,
|
||||||
parameters: {
|
|
||||||
layout: 'fullscreen'
|
|
||||||
}
|
|
||||||
} as Meta
|
} as Meta
|
||||||
|
|
||||||
export const Basic: Story<any> = (args: any) => <Component {...args} />
|
export const Basic: Story<any> = (args: any) => <Component {...args} />
|
48
src/dzeio/Footer/index.tsx
Normal file
48
src/dzeio/Footer/index.tsx
Normal 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 && (<> - </>)}<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>
|
||||||
|
)
|
||||||
|
}
|
30
src/dzeio/GradientBackground/GradientBackground.module.styl
Normal file
30
src/dzeio/GradientBackground/GradientBackground.module.styl
Normal 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))
|
27
src/dzeio/GradientBackground/index.tsx
Normal file
27
src/dzeio/GradientBackground/index.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
@ -69,7 +69,6 @@ export default class Image extends React.Component<Props, States> {
|
|||||||
priority
|
priority
|
||||||
quality={100}
|
quality={100}
|
||||||
{...this.props.imageProps}
|
{...this.props.imageProps}
|
||||||
layout={this.state.image ? 'fill' : this.props.imageProps.layout}
|
|
||||||
objectFit="contain"
|
objectFit="contain"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
265
src/dzeio/Input/Input.module.styl
Normal file
265
src/dzeio/Input/Input.module.styl
Normal 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
|
29
src/dzeio/Input/Input.stories.tsx
Normal file
29
src/dzeio/Input/Input.stories.tsx
Normal 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
247
src/dzeio/Input/index.tsx
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
src/dzeio/Link/Link.module.styl
Normal file
12
src/dzeio/Link/Link.module.styl
Normal 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
|
@ -13,4 +13,4 @@ export default {
|
|||||||
}
|
}
|
||||||
} as Meta
|
} as Meta
|
||||||
|
|
||||||
export const Link = (args: any) => <Component {...args}>{args.text}</Component>
|
export const Basic = (args: any) => <Component {...args}>{args.text}</Component>
|
19
src/dzeio/Loader/Loader.module.styl
Normal file
19
src/dzeio/Loader/Loader.module.styl
Normal 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
Reference in New Issue
Block a user