mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
initial commit
This commit is contained in:
commit
26583faa40
17
.babelrc
Normal file
17
.babelrc
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"presets": ["next/babel"],
|
||||
"plugins": [
|
||||
[
|
||||
"module-resolver",
|
||||
{
|
||||
"root": ["./"],
|
||||
"alias": {
|
||||
"@components": "./src/components",
|
||||
"@hooks": "./src/hooks",
|
||||
"@utils": "./src/utils",
|
||||
"@theme": "./src/theme"
|
||||
}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
13
.editorconfig
Normal file
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
9
.eslintignore
Normal file
9
.eslintignore
Normal file
@ -0,0 +1,9 @@
|
||||
.now/*
|
||||
.next/*
|
||||
examples/*
|
||||
dist/*
|
||||
esm/*
|
||||
public/*
|
||||
scripts/*
|
||||
tests/*
|
||||
*.config.js
|
||||
79
.eslintrc
Normal file
79
.eslintrc
Normal file
@ -0,0 +1,79 @@
|
||||
{
|
||||
"plugins": ["prettier", "@typescript-eslint"],
|
||||
"extends": ["eslint:recommended", "plugin:react/recommended", "prettier"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
"version": "detect"
|
||||
},
|
||||
"import/resolver": {
|
||||
"typescript": {
|
||||
"alwaysTryTypes": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"object-curly-spacing": ["warn", "always"],
|
||||
"no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"warn",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "none"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/no-explicit-any": [
|
||||
"error",
|
||||
{
|
||||
"ignoreRestArgs": true
|
||||
}
|
||||
],
|
||||
"max-len": [
|
||||
"warn",
|
||||
{
|
||||
"code": 80,
|
||||
"ignoreStrings": true,
|
||||
"ignoreTemplateLiterals": true,
|
||||
"ignoreComments": true
|
||||
}
|
||||
],
|
||||
"no-plusplus": [
|
||||
"error",
|
||||
{
|
||||
"allowForLoopAfterthoughts": true
|
||||
}
|
||||
],
|
||||
"react/jsx-key": "error",
|
||||
"react/jsx-props-no-spreading": "off",
|
||||
"import/prefer-default-export": "off",
|
||||
"react/jsx-boolean-value": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/jsx-one-expression-per-line": "off",
|
||||
"react/jsx-wrap-multilines": "off",
|
||||
"react/destructuring-assignment": "off",
|
||||
"@typescript-eslint/comma-dangle": [
|
||||
"error",
|
||||
{
|
||||
"arrays": "only-multiline",
|
||||
"objects": "only-multiline",
|
||||
"imports": "only-multiline",
|
||||
"exports": "only-multiline",
|
||||
"functions": "never"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.now
|
||||
dist
|
||||
esm
|
||||
examples/**/yarn.lock
|
||||
examples/**/out
|
||||
examples/**/.next
|
||||
12
.prettierignore
Normal file
12
.prettierignore
Normal file
@ -0,0 +1,12 @@
|
||||
.github
|
||||
.next
|
||||
.now
|
||||
.circleci
|
||||
dist
|
||||
coverage
|
||||
public
|
||||
esm
|
||||
*.json
|
||||
*.d.ts
|
||||
*.yml
|
||||
*.snap
|
||||
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2
|
||||
}
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020 Next UI
|
||||
|
||||
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.
|
||||
2
next-env.d.ts
vendored
Normal file
2
next-env.d.ts
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
5
next.config.js
Normal file
5
next.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
future: {
|
||||
webpack5: true,
|
||||
},
|
||||
};
|
||||
95
package.json
Normal file
95
package.json
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"name": "nextui",
|
||||
"version": "0.1.1",
|
||||
"main": "dist/index.js",
|
||||
"module": "esm/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"unpkg": "dist/index.min.js",
|
||||
"license": "MIT",
|
||||
"description": "🚀 Beautiful and modern React UI library.",
|
||||
"homepage": "https://nextui.org",
|
||||
"bugs": {
|
||||
"url": "https://github.com/jrgarciadev/next-ui/issues/new/choose"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/jrgarciadev/next-ui"
|
||||
},
|
||||
"keywords": [
|
||||
"next",
|
||||
"next ui",
|
||||
"components",
|
||||
"modern components",
|
||||
"react components",
|
||||
"react ui"
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"start": "next start",
|
||||
"contributor-collect": "node scripts/collect-contributors.js",
|
||||
"build:esm": "babel --config-file ./scripts/babel.config.js --extensions \".js,.ts,.tsx\" ./components --out-dir ./esm --ignore \"**/__tests__/**/*,**/*.d.ts\"",
|
||||
"build:webpack": "webpack --config scripts/webpack.config.js",
|
||||
"build:types": "tsc -p ./scripts",
|
||||
"build": "yarn clear && yarn build:esm && yarn build:webpack && yarn build:types",
|
||||
"clear": "rm -rf ./dist ./esm",
|
||||
"lint": "eslint --ext .js,.jsx,.ts,.tsx src --color",
|
||||
"format": "prettier --write 'src/**/*.{ts,tsx,scss,css,json}'",
|
||||
"prettier": "prettier --write .",
|
||||
"test": "jest --config .jest.config.js --no-cache",
|
||||
"test-update": "jest --config .jest.config.js --no-cache --update-snapshot",
|
||||
"coverage": "yarn test --coverage",
|
||||
"release": "yarn build && yarn publish --access public --non-interactive"
|
||||
},
|
||||
"files": [
|
||||
"/dist",
|
||||
"/esm"
|
||||
],
|
||||
"dependencies": {
|
||||
"styled-jsx": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.13.16",
|
||||
"@babel/plugin-transform-runtime": "^7.13.15",
|
||||
"@babel/preset-typescript": "^7.13.0",
|
||||
"@babel/runtime": "^7.13.17",
|
||||
"@mapbox/rehype-prism": "^0.6.0",
|
||||
"@mdx-js/loader": "^1.6.22",
|
||||
"@next/mdx": "^10.1.3",
|
||||
"@rollup/plugin-commonjs": "^18.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.2.1",
|
||||
"@types/react": "^17.0.3",
|
||||
"@types/react-dom": "^17.0.3",
|
||||
"@types/styled-jsx": "^2.2.8",
|
||||
"@typescript-eslint/eslint-plugin": "^4.22.0",
|
||||
"@typescript-eslint/parser": "^4.22.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"babel-jest": "^26.6.3",
|
||||
"babel-loader": "^8.2.2",
|
||||
"babel-plugin-module-resolver": "^4.1.0",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-airbnb": "^18.2.1",
|
||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||
"eslint-config-prettier": "^8.2.0",
|
||||
"eslint-config-react-app": "^6.0.0",
|
||||
"eslint-config-ts-lambdas": "^1.2.3",
|
||||
"eslint-import-resolver-typescript": "^2.4.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-flowtype": "^5.7.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"eslint-plugin-react": "^7.23.2",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"next": "^10.1.3",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier-eslint": "^12.0.0",
|
||||
"prettier-eslint-cli": "^5.0.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
"rollup-plugin-local-resolve": "^1.0.7",
|
||||
"typescript": "^4.2.4",
|
||||
"webpack": "^5.35.0",
|
||||
"webpack-cli": "^4.6.0"
|
||||
}
|
||||
}
|
||||
15
pages/index.tsx
Normal file
15
pages/index.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
import Checkbox from '@components/checkbox';
|
||||
|
||||
const IndexPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Checkbox</h1>
|
||||
<Checkbox checked={true} size="medium">
|
||||
medium
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default IndexPage;
|
||||
82
rollup.config.js
Normal file
82
rollup.config.js
Normal file
@ -0,0 +1,82 @@
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import localResolve from 'rollup-plugin-local-resolve'
|
||||
import babel from 'rollup-plugin-babel'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
const componentsPath = path.join(__dirname, 'components')
|
||||
const distPath = path.join(__dirname, 'dist')
|
||||
|
||||
const extensions = ['.js', '.jsx', '.ts', '.tsx']
|
||||
|
||||
const plugins = [
|
||||
babel({
|
||||
exclude: 'node_modules/**',
|
||||
extensions,
|
||||
presets: ['@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript'],
|
||||
plugins: ['styled-jsx/babel'],
|
||||
}),
|
||||
localResolve(),
|
||||
nodeResolve({
|
||||
browser: true,
|
||||
extensions,
|
||||
}),
|
||||
commonjs(),
|
||||
]
|
||||
|
||||
const globals = {
|
||||
react: 'React',
|
||||
'react-dom': 'ReactDOM',
|
||||
}
|
||||
|
||||
const external = id => /^react|react-dom|styled-jsx|next\/link/.test(id)
|
||||
|
||||
const cjsOutput = {
|
||||
format: 'cjs',
|
||||
exports: 'named',
|
||||
entryFileNames: '[name]/index.js',
|
||||
dir: 'dist',
|
||||
globals,
|
||||
}
|
||||
|
||||
export default (async () => {
|
||||
await fs.remove(distPath)
|
||||
const files = await fs.readdir(componentsPath)
|
||||
|
||||
const components = await Promise.all(
|
||||
files.map(async name => {
|
||||
const comPath = path.join(componentsPath, name)
|
||||
const entry = path.join(comPath, 'index.ts')
|
||||
|
||||
const stat = await fs.stat(comPath)
|
||||
if (!stat.isDirectory()) return null
|
||||
|
||||
const hasFile = await fs.pathExists(entry)
|
||||
if (!hasFile) return null
|
||||
|
||||
return { name, url: entry }
|
||||
}),
|
||||
)
|
||||
|
||||
return [
|
||||
...components
|
||||
.filter(r => r)
|
||||
.map(({ name, url }) => ({
|
||||
input: { [name]: url },
|
||||
output: [cjsOutput],
|
||||
external,
|
||||
plugins,
|
||||
})),
|
||||
{
|
||||
input: { index: path.join(componentsPath, 'index.ts') },
|
||||
output: [
|
||||
{
|
||||
...cjsOutput,
|
||||
entryFileNames: 'index.js',
|
||||
},
|
||||
],
|
||||
external,
|
||||
plugins,
|
||||
},
|
||||
]
|
||||
})()
|
||||
21
src/components/checkbox/checkbox-context.ts
Normal file
21
src/components/checkbox/checkbox-context.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
|
||||
export interface CheckboxConfig {
|
||||
updateState?: (value: string, checked: boolean) => void;
|
||||
disabledAll: boolean;
|
||||
values: string[];
|
||||
inGroup: boolean;
|
||||
}
|
||||
|
||||
const defaultContext = {
|
||||
disabledAll: false,
|
||||
inGroup: false,
|
||||
values: [],
|
||||
};
|
||||
|
||||
export const CheckboxContext = React.createContext<CheckboxConfig>(
|
||||
defaultContext
|
||||
);
|
||||
|
||||
export const useCheckbox = (): CheckboxConfig =>
|
||||
React.useContext<CheckboxConfig>(CheckboxContext);
|
||||
85
src/components/checkbox/checkbox-group.tsx
Normal file
85
src/components/checkbox/checkbox-group.tsx
Normal file
@ -0,0 +1,85 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { CheckboxContext } from './checkbox-context';
|
||||
import useWarning from '@hooks/use-warning';
|
||||
import { NormalSizes } from '@utils/prop-types';
|
||||
import withDefaults from '@utils/with-defaults';
|
||||
|
||||
interface Props {
|
||||
value: string[];
|
||||
disabled?: boolean;
|
||||
size?: NormalSizes;
|
||||
onChange?: (values: string[]) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
size: 'small' as NormalSizes,
|
||||
className: '',
|
||||
};
|
||||
|
||||
type NativeAttrs = Omit<React.HTMLAttributes<unknown>, keyof Props>;
|
||||
export type CheckboxGroupProps = Props & typeof defaultProps & NativeAttrs;
|
||||
|
||||
export const getCheckboxSize = (size: NormalSizes): string => {
|
||||
const sizes: { [key in NormalSizes]: string } = {
|
||||
mini: '.75rem',
|
||||
small: '.875rem',
|
||||
medium: '1rem',
|
||||
large: '1.125rem',
|
||||
};
|
||||
return sizes[size];
|
||||
};
|
||||
|
||||
const CheckboxGroup: React.FC<React.PropsWithChildren<CheckboxGroupProps>> = ({
|
||||
disabled,
|
||||
onChange,
|
||||
value,
|
||||
size,
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}) => {
|
||||
const [selfVal, setSelfVal] = useState<string[]>([]);
|
||||
if (!value) {
|
||||
value = [];
|
||||
useWarning('Props "value" is required.', 'Checkbox Group');
|
||||
}
|
||||
|
||||
const updateState = (val: string, checked: boolean) => {
|
||||
const removed = selfVal.filter((v) => v !== val);
|
||||
const next = checked ? [...removed, val] : removed;
|
||||
setSelfVal(next);
|
||||
onChange && onChange(next);
|
||||
};
|
||||
|
||||
const providerValue = useMemo(() => {
|
||||
return {
|
||||
updateState,
|
||||
disabledAll: disabled,
|
||||
inGroup: true,
|
||||
values: selfVal,
|
||||
};
|
||||
}, [disabled, selfVal]);
|
||||
const fontSize = useMemo(() => getCheckboxSize(size), [size]);
|
||||
|
||||
useEffect(() => {
|
||||
setSelfVal(value);
|
||||
}, [value.join(',')]);
|
||||
|
||||
return (
|
||||
<CheckboxContext.Provider value={providerValue}>
|
||||
<div className={`group ${className}`} {...props}>
|
||||
{children}
|
||||
<style jsx>{`
|
||||
.group :global(label) {
|
||||
margin-right: calc(${fontSize} * 2);
|
||||
--checkbox-size: ${fontSize};
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
</CheckboxContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default withDefaults(CheckboxGroup, defaultProps);
|
||||
54
src/components/checkbox/checkbox-icon.tsx
Normal file
54
src/components/checkbox/checkbox-icon.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import useTheme from '@hooks/use-theme';
|
||||
|
||||
interface Props {
|
||||
disabled?: boolean;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
const CheckboxIcon: React.FC<Props> = ({ disabled, checked }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { fill, bg, stroke } = useMemo(() => {
|
||||
return {
|
||||
fill: theme.palette.foreground,
|
||||
bg: theme.palette.background,
|
||||
stroke: theme.palette.accents_5,
|
||||
};
|
||||
}, [theme.palette]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{checked ? (
|
||||
<svg viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M12.1429 0H3.85714C1.7269 0 0 1.79086 0 4V12C0 14.2091 1.7269 16 3.85714 16H12.1429C14.2731 16 16 14.2091 16 12V4C16 1.79086 14.2731 0 12.1429 0Z"
|
||||
fill={fill}
|
||||
/>
|
||||
<path d="M16 3L7.72491 11L5 8" stroke={bg} strokeWidth="1.5" />
|
||||
</svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M8.5 0.5H3.5C1.84315 0.5 0.5 1.84315 0.5 3.5V8.5C0.5 10.1569 1.84315 11.5 3.5 11.5H8.5C10.1569 11.5 11.5 10.1569 11.5 8.5V3.5C11.5 1.84315 10.1569 0.5 8.5 0.5Z"
|
||||
stroke={stroke}
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
<style jsx>{`
|
||||
svg {
|
||||
display: inline-flex;
|
||||
width: calc(0.86 * var(--checkbox-size));
|
||||
height: calc(0.86 * var(--checkbox-size));
|
||||
user-select: none;
|
||||
opacity: ${disabled ? 0.4 : 1};
|
||||
cursor: ${disabled ? 'not-allowed' : 'pointer'};
|
||||
}
|
||||
`}</style>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoCheckboxIcon = React.memo(CheckboxIcon);
|
||||
|
||||
export default MemoCheckboxIcon;
|
||||
155
src/components/checkbox/checkbox.tsx
Normal file
155
src/components/checkbox/checkbox.tsx
Normal file
@ -0,0 +1,155 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useCheckbox } from './checkbox-context';
|
||||
import CheckboxGroup, { getCheckboxSize } from './checkbox-group';
|
||||
import CheckboxIcon from './checkbox-icon';
|
||||
import useWarning from '@hooks/use-warning';
|
||||
import { NormalSizes } from '@utils/prop-types';
|
||||
|
||||
interface CheckboxEventTarget {
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export interface CheckboxEvent {
|
||||
target: CheckboxEventTarget;
|
||||
stopPropagation: () => void;
|
||||
preventDefault: () => void;
|
||||
nativeEvent: React.ChangeEvent;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
checked?: boolean;
|
||||
disabled?: boolean;
|
||||
initialChecked?: boolean;
|
||||
onChange?: (e: CheckboxEvent) => void;
|
||||
size?: NormalSizes;
|
||||
className?: string;
|
||||
value?: string;
|
||||
}
|
||||
|
||||
const defaultProps = {
|
||||
disabled: false,
|
||||
initialChecked: false,
|
||||
size: 'small' as NormalSizes,
|
||||
className: '',
|
||||
value: '',
|
||||
};
|
||||
|
||||
type NativeAttrs = Omit<React.InputHTMLAttributes<unknown>, keyof Props>;
|
||||
export type CheckboxProps = Props & typeof defaultProps & NativeAttrs;
|
||||
|
||||
const Checkbox: React.FC<CheckboxProps> = ({
|
||||
checked,
|
||||
initialChecked,
|
||||
disabled,
|
||||
onChange,
|
||||
className,
|
||||
children,
|
||||
size,
|
||||
value,
|
||||
...props
|
||||
}) => {
|
||||
const [selfChecked, setSelfChecked] = useState<boolean>(initialChecked);
|
||||
const { updateState, inGroup, disabledAll, values } = useCheckbox();
|
||||
const isDisabled = inGroup ? disabledAll || disabled : disabled;
|
||||
|
||||
if (inGroup && checked) {
|
||||
useWarning(
|
||||
'Remove props "checked" when [Checkbox] component is in the group.',
|
||||
'Checkbox'
|
||||
);
|
||||
}
|
||||
if (inGroup) {
|
||||
useEffect(() => {
|
||||
const next = values.includes(value);
|
||||
if (next === selfChecked) return;
|
||||
setSelfChecked(next);
|
||||
}, [values.join(',')]);
|
||||
}
|
||||
|
||||
const fontSize = useMemo(() => getCheckboxSize(size), [size]);
|
||||
const changeHandle = useCallback(
|
||||
(ev: React.ChangeEvent) => {
|
||||
if (isDisabled) return;
|
||||
const selfEvent: CheckboxEvent = {
|
||||
target: {
|
||||
checked: !selfChecked,
|
||||
},
|
||||
stopPropagation: ev.stopPropagation,
|
||||
preventDefault: ev.preventDefault,
|
||||
nativeEvent: ev,
|
||||
};
|
||||
if (inGroup && updateState) {
|
||||
updateState && updateState(value, !selfChecked);
|
||||
}
|
||||
|
||||
setSelfChecked(!selfChecked);
|
||||
onChange && onChange(selfEvent);
|
||||
},
|
||||
[updateState, onChange, isDisabled, selfChecked]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (checked === undefined) return;
|
||||
setSelfChecked(checked);
|
||||
}, [checked]);
|
||||
|
||||
return (
|
||||
<label className={`${className}`}>
|
||||
<CheckboxIcon disabled={isDisabled} checked={selfChecked} />
|
||||
<input
|
||||
type="checkbox"
|
||||
disabled={isDisabled}
|
||||
checked={selfChecked}
|
||||
onChange={changeHandle}
|
||||
{...props}
|
||||
/>
|
||||
<span className="text">{children}</span>
|
||||
|
||||
<style jsx>{`
|
||||
label {
|
||||
--checkbox-size: ${fontSize};
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: auto;
|
||||
cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
|
||||
opacity: ${isDisabled ? 0.75 : 1};
|
||||
height: var(--checkbox-size);
|
||||
line-height: var(--checkbox-size);
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: var(--checkbox-size);
|
||||
line-height: var(--checkbox-size);
|
||||
padding-left: calc(var(--checkbox-size) * 0.57);
|
||||
user-select: none;
|
||||
cursor: ${isDisabled ? 'not-allowed' : 'pointer'};
|
||||
}
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
z-index: -1;
|
||||
background-color: transparent;
|
||||
}
|
||||
`}</style>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
Checkbox.defaultProps = defaultProps;
|
||||
|
||||
type CheckboxComponent<P = {}> = React.FC<P> & {
|
||||
Group: typeof CheckboxGroup;
|
||||
};
|
||||
|
||||
type ComponentProps = Partial<typeof defaultProps> &
|
||||
Omit<Props, keyof typeof defaultProps> &
|
||||
NativeAttrs;
|
||||
|
||||
export default Checkbox as CheckboxComponent<ComponentProps>;
|
||||
10
src/components/checkbox/index.ts
Normal file
10
src/components/checkbox/index.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import Checkbox from './checkbox'
|
||||
import CheckboxGroup from './checkbox-group'
|
||||
import { CheckboxProps, CheckboxEvent } from './checkbox'
|
||||
|
||||
export type Props = CheckboxProps
|
||||
export type Event = CheckboxEvent
|
||||
|
||||
Checkbox.Group = CheckboxGroup
|
||||
|
||||
export default Checkbox
|
||||
1
src/components/index.ts
Normal file
1
src/components/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { default as Checkbox } from './checkbox';
|
||||
7
src/hooks/use-theme.ts
Normal file
7
src/hooks/use-theme.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
import ThemeContext from '@theme/theme-context'
|
||||
import { NextUIThemes } from '@theme/index'
|
||||
|
||||
const useTheme = (): NextUIThemes => React.useContext<NextUIThemes>(ThemeContext)
|
||||
|
||||
export default useTheme
|
||||
16
src/hooks/use-warning.ts
Normal file
16
src/hooks/use-warning.ts
Normal file
@ -0,0 +1,16 @@
|
||||
const warningStack: { [key: string]: boolean } = {}
|
||||
|
||||
const useWarning = (message: string, component?: string) => {
|
||||
const tag = component ? ` [${component}]` : ' '
|
||||
const log = `[Next UI]${tag}: ${message}`
|
||||
if (typeof console === 'undefined') return
|
||||
if (warningStack[log]) return
|
||||
warningStack[log] = true
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return console.error(log)
|
||||
}
|
||||
console.warn(log)
|
||||
}
|
||||
|
||||
export default useWarning
|
||||
72
src/theme/default.ts
Normal file
72
src/theme/default.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { NextUIThemes, NextUIThemesPalette, NextUIThemesExpressiveness } from './index'
|
||||
import { defaultFont, defaultBreakpoints, defaultLayout } from './shared'
|
||||
|
||||
export const palette: NextUIThemesPalette = {
|
||||
accents_1: '#fafafa',
|
||||
accents_2: '#eaeaea',
|
||||
accents_3: '#999',
|
||||
accents_4: '#888',
|
||||
accents_5: '#666',
|
||||
accents_6: '#444',
|
||||
accents_7: '#333',
|
||||
accents_8: '#111',
|
||||
background: '#fff',
|
||||
foreground: '#000',
|
||||
selection: '#79ffe1',
|
||||
secondary: '#666',
|
||||
code: '#f81ce5',
|
||||
border: '#eaeaea',
|
||||
error: '#e00',
|
||||
errorLight: '#ff1a1a',
|
||||
errorLighter: '#f7d4d6',
|
||||
errorDark: '#c50000',
|
||||
success: '#0070f3',
|
||||
successLight: '#3291ff',
|
||||
successLighter: '#d3e5ff',
|
||||
successDark: '#0761d1',
|
||||
warning: '#f5a623',
|
||||
warningLight: '#f7b955',
|
||||
warningLighter: '#ffefcf',
|
||||
warningDark: '#ab570a',
|
||||
cyan: '#50e3c2',
|
||||
cyanLighter: '#aaffec',
|
||||
cyanLight: '#79ffe1',
|
||||
cyanDark: '#29bc9b',
|
||||
violet: '#7928ca',
|
||||
violetLighter: '#e3d7fc',
|
||||
violetLight: '#8a63d2',
|
||||
violetDark: '#4c2889',
|
||||
purple: '#f81ce5',
|
||||
alert: '#ff0080',
|
||||
magenta: '#eb367f',
|
||||
link: '#0070f3',
|
||||
}
|
||||
|
||||
export const expressiveness: NextUIThemesExpressiveness = {
|
||||
linkStyle: 'none',
|
||||
linkHoverStyle: 'none',
|
||||
dropdownBoxShadow: '0 4px 4px 0 rgba(0, 0, 0, 0.02)',
|
||||
scrollerStart: 'rgba(255, 255, 255, 1)',
|
||||
scrollerEnd: 'rgba(255, 255, 255, 0)',
|
||||
shadowSmall: '0 5px 10px rgba(0, 0, 0, 0.12)',
|
||||
shadowMedium: '0 8px 30px rgba(0, 0, 0, 0.12)',
|
||||
shadowLarge: '0 30px 60px rgba(0, 0, 0, 0.12)',
|
||||
portalOpacity: 0.25,
|
||||
}
|
||||
|
||||
export const font = defaultFont
|
||||
|
||||
export const breakpoints = defaultBreakpoints
|
||||
|
||||
export const layout = defaultLayout
|
||||
|
||||
export const themes: NextUIThemes = {
|
||||
type: 'light',
|
||||
font,
|
||||
layout,
|
||||
palette,
|
||||
breakpoints,
|
||||
expressiveness,
|
||||
}
|
||||
|
||||
export default themes
|
||||
96
src/theme/index.ts
Normal file
96
src/theme/index.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { ThemeTypes } from '@utils/prop-types'
|
||||
|
||||
export interface NextUIThemesPalette {
|
||||
accents_1: string
|
||||
accents_2: string
|
||||
accents_3: string
|
||||
accents_4: string
|
||||
accents_5: string
|
||||
accents_6: string
|
||||
accents_7: string
|
||||
accents_8: string
|
||||
background: string
|
||||
foreground: string
|
||||
selection: string
|
||||
secondary: string
|
||||
code: string
|
||||
border: string
|
||||
success: string
|
||||
successLighter: string
|
||||
successLight: string
|
||||
successDark: string
|
||||
error: string
|
||||
errorLighter: string
|
||||
errorLight: string
|
||||
errorDark: string
|
||||
warning: string
|
||||
warningLighter: string
|
||||
warningLight: string
|
||||
warningDark: string
|
||||
cyan: string
|
||||
cyanLighter: string
|
||||
cyanLight: string
|
||||
cyanDark: string
|
||||
violet: string
|
||||
violetLighter: string
|
||||
violetLight: string
|
||||
violetDark: string
|
||||
link: string
|
||||
purple: string
|
||||
magenta: string
|
||||
alert: string
|
||||
}
|
||||
|
||||
export interface NextUIThemesExpressiveness {
|
||||
linkStyle: string
|
||||
linkHoverStyle: string
|
||||
dropdownBoxShadow: string
|
||||
scrollerStart: string
|
||||
scrollerEnd: string
|
||||
shadowSmall: string
|
||||
shadowMedium: string
|
||||
shadowLarge: string
|
||||
portalOpacity: number
|
||||
}
|
||||
|
||||
export interface NextUIThemesLayout {
|
||||
gap: string
|
||||
gapNegative: string
|
||||
gapHalf: string
|
||||
gapHalfNegative: string
|
||||
gapQuarter: string
|
||||
gapQuarterNegative: string
|
||||
pageMargin: string
|
||||
pageWidth: string
|
||||
pageWidthWithMargin: string
|
||||
breakpointMobile: string
|
||||
breakpointTablet: string
|
||||
radius: string
|
||||
}
|
||||
|
||||
export interface NextUIThemesFont {
|
||||
sans: string
|
||||
mono: string
|
||||
}
|
||||
|
||||
export interface BreakpointsItem {
|
||||
min: string
|
||||
max: string
|
||||
}
|
||||
|
||||
export interface NextUIThemesBreakpoints {
|
||||
xs: BreakpointsItem
|
||||
sm: BreakpointsItem
|
||||
md: BreakpointsItem
|
||||
lg: BreakpointsItem
|
||||
xl: BreakpointsItem
|
||||
}
|
||||
|
||||
export interface NextUIThemes {
|
||||
type: ThemeTypes
|
||||
font: NextUIThemesFont
|
||||
layout: NextUIThemesLayout
|
||||
palette: NextUIThemesPalette
|
||||
breakpoints: NextUIThemesBreakpoints
|
||||
expressiveness: NextUIThemesExpressiveness
|
||||
}
|
||||
51
src/theme/shared.ts
Normal file
51
src/theme/shared.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import {
|
||||
NextUIThemesBreakpoints,
|
||||
NextUIThemesFont,
|
||||
NextUIThemesLayout,
|
||||
} from './index'
|
||||
|
||||
export const defaultFont: NextUIThemesFont = {
|
||||
sans:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
|
||||
mono:
|
||||
'Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace',
|
||||
}
|
||||
|
||||
export const defaultBreakpoints: NextUIThemesBreakpoints = {
|
||||
xs: {
|
||||
min: '0',
|
||||
max: '650px',
|
||||
},
|
||||
sm: {
|
||||
min: '650px',
|
||||
max: '900px',
|
||||
},
|
||||
md: {
|
||||
min: '900px',
|
||||
max: '1280px',
|
||||
},
|
||||
lg: {
|
||||
min: '1280px',
|
||||
max: '1920px',
|
||||
},
|
||||
xl: {
|
||||
min: '1920px',
|
||||
max: '10000px',
|
||||
},
|
||||
}
|
||||
|
||||
export const defaultLayout: NextUIThemesLayout = {
|
||||
gap: '16pt',
|
||||
gapNegative: '-16pt',
|
||||
gapHalf: '8pt',
|
||||
gapHalfNegative: '-8pt',
|
||||
gapQuarter: '4pt',
|
||||
gapQuarterNegative: '-4pt',
|
||||
pageMargin: '16pt',
|
||||
pageWidth: '750pt',
|
||||
pageWidthWithMargin: '782pt',
|
||||
breakpointMobile: defaultBreakpoints.xs.max,
|
||||
breakpointTablet: defaultBreakpoints.sm.max,
|
||||
radius: '5px',
|
||||
}
|
||||
|
||||
7
src/theme/theme-context.ts
Normal file
7
src/theme/theme-context.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
import { NextUIThemes } from './index'
|
||||
import defaultTheme from './default'
|
||||
|
||||
const ThemeContext: React.Context<NextUIThemes> = React.createContext<NextUIThemes>(defaultTheme)
|
||||
|
||||
export default ThemeContext
|
||||
77
src/utils/prop-types.ts
Normal file
77
src/utils/prop-types.ts
Normal file
@ -0,0 +1,77 @@
|
||||
export const tuple = <T extends string[]>(...args: T) => args
|
||||
|
||||
const buttonTypes = tuple(
|
||||
'default',
|
||||
'secondary',
|
||||
'success',
|
||||
'warning',
|
||||
'error',
|
||||
'abort',
|
||||
'secondary-light',
|
||||
'success-light',
|
||||
'warning-light',
|
||||
'error-light',
|
||||
)
|
||||
|
||||
const normalSizes = tuple('mini', 'small', 'medium', 'large')
|
||||
|
||||
const normalTypes = tuple('default', 'secondary', 'success', 'warning', 'error')
|
||||
|
||||
const themeTypes = tuple('dark', 'light')
|
||||
|
||||
const snippetTypes = tuple('default', 'secondary', 'success', 'warning', 'error', 'dark', 'lite')
|
||||
|
||||
const cardTypes = tuple(
|
||||
'default',
|
||||
'secondary',
|
||||
'success',
|
||||
'warning',
|
||||
'error',
|
||||
'dark',
|
||||
'lite',
|
||||
'alert',
|
||||
'purple',
|
||||
'violet',
|
||||
'cyan',
|
||||
)
|
||||
|
||||
const copyTypes = tuple('default', 'slient', 'prevent')
|
||||
|
||||
const triggerTypes = tuple('hover', 'click')
|
||||
|
||||
const placement = tuple(
|
||||
'top',
|
||||
'topStart',
|
||||
'topEnd',
|
||||
'left',
|
||||
'leftStart',
|
||||
'leftEnd',
|
||||
'bottom',
|
||||
'bottomStart',
|
||||
'bottomEnd',
|
||||
'right',
|
||||
'rightStart',
|
||||
'rightEnd',
|
||||
)
|
||||
|
||||
const dividerAlign = tuple('start', 'center', 'end', 'left', 'right')
|
||||
|
||||
export type ButtonTypes = typeof buttonTypes[number]
|
||||
|
||||
export type NormalSizes = typeof normalSizes[number]
|
||||
|
||||
export type NormalTypes = typeof normalTypes[number]
|
||||
|
||||
export type ThemeTypes = typeof themeTypes[number]
|
||||
|
||||
export type SnippetTypes = typeof snippetTypes[number]
|
||||
|
||||
export type CardTypes = typeof cardTypes[number]
|
||||
|
||||
export type CopyTypes = typeof copyTypes[number]
|
||||
|
||||
export type TriggerTypes = typeof triggerTypes[number]
|
||||
|
||||
export type Placement = typeof placement[number]
|
||||
|
||||
export type DividerAlign = typeof dividerAlign[number]
|
||||
9
src/utils/with-defaults.ts
Normal file
9
src/utils/with-defaults.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import React from 'react'
|
||||
|
||||
const withDefaults = <P, DP>(component: React.ComponentType<P>, defaultProps: DP) => {
|
||||
type Props = Partial<DP> & Omit<P, keyof DP>
|
||||
component.defaultProps = defaultProps
|
||||
return component as React.ComponentType<Props>
|
||||
}
|
||||
|
||||
export default withDefaults
|
||||
47
tsconfig.json
Normal file
47
tsconfig.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@hooks/*": ["./src/hooks/*"],
|
||||
"@utils/*": ["./src/utils/*"],
|
||||
"@theme/*": ["./src/theme/*"]
|
||||
},
|
||||
"strictNullChecks": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true,
|
||||
"jsx": "preserve",
|
||||
"noUnusedParameters": true,
|
||||
"noUnusedLocals": true,
|
||||
"noImplicitAny": true,
|
||||
"target": "es6",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es2017"
|
||||
],
|
||||
"skipLibCheck": true,
|
||||
"allowJs": true,
|
||||
"strict": false,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"module": "esnext",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"./typings"
|
||||
]
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"**/*.js",
|
||||
"**/*.jsx",
|
||||
"src/**/*.tsx"
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user