Browse Source

init commit

master
Eduard Makarov 9 months ago
commit
9d04f2e7a8
  1. 18
      .eslintrc.cjs
  2. 24
      .gitignore
  3. 30
      README.md
  4. 13
      index.html
  5. 9
      main.tsx
  6. 3748
      package-lock.json
  7. 34
      package.json
  8. 1
      public/vite.svg
  9. 8
      src/App.tsx
  10. BIN
      src/assets/fonts/Roboto-Black.ttf
  11. BIN
      src/assets/fonts/Roboto-Bold.ttf
  12. BIN
      src/assets/fonts/Roboto-Light.ttf
  13. BIN
      src/assets/fonts/Roboto-Medium.ttf
  14. BIN
      src/assets/fonts/Roboto-Regular.ttf
  15. 3
      src/assets/images/circle-plus.svg
  16. 150
      src/assets/styles/index.css
  17. 12
      src/core/theme/colors/index.ts
  18. 1
      src/core/theme/components/buttons/button-primary/index.ts
  19. 57
      src/core/theme/components/buttons/button-primary/primary-button.tsx
  20. 54
      src/core/theme/components/buttons/button-primary/style.module.css
  21. 1
      src/core/theme/components/buttons/index.ts
  22. 2
      src/core/theme/components/form-controls/index.ts
  23. 49
      src/core/theme/components/form-controls/select-field/atoms/select-header.atom.tsx
  24. 1
      src/core/theme/components/form-controls/select-field/index.ts
  25. 103
      src/core/theme/components/form-controls/select-field/select-field.tsx
  26. 0
      src/core/theme/components/form-controls/select-field/style.module.css
  27. 1
      src/core/theme/components/form-controls/text-field/index.ts
  28. 32
      src/core/theme/components/form-controls/text-field/styles.module.css
  29. 69
      src/core/theme/components/form-controls/text-field/text-field.control.tsx
  30. 2
      src/core/theme/components/index.ts
  31. 10
      src/core/theme/typing/colors.interfaces.ts
  32. 70
      src/core/ui-kit/pages/buttons/buttons.page.tsx
  33. 1
      src/core/ui-kit/pages/buttons/index.ts
  34. 54
      src/core/ui-kit/pages/forms/forms.page.tsx
  35. 1
      src/core/ui-kit/pages/forms/index.ts
  36. 3
      src/core/ui-kit/pages/index.ts
  37. 23
      src/core/ui-kit/pages/root.page.tsx
  38. 3
      src/core/utils/helpers/styles.helper.ts
  39. 1
      src/navigation/hooks/interface.ts
  40. 24
      src/navigation/hooks/use-routes.hook.tsx
  41. 12
      src/navigation/index.tsx
  42. 22
      src/navigation/routes-config.tsx
  43. 9
      src/navigation/typing/interfaces.ts
  44. 1
      src/vite-env.d.ts
  45. 34
      tsconfig.json
  46. 11
      tsconfig.node.json
  47. 12
      vite.config.ts

18
.eslintrc.cjs

@ -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 },
],
},
}

24
.gitignore vendored

@ -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?

30
README.md

@ -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

13
index.html

@ -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>

9
main.tsx

@ -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>
);

3748
package-lock.json generated

File diff suppressed because it is too large Load Diff

34
package.json

@ -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"
}
}

1
public/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

8
src/App.tsx

@ -0,0 +1,8 @@
import "@/assets/styles/index.css";
import { Navigation } from "./navigation";
function App() {
return <Navigation />;
}
export default App;

BIN
src/assets/fonts/Roboto-Black.ttf

Binary file not shown.

BIN
src/assets/fonts/Roboto-Bold.ttf

Binary file not shown.

BIN
src/assets/fonts/Roboto-Light.ttf

Binary file not shown.

BIN
src/assets/fonts/Roboto-Medium.ttf

Binary file not shown.

BIN
src/assets/fonts/Roboto-Regular.ttf

Binary file not shown.

3
src/assets/images/circle-plus.svg

@ -0,0 +1,3 @@
<svg class="w-6 h-6 text-gray-800" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="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>

After

Width:  |  Height:  |  Size: 292 B

150
src/assets/styles/index.css

@ -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;
}

12
src/core/theme/colors/index.ts

@ -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",
};

1
src/core/theme/components/buttons/button-primary/index.ts

@ -0,0 +1 @@
export { PrimaryButton } from "./primary-button";

57
src/core/theme/components/buttons/button-primary/primary-button.tsx

@ -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>
);
};

54
src/core/theme/components/buttons/button-primary/style.module.css

@ -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);
}
}

1
src/core/theme/components/buttons/index.ts

@ -0,0 +1 @@
export * from "./button-primary";

2
src/core/theme/components/form-controls/index.ts

@ -0,0 +1,2 @@
export * from "./text-field";
export * from "./select-field";

49
src/core/theme/components/form-controls/select-field/atoms/select-header.atom.tsx

@ -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>
);
};

1
src/core/theme/components/form-controls/select-field/index.ts

@ -0,0 +1 @@
export { SelectField } from "./select-field";

103
src/core/theme/components/form-controls/select-field/select-field.tsx

@ -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
src/core/theme/components/form-controls/select-field/style.module.css

1
src/core/theme/components/form-controls/text-field/index.ts

@ -0,0 +1 @@
export { TextField } from "./text-field.control";

32
src/core/theme/components/form-controls/text-field/styles.module.css

@ -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
}

69
src/core/theme/components/form-controls/text-field/text-field.control.tsx

@ -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>
);
};

2
src/core/theme/components/index.ts

@ -0,0 +1,2 @@
export * from "./buttons";
export * from "./form-controls";

10
src/core/theme/typing/colors.interfaces.ts

@ -0,0 +1,10 @@
export interface Colors {
primatyText: string;
secondaryText: string;
primary: string;
disabledColor: string;
errorColor: string;
whiteText: string;
lightBlue: string;
lightYellow: string;
}

70
src/core/ui-kit/pages/buttons/buttons.page.tsx

@ -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>
);
};

1
src/core/ui-kit/pages/buttons/index.ts

@ -0,0 +1 @@
export { ButtonsPage } from "./buttons.page";

54
src/core/ui-kit/pages/forms/forms.page.tsx

@ -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>
);
};

1
src/core/ui-kit/pages/forms/index.ts

@ -0,0 +1 @@
export { FormsPage } from "./forms.page";

3
src/core/ui-kit/pages/index.ts

@ -0,0 +1,3 @@
export { UIKitPage } from "./root.page";
export { ButtonsPage } from "./buttons";
export { FormsPage } from "./forms";

23
src/core/ui-kit/pages/root.page.tsx

@ -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 />
</>
);
};

3
src/core/utils/helpers/styles.helper.ts

@ -0,0 +1,3 @@
export const createStyleSheet = <K extends string>(
styles: Record<K, React.CSSProperties>
) => styles;

1
src/navigation/hooks/interface.ts

@ -0,0 +1 @@
export * from "./use-routes.hook";

24
src/navigation/hooks/use-routes.hook.tsx

@ -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} />;
});
};

12
src/navigation/index.tsx

@ -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>
);
};

22
src/navigation/routes-config.tsx

@ -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 />,
},
],
},
];

9
src/navigation/typing/interfaces.ts

@ -0,0 +1,9 @@
interface RouteElement {
title: string;
path: string;
element?: JSX.Element;
}
export interface AppRoute extends RouteElement {
children?: RouteElement[];
}

1
src/vite-env.d.ts vendored

@ -0,0 +1 @@
/// <reference types="vite/client" />

34
tsconfig.json

@ -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"
}
]
}

11
tsconfig.node.json

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["vite.config.ts"]
}

12
vite.config.ts

@ -0,0 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": "/src",
"@/core": "/src/core",
},
},
});
Loading…
Cancel
Save