Eduard Makarov
9 months ago
commit
9d04f2e7a8
47 changed files with 4713 additions and 0 deletions
@ -0,0 +1,18 @@ |
|||||||
|
module.exports = { |
||||||
|
root: true, |
||||||
|
env: { browser: true, es2020: true }, |
||||||
|
extends: [ |
||||||
|
'eslint:recommended', |
||||||
|
'plugin:@typescript-eslint/recommended', |
||||||
|
'plugin:react-hooks/recommended', |
||||||
|
], |
||||||
|
ignorePatterns: ['dist', '.eslintrc.cjs'], |
||||||
|
parser: '@typescript-eslint/parser', |
||||||
|
plugins: ['react-refresh'], |
||||||
|
rules: { |
||||||
|
'react-refresh/only-export-components': [ |
||||||
|
'warn', |
||||||
|
{ allowConstantExport: true }, |
||||||
|
], |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,24 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/* |
||||||
|
!.vscode/extensions.json |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
@ -0,0 +1,30 @@ |
|||||||
|
# React + TypeScript + Vite |
||||||
|
|
||||||
|
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. |
||||||
|
|
||||||
|
Currently, two official plugins are available: |
||||||
|
|
||||||
|
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh |
||||||
|
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh |
||||||
|
|
||||||
|
## Expanding the ESLint configuration |
||||||
|
|
||||||
|
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: |
||||||
|
|
||||||
|
- Configure the top-level `parserOptions` property like this: |
||||||
|
|
||||||
|
```js |
||||||
|
export default { |
||||||
|
// other rules... |
||||||
|
parserOptions: { |
||||||
|
ecmaVersion: 'latest', |
||||||
|
sourceType: 'module', |
||||||
|
project: ['./tsconfig.json', './tsconfig.node.json'], |
||||||
|
tsconfigRootDir: __dirname, |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` |
||||||
|
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` |
||||||
|
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list |
@ -0,0 +1,13 @@ |
|||||||
|
<!doctype html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<title>Vite + React + TS</title> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
<script type="module" src="main.tsx"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,9 @@ |
|||||||
|
import React from "react"; |
||||||
|
import ReactDOM from "react-dom/client"; |
||||||
|
import App from "./src/App.tsx"; |
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render( |
||||||
|
<React.StrictMode> |
||||||
|
<App /> |
||||||
|
</React.StrictMode> |
||||||
|
); |
@ -0,0 +1,34 @@ |
|||||||
|
{ |
||||||
|
"name": "almont", |
||||||
|
"private": true, |
||||||
|
"version": "0.0.0", |
||||||
|
"type": "module", |
||||||
|
"scripts": { |
||||||
|
"dev": "vite --open --port 3000", |
||||||
|
"build": "tsc && vite build", |
||||||
|
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", |
||||||
|
"preview": "vite preview" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"localforage": "^1.10.0", |
||||||
|
"match-sorter": "^6.3.4", |
||||||
|
"react": "^18.2.0", |
||||||
|
"react-dom": "^18.2.0", |
||||||
|
"react-icons": "^5.0.1", |
||||||
|
"react-router-dom": "^6.22.2", |
||||||
|
"react-select": "^5.8.0", |
||||||
|
"sort-by": "^1.2.0" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@types/react": "^18.2.56", |
||||||
|
"@types/react-dom": "^18.2.19", |
||||||
|
"@typescript-eslint/eslint-plugin": "^7.0.2", |
||||||
|
"@typescript-eslint/parser": "^7.0.2", |
||||||
|
"@vitejs/plugin-react": "^4.2.1", |
||||||
|
"eslint": "^8.56.0", |
||||||
|
"eslint-plugin-react-hooks": "^4.6.0", |
||||||
|
"eslint-plugin-react-refresh": "^0.4.5", |
||||||
|
"typescript": "^5.2.2", |
||||||
|
"vite": "^5.1.4" |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,8 @@ |
|||||||
|
import "@/assets/styles/index.css"; |
||||||
|
import { Navigation } from "./navigation"; |
||||||
|
|
||||||
|
function App() { |
||||||
|
return <Navigation />; |
||||||
|
} |
||||||
|
|
||||||
|
export default App; |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,150 @@ |
|||||||
|
@font-face { |
||||||
|
font-family: "Roboto"; |
||||||
|
src: url("/src/assets/fonts/Roboto-Bold.ttf"); |
||||||
|
font-weight: 900; |
||||||
|
font-style: normal; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Roboto"; |
||||||
|
src: url("/src/assets/fonts/Roboto-Bold.ttf"); |
||||||
|
font-weight: 700; |
||||||
|
font-style: normal; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Roboto"; |
||||||
|
src: url("/src/assets/fonts/Roboto-Medium.ttf"); |
||||||
|
font-weight: 500; |
||||||
|
font-style: normal; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Roboto"; |
||||||
|
src: url("/src/assets/fonts/Roboto-Regular.ttf"); |
||||||
|
font-weight: 400; |
||||||
|
font-style: normal; |
||||||
|
} |
||||||
|
|
||||||
|
@font-face { |
||||||
|
font-family: "Roboto"; |
||||||
|
src: url("/src/assets/fonts/Roboto-Light.ttf"); |
||||||
|
font-weight: 300; |
||||||
|
font-style: normal; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
#root { |
||||||
|
max-width: 1280px; |
||||||
|
margin: 0 auto; |
||||||
|
} |
||||||
|
|
||||||
|
* { |
||||||
|
box-sizing: border-box; |
||||||
|
font-family: "Roboto", sans-serif !important; |
||||||
|
outline: none !important; |
||||||
|
} |
||||||
|
|
||||||
|
html, |
||||||
|
body, |
||||||
|
div, |
||||||
|
span, |
||||||
|
object, |
||||||
|
iframe, |
||||||
|
h1, |
||||||
|
h2, |
||||||
|
h3, |
||||||
|
h4, |
||||||
|
h5, |
||||||
|
h6, |
||||||
|
p, |
||||||
|
blockquote, |
||||||
|
pre, |
||||||
|
abbr, |
||||||
|
address, |
||||||
|
cite, |
||||||
|
code, |
||||||
|
del, |
||||||
|
dfn, |
||||||
|
em, |
||||||
|
img, |
||||||
|
ins, |
||||||
|
kbd, |
||||||
|
q, |
||||||
|
samp, |
||||||
|
small, |
||||||
|
strong, |
||||||
|
sub, |
||||||
|
sup, |
||||||
|
var, |
||||||
|
b, |
||||||
|
i, |
||||||
|
dl, |
||||||
|
dt, |
||||||
|
dd, |
||||||
|
ol, |
||||||
|
ul, |
||||||
|
li, |
||||||
|
fieldset, |
||||||
|
form, |
||||||
|
label, |
||||||
|
legend, |
||||||
|
table, |
||||||
|
caption, |
||||||
|
tbody, |
||||||
|
tfoot, |
||||||
|
thead, |
||||||
|
tr, |
||||||
|
th, |
||||||
|
td, |
||||||
|
article, |
||||||
|
aside, |
||||||
|
canvas, |
||||||
|
details, |
||||||
|
figcaption, |
||||||
|
figure, |
||||||
|
footer, |
||||||
|
header, |
||||||
|
hgroup, |
||||||
|
menu, |
||||||
|
nav, |
||||||
|
section, |
||||||
|
summary, |
||||||
|
time, |
||||||
|
mark, |
||||||
|
audio, |
||||||
|
video { |
||||||
|
margin: 0; |
||||||
|
padding: 0; |
||||||
|
border: 0; |
||||||
|
outline: 0; |
||||||
|
font-size: 100%; |
||||||
|
vertical-align: baseline; |
||||||
|
background: transparent; |
||||||
|
list-style: none; |
||||||
|
list-style: none; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
body { |
||||||
|
line-height: 1; |
||||||
|
height: 100vh; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
color: #000000; |
||||||
|
} |
||||||
|
|
||||||
|
button{ |
||||||
|
border: none; |
||||||
|
background-color: transparent; |
||||||
|
margin:0; |
||||||
|
padding:0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
a:link, |
||||||
|
a:visited { |
||||||
|
color: inherit; |
||||||
|
text-decoration: none; |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
import { Colors } from "../typing/colors.interfaces"; |
||||||
|
|
||||||
|
export const colors: Colors = { |
||||||
|
primatyText: "#000000D9", |
||||||
|
lightBlue: "#EAF3FF", |
||||||
|
secondaryText: "#6E6E6E", |
||||||
|
whiteText: "#FFFFFF", |
||||||
|
primary: "#2D3A5F", |
||||||
|
disabledColor: "gray", |
||||||
|
errorColor: "red", |
||||||
|
lightYellow: "#FFFFE7", |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export { PrimaryButton } from "./primary-button"; |
@ -0,0 +1,57 @@ |
|||||||
|
import { CSSProperties, useMemo } from "react"; |
||||||
|
import styles from "./style.module.css"; |
||||||
|
|
||||||
|
interface Props { |
||||||
|
onClick: () => void; |
||||||
|
label: string; |
||||||
|
disabled?: boolean; |
||||||
|
isLoading?: boolean; |
||||||
|
leftIcon?: JSX.Element | null; |
||||||
|
style?: CSSProperties; |
||||||
|
labelStyle?: CSSProperties; |
||||||
|
} |
||||||
|
|
||||||
|
export const PrimaryButton = ({ |
||||||
|
onClick, |
||||||
|
label, |
||||||
|
style, |
||||||
|
labelStyle, |
||||||
|
disabled, |
||||||
|
isLoading, |
||||||
|
leftIcon = null, |
||||||
|
}: Props) => { |
||||||
|
const getButtonClass = () => { |
||||||
|
switch (true) { |
||||||
|
case disabled: |
||||||
|
return `${styles.disabled} ${styles.button_container}`; |
||||||
|
default: |
||||||
|
return styles.button_container; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const content = useMemo(() => { |
||||||
|
if (isLoading) { |
||||||
|
return <div className={styles.loader}></div>; |
||||||
|
} else { |
||||||
|
return ( |
||||||
|
<div className={styles.content}> |
||||||
|
<>{leftIcon}</> |
||||||
|
<p className={styles.button_label} style={labelStyle}> |
||||||
|
{label} |
||||||
|
</p> |
||||||
|
</div> |
||||||
|
); |
||||||
|
} |
||||||
|
}, [isLoading, label, labelStyle]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<button |
||||||
|
disabled={disabled} |
||||||
|
onClick={onClick} |
||||||
|
style={style} |
||||||
|
className={getButtonClass()} |
||||||
|
> |
||||||
|
{content} |
||||||
|
</button> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,54 @@ |
|||||||
|
.button_container { |
||||||
|
position: relative; |
||||||
|
width:300px; |
||||||
|
height: 40px; |
||||||
|
cursor: pointer; |
||||||
|
transition: background-color 0.5s; |
||||||
|
background-color: #2D3A5F; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
} |
||||||
|
|
||||||
|
.button_label{ |
||||||
|
color: #FFFF; |
||||||
|
font-size: 16px; |
||||||
|
|
||||||
|
} |
||||||
|
.disabled{ |
||||||
|
pointer-events: none; |
||||||
|
background-color: #00000052; |
||||||
|
|
||||||
|
} |
||||||
|
.button_container:hover { |
||||||
|
opacity: 0.9; |
||||||
|
} |
||||||
|
|
||||||
|
.button_container:active { |
||||||
|
opacity: 0.8; |
||||||
|
} |
||||||
|
|
||||||
|
.content{ |
||||||
|
display: flex; |
||||||
|
gap: 8px; |
||||||
|
} |
||||||
|
|
||||||
|
.loader { |
||||||
|
position: absolute; |
||||||
|
transform: translate(-50%, -50%); |
||||||
|
border: 2px solid #e1dddd; |
||||||
|
border-top: 2px solid #88a2eb; |
||||||
|
border-radius: 50%; |
||||||
|
width: 20px; |
||||||
|
height: 20px; |
||||||
|
animation: spin 1s linear infinite; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes spin { |
||||||
|
0% { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
100% { |
||||||
|
transform: rotate(360deg); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1 @@ |
|||||||
|
export * from "./button-primary"; |
@ -0,0 +1,2 @@ |
|||||||
|
export * from "./text-field"; |
||||||
|
export * from "./select-field"; |
@ -0,0 +1,49 @@ |
|||||||
|
import React from "react"; |
||||||
|
|
||||||
|
interface Option { |
||||||
|
value: string; |
||||||
|
label: string; |
||||||
|
options?: unknown; |
||||||
|
} |
||||||
|
|
||||||
|
interface Props { |
||||||
|
isMulti: boolean; |
||||||
|
onClick: () => void; |
||||||
|
selecedOptions: Option[]; |
||||||
|
} |
||||||
|
export const SelectHeaderAtom = ({ |
||||||
|
isMulti, |
||||||
|
onClick, |
||||||
|
selecedOptions, |
||||||
|
}: Props) => { |
||||||
|
return ( |
||||||
|
<div |
||||||
|
className="select-header" |
||||||
|
onClick={onClick} |
||||||
|
style={{ |
||||||
|
width: 200, |
||||||
|
border: "1px solid", |
||||||
|
display: "flex", |
||||||
|
flexDirection: "row", |
||||||
|
justifyContent: "space-between", |
||||||
|
alignItems: "center", |
||||||
|
padding: 10, |
||||||
|
}} |
||||||
|
> |
||||||
|
{isMulti && selectedOptions.length > 0 |
||||||
|
? selectedOptions.map((opt) => ( |
||||||
|
<div style={{ display: "flex" }}> |
||||||
|
<p>{opt.label}</p> |
||||||
|
<FiTrash2 |
||||||
|
style={{ width: 15, height: 15, cursor: "pointer" }} |
||||||
|
onClick={() => removeTag(opt)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
)) |
||||||
|
: selectedOptions.length > 0 |
||||||
|
? selectedOptions[0].label |
||||||
|
: "Select..."} |
||||||
|
{isOpen ? <FiChevronUp /> : <FiChevronDown />} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export { SelectField } from "./select-field"; |
@ -0,0 +1,103 @@ |
|||||||
|
import React from "react"; |
||||||
|
// import styles from "./style.module.css";
|
||||||
|
import { useState } from "react"; |
||||||
|
import { FiChevronUp, FiChevronDown, FiTrash2 } from "react-icons/fi"; // Іконки стрілок
|
||||||
|
|
||||||
|
const options: Option[] = [ |
||||||
|
{ value: "blues", label: "Blues" }, |
||||||
|
{ value: "rock", label: "Rock" }, |
||||||
|
{ value: "jazz", label: "Jazz" }, |
||||||
|
{ value: "orchestra", label: "Orchestra" }, |
||||||
|
]; |
||||||
|
|
||||||
|
interface Option { |
||||||
|
value: string; |
||||||
|
label: string; |
||||||
|
options?: unknown; |
||||||
|
} |
||||||
|
|
||||||
|
interface Props { |
||||||
|
isMulti: boolean; |
||||||
|
error?: boolean; |
||||||
|
} |
||||||
|
|
||||||
|
export const SelectField = ({ isMulti }: Props) => { |
||||||
|
const [isOpen, setIsOpen] = useState(false); |
||||||
|
const [selectedOptions, setSelectedOptions] = useState<Option[]>([]); |
||||||
|
|
||||||
|
const toggleSelect = () => { |
||||||
|
setIsOpen((prevState) => !prevState); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleOptionSelect = (option: Option) => { |
||||||
|
if (isMulti) { |
||||||
|
setSelectedOptions((prevOptions) => { |
||||||
|
const exists = prevOptions.some((item) => item.value === option.value); |
||||||
|
if (exists) { |
||||||
|
return prevOptions.filter((item) => item.value !== option.value); |
||||||
|
} else { |
||||||
|
return [...prevOptions, option]; |
||||||
|
} |
||||||
|
}); |
||||||
|
} else { |
||||||
|
setSelectedOptions([option]); |
||||||
|
setIsOpen(false); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const removeTag = (option: Option) => { |
||||||
|
setSelectedOptions((prev) => { |
||||||
|
return prev.filter((it) => it.value !== option.value); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div className="custom-select"> |
||||||
|
<div |
||||||
|
className="select-header" |
||||||
|
onClick={toggleSelect} |
||||||
|
style={{ |
||||||
|
width: 200, |
||||||
|
border: "1px solid", |
||||||
|
display: "flex", |
||||||
|
flexDirection: "row", |
||||||
|
justifyContent: "space-between", |
||||||
|
alignItems: "center", |
||||||
|
padding: 10, |
||||||
|
}} |
||||||
|
> |
||||||
|
{isMulti && selectedOptions.length > 0 |
||||||
|
? selectedOptions.map((opt) => ( |
||||||
|
<div style={{ display: "flex" }}> |
||||||
|
<p>{opt.label}</p> |
||||||
|
<FiTrash2 |
||||||
|
style={{ width: 15, height: 15, cursor: "pointer" }} |
||||||
|
onClick={() => removeTag(opt)} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
)) |
||||||
|
: selectedOptions.length > 0 |
||||||
|
? selectedOptions[0].label |
||||||
|
: "Select..."} |
||||||
|
{isOpen ? <FiChevronUp /> : <FiChevronDown />} |
||||||
|
</div> |
||||||
|
{isOpen && ( |
||||||
|
<ul className="options-list"> |
||||||
|
{options.map((option) => ( |
||||||
|
<li |
||||||
|
key={option.value} |
||||||
|
onClick={() => handleOptionSelect(option)} |
||||||
|
className={ |
||||||
|
selectedOptions.some((item) => item.value === option.value) |
||||||
|
? "selected" |
||||||
|
: "" |
||||||
|
} |
||||||
|
> |
||||||
|
{option.label} |
||||||
|
</li> |
||||||
|
))} |
||||||
|
</ul> |
||||||
|
)} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export { TextField } from "./text-field.control"; |
@ -0,0 +1,32 @@ |
|||||||
|
.text_field { |
||||||
|
max-width: 200px; |
||||||
|
height: 40px; |
||||||
|
padding: 5px 10px; |
||||||
|
align-items: center; |
||||||
|
border: 1px solid #D9D9D9; |
||||||
|
border-radius: 2px; |
||||||
|
} |
||||||
|
|
||||||
|
.text_field.focused { |
||||||
|
border-color: #bdbdbd; |
||||||
|
box-shadow: 0 0 0.4rem 0.2rem rgba(209, 209, 209, 0.162); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
.label{ |
||||||
|
font-size: 14px; |
||||||
|
color: #000000D9; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
.error_field{border-color: red;} |
||||||
|
|
||||||
|
.error_focused { |
||||||
|
border-color: red; |
||||||
|
box-shadow: 0 0 0.4rem 0.2rem rgba(255, 0, 0, 0.135); |
||||||
|
} |
||||||
|
.error{ |
||||||
|
color: red; |
||||||
|
font-size: 10px; |
||||||
|
margin-top: 3px |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
import { CSSProperties, InputHTMLAttributes, useMemo, useState } from "react"; |
||||||
|
import styles from "./styles.module.css"; |
||||||
|
interface Props extends InputHTMLAttributes<HTMLInputElement> { |
||||||
|
value: string; |
||||||
|
onChangeField: (val: string) => void; |
||||||
|
label?: string; |
||||||
|
error?: string; |
||||||
|
styleContainer?: CSSProperties; |
||||||
|
inputStyle?: CSSProperties; |
||||||
|
} |
||||||
|
export const TextField = ({ |
||||||
|
value, |
||||||
|
onChangeField, |
||||||
|
error, |
||||||
|
label, |
||||||
|
styleContainer, |
||||||
|
inputStyle, |
||||||
|
...props |
||||||
|
}: Props) => { |
||||||
|
const [focused, setFocused] = useState(false); |
||||||
|
|
||||||
|
const handleFocus = () => { |
||||||
|
setFocused(true); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleBlur = () => { |
||||||
|
setFocused(false); |
||||||
|
}; |
||||||
|
const labelField = useMemo(() => { |
||||||
|
if (!label) { |
||||||
|
return null; |
||||||
|
} |
||||||
|
return <p className={styles.label}>{label}</p>; |
||||||
|
}, [label]); |
||||||
|
|
||||||
|
const getClassNames = () => { |
||||||
|
let classNames = styles.text_field; |
||||||
|
switch (true) { |
||||||
|
case focused && !!error: |
||||||
|
classNames += ` ${styles.error_focused}`; |
||||||
|
break; |
||||||
|
case !!error && !focused: |
||||||
|
classNames += ` ${styles.error_field}`; |
||||||
|
break; |
||||||
|
case focused: |
||||||
|
classNames += ` ${styles.focused}`; |
||||||
|
break; |
||||||
|
default: |
||||||
|
break; |
||||||
|
} |
||||||
|
return classNames; |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<div style={styleContainer}> |
||||||
|
<>{labelField}</> |
||||||
|
<input |
||||||
|
style={inputStyle} |
||||||
|
value={value} |
||||||
|
onChange={(e) => onChangeField(e.target.value)} |
||||||
|
className={getClassNames()} |
||||||
|
onFocus={handleFocus} |
||||||
|
onBlur={handleBlur} |
||||||
|
{...props} |
||||||
|
/> |
||||||
|
{error ? <p className={styles.error}>{error}</p> : null} |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,2 @@ |
|||||||
|
export * from "./buttons"; |
||||||
|
export * from "./form-controls"; |
@ -0,0 +1,10 @@ |
|||||||
|
export interface Colors { |
||||||
|
primatyText: string; |
||||||
|
secondaryText: string; |
||||||
|
primary: string; |
||||||
|
disabledColor: string; |
||||||
|
errorColor: string; |
||||||
|
whiteText: string; |
||||||
|
lightBlue: string; |
||||||
|
lightYellow: string; |
||||||
|
} |
@ -0,0 +1,70 @@ |
|||||||
|
import React, { CSSProperties } from "react"; |
||||||
|
import { PrimaryButton } from "../../../theme/components"; |
||||||
|
import { colors } from "../../../theme/colors"; |
||||||
|
|
||||||
|
const IconsPlus = (props: { style?: CSSProperties; colorIcon?: string }) => ( |
||||||
|
<div style={props?.style}> |
||||||
|
<svg |
||||||
|
fill="none" |
||||||
|
aria-hidden="true" |
||||||
|
className="w-6 h-6 text-gray-800 dark:text-white" |
||||||
|
viewBox="0 0 24 24" |
||||||
|
> |
||||||
|
<path |
||||||
|
stroke={props?.colorIcon || "currentColor"} |
||||||
|
strokeLinecap="round" |
||||||
|
strokeLinejoin="round" |
||||||
|
strokeWidth={2} |
||||||
|
d="M12 7.8v8.4M7.8 12h8.4m4.8 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
); |
||||||
|
|
||||||
|
export const ButtonsPage = () => { |
||||||
|
return ( |
||||||
|
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}> |
||||||
|
<PrimaryButton |
||||||
|
onClick={() => console.log("hello 1")} |
||||||
|
label="Login" |
||||||
|
isLoading={true} |
||||||
|
/> |
||||||
|
<PrimaryButton onClick={() => console.log("hello 1")} label="Login" /> |
||||||
|
|
||||||
|
<PrimaryButton |
||||||
|
disabled={true} |
||||||
|
onClick={() => console.log("hello 2")} |
||||||
|
label="Disaled button" |
||||||
|
/> |
||||||
|
|
||||||
|
<PrimaryButton |
||||||
|
onClick={() => console.log("hello 2")} |
||||||
|
label="Outline button" |
||||||
|
style={{ backgroundColor: "transparent", border: "1px solid" }} |
||||||
|
labelStyle={{ color: colors.primatyText }} |
||||||
|
/> |
||||||
|
|
||||||
|
<PrimaryButton |
||||||
|
onClick={() => console.log("hello 3")} |
||||||
|
label="Text button" |
||||||
|
labelStyle={{ color: colors.primatyText, fontWeight: "500" }} |
||||||
|
style={{ backgroundColor: "transparent" }} |
||||||
|
/> |
||||||
|
|
||||||
|
<PrimaryButton |
||||||
|
onClick={() => console.log("hello 3")} |
||||||
|
label="Text button with icon" |
||||||
|
labelStyle={{ color: colors.primatyText, fontWeight: "500" }} |
||||||
|
style={{ backgroundColor: "transparent" }} |
||||||
|
leftIcon={<IconsPlus style={{ height: 20, width: 20 }} />} |
||||||
|
/> |
||||||
|
<PrimaryButton |
||||||
|
onClick={() => console.log("hello 1")} |
||||||
|
label="Primary with icon" |
||||||
|
leftIcon={ |
||||||
|
<IconsPlus style={{ height: 20, width: 20 }} colorIcon="white" /> |
||||||
|
} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export { ButtonsPage } from "./buttons.page"; |
@ -0,0 +1,54 @@ |
|||||||
|
import React, { useEffect, useState } from "react"; |
||||||
|
import { TextField, SelectField } from "../../../theme/components"; |
||||||
|
|
||||||
|
export const FormsPage = () => { |
||||||
|
const [value, setValue] = useState(""); |
||||||
|
const [error, setError] = useState("Error message"); |
||||||
|
console.log("value", value); |
||||||
|
useEffect(() => { |
||||||
|
if (value.length > 0) { |
||||||
|
setError(""); |
||||||
|
} else { |
||||||
|
setError("Error message"); |
||||||
|
} |
||||||
|
}, [value]); |
||||||
|
|
||||||
|
return ( |
||||||
|
<div> |
||||||
|
<h4 style={{ marginBottom: 10 }}>Text fields</h4> |
||||||
|
|
||||||
|
<div style={{ display: "flex", gap: 20 }}> |
||||||
|
<TextField |
||||||
|
label="Username" |
||||||
|
placeholder="Enter name" |
||||||
|
value={value} |
||||||
|
onChangeField={setValue} |
||||||
|
error={error} |
||||||
|
/> |
||||||
|
|
||||||
|
<TextField |
||||||
|
label="Numeric" |
||||||
|
placeholder="Only number" |
||||||
|
type="number" |
||||||
|
value={value} |
||||||
|
onChangeField={setValue} |
||||||
|
/> |
||||||
|
|
||||||
|
<TextField |
||||||
|
label="Password" |
||||||
|
placeholder="Enter password" |
||||||
|
type="password" |
||||||
|
value={value} |
||||||
|
onChangeField={setValue} |
||||||
|
error={error} |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<h4 style={{ margin: "10px 0px" }}>Select field</h4> |
||||||
|
<div style={{ display: "flex", gap: 20 }}> |
||||||
|
<SelectField isMulti /> |
||||||
|
<SelectField isMulti={false} /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1 @@ |
|||||||
|
export { FormsPage } from "./forms.page"; |
@ -0,0 +1,3 @@ |
|||||||
|
export { UIKitPage } from "./root.page"; |
||||||
|
export { ButtonsPage } from "./buttons"; |
||||||
|
export { FormsPage } from "./forms"; |
@ -0,0 +1,23 @@ |
|||||||
|
import { Outlet, useNavigate } from "react-router-dom"; |
||||||
|
import { PrimaryButton } from "../../theme/components"; |
||||||
|
|
||||||
|
export const UIKitPage = () => { |
||||||
|
const nav = useNavigate(); |
||||||
|
const navigateTo = (urlPage: string) => nav(urlPage); |
||||||
|
return ( |
||||||
|
<> |
||||||
|
<div style={{ display: "flex", gap: 20, padding: "30px 0px" }}> |
||||||
|
<PrimaryButton |
||||||
|
onClick={() => navigateTo("buttons")} |
||||||
|
label="Buttons page" |
||||||
|
/> |
||||||
|
<PrimaryButton |
||||||
|
onClick={() => navigateTo("forms")} |
||||||
|
label="Forms control page" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<Outlet /> |
||||||
|
</> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,3 @@ |
|||||||
|
export const createStyleSheet = <K extends string>( |
||||||
|
styles: Record<K, React.CSSProperties> |
||||||
|
) => styles; |
@ -0,0 +1 @@ |
|||||||
|
export * from "./use-routes.hook"; |
@ -0,0 +1,24 @@ |
|||||||
|
import { Route } from "react-router-dom"; |
||||||
|
import { AppRoute } from "../typing/interfaces"; |
||||||
|
|
||||||
|
interface IProps { |
||||||
|
routes: AppRoute[]; |
||||||
|
} |
||||||
|
export const useRoutes = ({ routes }: IProps) => { |
||||||
|
return routes.map((route) => { |
||||||
|
if (route.children) { |
||||||
|
return ( |
||||||
|
<Route key={route.path} element={route.element} path={route.path}> |
||||||
|
{route.children.map((childRoute) => ( |
||||||
|
<Route |
||||||
|
key={childRoute.path} |
||||||
|
element={childRoute.element} |
||||||
|
path={childRoute.path} |
||||||
|
/> |
||||||
|
))} |
||||||
|
</Route> |
||||||
|
); |
||||||
|
} |
||||||
|
return <Route key={route.path} element={route.element} path={route.path} />; |
||||||
|
}); |
||||||
|
}; |
@ -0,0 +1,12 @@ |
|||||||
|
import { BrowserRouter, Routes } from "react-router-dom"; |
||||||
|
import { routesConfig } from "./routes-config"; |
||||||
|
import { useRoutes } from "./hooks/use-routes.hook"; |
||||||
|
|
||||||
|
export const Navigation = () => { |
||||||
|
const routes = useRoutes({ routes: routesConfig }); |
||||||
|
return ( |
||||||
|
<BrowserRouter> |
||||||
|
<Routes>{routes}</Routes> |
||||||
|
</BrowserRouter> |
||||||
|
); |
||||||
|
}; |
@ -0,0 +1,22 @@ |
|||||||
|
import { UIKitPage, FormsPage, ButtonsPage } from "../core/ui-kit/pages"; |
||||||
|
import { AppRoute } from "./typing/interfaces"; |
||||||
|
|
||||||
|
export const routesConfig: AppRoute[] = [ |
||||||
|
{ |
||||||
|
title: "UI Kit", |
||||||
|
path: "/", |
||||||
|
element: <UIKitPage />, |
||||||
|
children: [ |
||||||
|
{ |
||||||
|
title: "Buttons UI", |
||||||
|
path: "buttons", |
||||||
|
element: <ButtonsPage />, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: "Forms UI", |
||||||
|
path: "forms", |
||||||
|
element: <FormsPage />, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
]; |
@ -0,0 +1,9 @@ |
|||||||
|
interface RouteElement { |
||||||
|
title: string; |
||||||
|
path: string; |
||||||
|
element?: JSX.Element; |
||||||
|
} |
||||||
|
|
||||||
|
export interface AppRoute extends RouteElement { |
||||||
|
children?: RouteElement[]; |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ES2020", |
||||||
|
"useDefineForClassFields": true, |
||||||
|
"lib": [ |
||||||
|
"ES2020", |
||||||
|
"DOM", |
||||||
|
"DOM.Iterable" |
||||||
|
], |
||||||
|
"module": "ESNext", |
||||||
|
"skipLibCheck": true, |
||||||
|
/* Bundler mode */ |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowImportingTsExtensions": true, |
||||||
|
"resolveJsonModule": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"noEmit": true, |
||||||
|
"jsx": "react-jsx", |
||||||
|
/* Linting */ |
||||||
|
"strict": true, |
||||||
|
"noUnusedLocals": true, |
||||||
|
"noUnusedParameters": true, |
||||||
|
"noFallthroughCasesInSwitch": true |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"src", |
||||||
|
"main.tsx" |
||||||
|
], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.node.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"composite": true, |
||||||
|
"skipLibCheck": true, |
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "bundler", |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"strict": true |
||||||
|
}, |
||||||
|
"include": ["vite.config.ts"] |
||||||
|
} |
Loading…
Reference in new issue