diff --git a/.gitignore b/.gitignore
index 7dca590..dd0c177 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,4 +2,5 @@
/.vscode
/.next
/.env
-/next-env.d.ts
\ No newline at end of file
+/next-env.d.ts
+/tsconfig.tsbuildinfo
\ No newline at end of file
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 87d7dcd..0451ed1 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -34,6 +34,8 @@ const eslintCommonRules = {
],
// https://eslint.org/docs/latest/rules/no-console
'no-console': ['error', { allow: ['warn', 'info', 'error'] }],
+ // Запретить использование пустых функций
+ 'no-empty-function': 'error',
// Форматирование объектов
'object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }],
// https://eslint.org/docs/latest/rules/padding-line-between-statements
diff --git a/package.json b/package.json
index 7d9aae3..1b28996 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
- "lint": "next lint && npx stylelint \"**/*.scss\""
+ "lint": "next lint && npx stylelint \"**/*.scss\" && tsc --noEmit"
},
"dependencies": {
"axios": "^1.7.9",
diff --git a/public/images/light-dark.avif b/public/images/light-dark.avif
new file mode 100644
index 0000000..1966010
Binary files /dev/null and b/public/images/light-dark.avif differ
diff --git a/public/images/light.avif b/public/images/light.avif
new file mode 100644
index 0000000..a05ed83
Binary files /dev/null and b/public/images/light.avif differ
diff --git a/src/app/(with-navbar)/layout.tsx b/src/app/(with-navbar)/layout.tsx
new file mode 100644
index 0000000..41bf83a
--- /dev/null
+++ b/src/app/(with-navbar)/layout.tsx
@@ -0,0 +1,14 @@
+import { Navbar } from '@/widgets/Navbar/client';
+
+interface INavbarLayout {
+ children: React.ReactNode
+}
+
+const NavbarLayout = ({ children }: INavbarLayout) => (
+ <>
+ {children}
+
+ >
+);
+
+export default NavbarLayout;
diff --git a/src/app/page.tsx b/src/app/(with-navbar)/page.tsx
similarity index 85%
rename from src/app/page.tsx
rename to src/app/(with-navbar)/page.tsx
index 50acba8..5610e4c 100644
--- a/src/app/page.tsx
+++ b/src/app/(with-navbar)/page.tsx
@@ -1,13 +1,12 @@
import { Headers } from '@/widgets/Headers';
import { InfoBlock } from '@/widgets/InfoBlock/client';
import { mockInfoBlockExercisesItems } from '@/widgets/InfoBlock/model/mock-exercises';
-import { Navbar } from '@/widgets/Navbar/client';
import { Layout } from '@/shared/ui';
const Home = () => (
<>
-
+
(
/>
-
>
);
diff --git a/src/app/(with-navbar)/settings/page.module.scss b/src/app/(with-navbar)/settings/page.module.scss
new file mode 100644
index 0000000..567a280
--- /dev/null
+++ b/src/app/(with-navbar)/settings/page.module.scss
@@ -0,0 +1,5 @@
+.layout {
+ display: flex;
+ flex-direction: column;
+ gap: 24px;
+}
\ No newline at end of file
diff --git a/src/app/(with-navbar)/settings/page.tsx b/src/app/(with-navbar)/settings/page.tsx
new file mode 100644
index 0000000..03a32da
--- /dev/null
+++ b/src/app/(with-navbar)/settings/page.tsx
@@ -0,0 +1,28 @@
+import { Headers } from '@/widgets/Headers';
+import { Navbar } from '@/widgets/Navbar/client';
+
+import { Layout } from '@/shared/ui';
+
+import { SettingsContent } from './ui/SettingsContent';
+
+import styles from './page.module.scss';
+
+import type { Metadata } from 'next';
+
+export const metadata: Metadata = {
+ title: 'Vees My | Настройки',
+};
+
+const Settings = () => (
+ <>
+
+
+
+
+
+
+
+ >
+);
+
+export default Settings;
diff --git a/src/app/(with-navbar)/settings/ui/SettingsContent.tsx b/src/app/(with-navbar)/settings/ui/SettingsContent.tsx
new file mode 100644
index 0000000..5a89580
--- /dev/null
+++ b/src/app/(with-navbar)/settings/ui/SettingsContent.tsx
@@ -0,0 +1,87 @@
+'use client';
+
+import {
+ ExportOutlineIcon,
+ FlashOutlineIcon,
+ FolderOutlineIcon,
+ ImportOutlineIcon,
+ LangIcon,
+ ThemeIcon,
+ VeesIcon,
+} from '@/shared/icons';
+import { Button, Section } from '@/shared/ui';
+
+import styles from './settingsContent.module.scss';
+
+export const SettingsContent = () => (
+ <>
+ ,
+ onClick: () => { /* void */ },
+ title: 'Тренировки',
+ },
+ {
+ color: '#FFB21A',
+ icon: ,
+ onClick: () => { /* void */ },
+ title: 'Настроить группы',
+ },
+ {
+ color: '#FA4838',
+ icon: ,
+ onClick: () => { /* void */ },
+ title: 'Упражнения',
+ },
+ ]}
+ />
+ ,
+ onClick: () => { /* void */ },
+ rightText: 'Русский',
+ title: 'Язык',
+ },
+ {
+ color: '#5AADF2',
+ icon: ,
+ onClick: () => { /* void */ },
+ rightText: 'Как в системе',
+ title: 'Оформление',
+ },
+ ]}
+ />
+ ,
+ onClick: () => { /* void */ },
+ title: 'Импорт настроек',
+ },
+ {
+ color: '#D15347',
+ description: 'Сохранить файл',
+ icon: ,
+ onClick: () => { /* void */ },
+ title: 'Экспорт настроек',
+ },
+ ]}
+ />
+
+ >
+);
diff --git a/src/app/(with-navbar)/settings/ui/settingsContent.module.scss b/src/app/(with-navbar)/settings/ui/settingsContent.module.scss
new file mode 100644
index 0000000..b446597
--- /dev/null
+++ b/src/app/(with-navbar)/settings/ui/settingsContent.module.scss
@@ -0,0 +1,3 @@
+.logout {
+ animation: var(--anim-scale-in);
+}
\ No newline at end of file
diff --git a/src/app/globals.scss b/src/app/globals.scss
index 0b92178..c923f3f 100644
--- a/src/app/globals.scss
+++ b/src/app/globals.scss
@@ -29,4 +29,8 @@ body {
&:hover {
background: theme('colors.base.100');
}
+}
+
+:disabled {
+ cursor: not-allowed;
}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f24c54d..7b8273e 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,6 +1,8 @@
import { Inter } from 'next/font/google';
import { ThemeProvider } from 'next-themes';
+import { Light } from '@/shared/ui';
+
import type { Metadata } from 'next';
import './globals.scss';
@@ -26,6 +28,7 @@ const RootLayout = ({ children }: IRootLayout) => (
{children}
+
diff --git a/src/app/loading.module.scss b/src/app/loading.module.scss
new file mode 100644
index 0000000..e33fc3b
--- /dev/null
+++ b/src/app/loading.module.scss
@@ -0,0 +1,24 @@
+.wrapper {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ gap: 8px;
+ height: 100vh;
+}
+
+.animation {
+ animation: var(--anim-scale-fade-in);
+}
+
+.content {
+ position: relative;
+}
+
+.loading {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+
+ transform: translate(-50%, -50%);
+}
\ No newline at end of file
diff --git a/src/app/loading.tsx b/src/app/loading.tsx
new file mode 100644
index 0000000..54d9ca3
--- /dev/null
+++ b/src/app/loading.tsx
@@ -0,0 +1,22 @@
+import { clsx } from 'clsx';
+
+import { LogoIcon } from '@/shared/icons';
+import { Loading as LoadingComponent, Text } from '@/shared/ui';
+
+import styles from './loading.module.scss';
+
+const Loading = () => (
+
+);
+
+export default Loading;
diff --git a/src/app/not-found.module.scss b/src/app/not-found.module.scss
new file mode 100644
index 0000000..1b7306e
--- /dev/null
+++ b/src/app/not-found.module.scss
@@ -0,0 +1,3 @@
+.wrapper {
+ height: 100vh;
+}
\ No newline at end of file
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 0000000..7c5ed52
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,31 @@
+import { AppRoutes } from '@/shared/constants';
+import { ArrowLeftIcon } from '@/shared/icons';
+import { Button, Flex, Text } from '@/shared/ui';
+
+import styles from './not-found.module.scss';
+
+const NotFound = () => (
+
+
+ 404
+ Такой страницы не существует
+
+ }
+ size="large"
+ to={AppRoutes.MAIN}
+ variant="solid"
+ >
+ Вернуться на главную
+
+
+);
+
+export default NotFound;
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
deleted file mode 100644
index f91caaa..0000000
--- a/src/app/settings/page.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Navbar } from '@/widgets/Navbar/client';
-
-const Home = () => (
- <>
- {/* */}
-
- Настройки
-
-
- >
-);
-
-export default Home;
diff --git a/src/app/vees/page.tsx b/src/app/vees/page.tsx
index 1b9dfd1..9e89276 100644
--- a/src/app/vees/page.tsx
+++ b/src/app/vees/page.tsx
@@ -8,7 +8,7 @@ import { Layout } from '@/shared/ui';
import { Timer } from '@/features/Timer/client';
-const Home = () => (
+const Vees = () => (
<>
@@ -23,4 +23,4 @@ const Home = () => (
>
);
-export default Home;
+export default Vees;
diff --git a/src/features/Timer/client.ts b/src/features/Timer/client.ts
index f8b97d0..5c0243a 100644
--- a/src/features/Timer/client.ts
+++ b/src/features/Timer/client.ts
@@ -1 +1 @@
-export { Timer } from './Timer';
+export { Timer } from './ui/Timer';
diff --git a/src/features/Timer/model/store.ts b/src/features/Timer/model/store.ts
new file mode 100644
index 0000000..e6222c8
--- /dev/null
+++ b/src/features/Timer/model/store.ts
@@ -0,0 +1 @@
+// TODO: use zustand for store
diff --git a/src/features/Timer/Timer.tsx b/src/features/Timer/ui/Timer.tsx
similarity index 100%
rename from src/features/Timer/Timer.tsx
rename to src/features/Timer/ui/Timer.tsx
diff --git a/src/features/Timer/timer.module.scss b/src/features/Timer/ui/timer.module.scss
similarity index 100%
rename from src/features/Timer/timer.module.scss
rename to src/features/Timer/ui/timer.module.scss
diff --git a/src/shared/constants/routes.ts b/src/shared/constants/routes.ts
index d867328..3043a77 100644
--- a/src/shared/constants/routes.ts
+++ b/src/shared/constants/routes.ts
@@ -1,5 +1,5 @@
export enum AppRoutes {
- ALL_VEES = '/all-vees',
+ MAIN = '/',
VEES = '/vees',
SETTINGS = '/settings',
// last
diff --git a/src/shared/icons/export-outline.tsx b/src/shared/icons/export-outline.tsx
new file mode 100644
index 0000000..e517afb
--- /dev/null
+++ b/src/shared/icons/export-outline.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const ExportOutlineIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ width = 28,
+}: IIcon) => (
+
+
+
+
+
+);
diff --git a/src/shared/icons/flash-outline.tsx b/src/shared/icons/flash-outline.tsx
new file mode 100644
index 0000000..dc7b306
--- /dev/null
+++ b/src/shared/icons/flash-outline.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const FlashOutlineIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ strokeWidth = 1.5,
+ width = 28,
+}: IIcon) => (
+
+
+
+);
diff --git a/src/shared/icons/folder-outline.tsx b/src/shared/icons/folder-outline.tsx
new file mode 100644
index 0000000..5981848
--- /dev/null
+++ b/src/shared/icons/folder-outline.tsx
@@ -0,0 +1,19 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const FolderOutlineIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ strokeWidth = 1.5,
+ width = 28,
+}: IIcon) => (
+
+
+
+);
diff --git a/src/shared/icons/import-outline.tsx b/src/shared/icons/import-outline.tsx
new file mode 100644
index 0000000..e25b95f
--- /dev/null
+++ b/src/shared/icons/import-outline.tsx
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const ImportOutlineIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ width = 28,
+}: IIcon) => (
+
+
+
+
+
+);
diff --git a/src/shared/icons/index.ts b/src/shared/icons/index.ts
index 9b72e5f..5e6f9f4 100644
--- a/src/shared/icons/index.ts
+++ b/src/shared/icons/index.ts
@@ -2,7 +2,13 @@ export * from './add-outline';
export * from './arrow-dropdown';
export * from './arrow-left';
export * from './drag';
+export * from './export-outline';
export * from './finish-outline';
+export * from './flash-outline';
+export * from './folder-outline';
+export * from './import-outline';
+export * from './lang';
+export * from './logo';
export * from './message';
export * from './paper-outline';
export * from './pause';
@@ -10,4 +16,6 @@ export * from './play';
export * from './plus';
export * from './setting-outline';
export * from './stopwatch-outline';
+export * from './theme';
+export * from './user';
export * from './vees';
diff --git a/src/shared/icons/lang.tsx b/src/shared/icons/lang.tsx
new file mode 100644
index 0000000..a2f7352
--- /dev/null
+++ b/src/shared/icons/lang.tsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const LangIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ strokeWidth = 1.5,
+ width = 28,
+}: IIcon) => (
+
+
+
+);
diff --git a/src/shared/icons/logo.tsx b/src/shared/icons/logo.tsx
new file mode 100644
index 0000000..ce32fa4
--- /dev/null
+++ b/src/shared/icons/logo.tsx
@@ -0,0 +1,26 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const LogoIcon = ({
+ className,
+ fill = 'currentColor',
+ height = 24,
+ strokeWidth = 1.5,
+ width = 24,
+}: IIcon) => (
+
+
+
+
+);
diff --git a/src/shared/icons/theme.tsx b/src/shared/icons/theme.tsx
new file mode 100644
index 0000000..f80b93b
--- /dev/null
+++ b/src/shared/icons/theme.tsx
@@ -0,0 +1,23 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const ThemeIcon = ({
+ fill = 'currentColor',
+ height = 28,
+ width = 28,
+}: IIcon) => (
+
+
+
+
+);
diff --git a/src/shared/icons/user.tsx b/src/shared/icons/user.tsx
new file mode 100644
index 0000000..3aa39bd
--- /dev/null
+++ b/src/shared/icons/user.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { Icon, IIcon } from '@/shared/ui';
+
+export const UserIcon = ({
+ fill = 'currentColor',
+ height = 24,
+ width = 24,
+}: IIcon) => (
+
+
+
+);
diff --git a/src/shared/styles/custom.scss b/src/shared/styles/custom.scss
index 13e08a6..87f1d3a 100644
--- a/src/shared/styles/custom.scss
+++ b/src/shared/styles/custom.scss
@@ -13,3 +13,43 @@ path {
50%{ transform: translateY(0) scale(1.2); }
100%{ transform: translateY(0) scale(1); }
}
+
+:root {
+ --anim-scale-fade-in: scale-fade-in .3s ease-out;
+ --anim-fade-in: fade-in .3s ease-out;
+ --anim-scale-in: scale-in .3s ease-out;
+}
+
+@keyframes scale-fade-in {
+ 0% {
+ opacity: 0;
+
+ transform: scale(0.95);
+ }
+
+ 100% {
+ opacity: 1;
+
+ transform: scale(1);
+ }
+}
+
+@keyframes scale-in {
+ 0% {
+ transform: scale(0.95);
+ }
+
+ 100% {
+ transform: scale(1);
+ }
+}
+
+@keyframes fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/shared/types/index.ts b/src/shared/types/index.ts
index 3008d70..a2b8e6c 100644
--- a/src/shared/types/index.ts
+++ b/src/shared/types/index.ts
@@ -1,5 +1,11 @@
import { JSX } from 'react';
+import { AppRoutes } from '../constants';
+
export type * from './tailwind';
export type TComponent = keyof JSX.IntrinsicElements | React.ElementType;
+
+export type TLinkOrClick =
+ | (T & { href: AppRoutes, onClick?: never })
+ | (T & { href?: never, onClick: () => void });
diff --git a/src/shared/types/tailwind.ts b/src/shared/types/tailwind.ts
index 89d62ed..6efdcf1 100644
--- a/src/shared/types/tailwind.ts
+++ b/src/shared/types/tailwind.ts
@@ -37,9 +37,7 @@ type Colors =
| 'pink'
| 'rose';
-type ColorWithShades = `text-${T}-${Shades}`;
-
-export type TailwindColors = ColorWithShades;
+export type TailwindColors = `text-${Colors}-${Shades}`;
export type TailwindWeight =
| 'font-extralight'
diff --git a/src/shared/ui/Avatar/Avatar.tsx b/src/shared/ui/Avatar/Avatar.tsx
new file mode 100644
index 0000000..5219448
--- /dev/null
+++ b/src/shared/ui/Avatar/Avatar.tsx
@@ -0,0 +1,20 @@
+import { clsx } from 'clsx';
+
+import { UserIcon } from '@/shared/icons';
+
+import styles from './avatar.module.scss';
+
+interface IAvatar {
+ bordered?: boolean
+}
+
+export const Avatar = ({
+ bordered = true,
+}: IAvatar) => (
+
+);
diff --git a/src/shared/ui/Avatar/avatar.module.scss b/src/shared/ui/Avatar/avatar.module.scss
new file mode 100644
index 0000000..e38848d
--- /dev/null
+++ b/src/shared/ui/Avatar/avatar.module.scss
@@ -0,0 +1,33 @@
+.wrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ width: 34px;
+ height: 34px;
+}
+
+.bordered {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ border-radius: 12px;
+ border: 2px solid theme('colors.primary.500');
+}
+
+.avatar {
+ $outline-size: 8px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ width: calc(100% - $outline-size);
+ height: calc(100% - $outline-size);
+
+ color: theme('colors.base.500');
+ background: theme('colors.base.700');
+ border-radius: 8px;
+}
\ No newline at end of file
diff --git a/src/shared/ui/Avatar/index.ts b/src/shared/ui/Avatar/index.ts
new file mode 100644
index 0000000..d3fb6df
--- /dev/null
+++ b/src/shared/ui/Avatar/index.ts
@@ -0,0 +1 @@
+export { Avatar } from './Avatar';
diff --git a/src/shared/ui/Button/Button.tsx b/src/shared/ui/Button/Button.tsx
index a03572e..51bb6b8 100644
--- a/src/shared/ui/Button/Button.tsx
+++ b/src/shared/ui/Button/Button.tsx
@@ -1,23 +1,26 @@
import { clsx } from 'clsx';
import Link from 'next/link';
+import { AppRoutes } from '@/shared/constants';
+
import styles from './button.module.scss';
interface IButton {
align?: 'start' | 'center' | 'end'
children?: React.ReactNode
className?: string
- color?: 'base' | 'primary'
+ color?: 'base' | 'primary' | 'red'
disabled?: boolean
full?: boolean
icon?: React.ReactNode
iconOnly?: boolean
+ iconRight?: React.ReactNode
isActiveAnimation?: boolean
onClick?: () => void
radius?: 'none' | 'medium' | 'full'
size?: 'none' | 'medium' | 'large'
- to?: string
- variant?: 'solid' | 'light' | 'flat' | 'none'
+ to?: AppRoutes
+ variant?: 'solid' | 'light' | 'flat' | 'bordered' | 'none'
}
export const Button = ({
@@ -29,6 +32,7 @@ export const Button = ({
full,
icon,
iconOnly,
+ iconRight,
isActiveAnimation = true,
onClick,
radius = 'medium',
@@ -61,6 +65,7 @@ export const Button = ({
>
{icon}
{children}
+ {iconRight}
);
};
diff --git a/src/shared/ui/Button/button.module.scss b/src/shared/ui/Button/button.module.scss
index 04dac1a..6af4f8f 100644
--- a/src/shared/ui/Button/button.module.scss
+++ b/src/shared/ui/Button/button.module.scss
@@ -50,6 +50,18 @@
}
}
+@mixin variant-bordered($color) {
+ color: theme('colors.#{$color}.500');
+ background: theme('colors.#{$color}.500 / 10%');
+ border: 1px solid theme('colors.#{$color}.500');
+
+ &:not(.disabled):hover {
+ color: theme('colors.#{$color}.600');
+ background: theme('colors.#{$color}.500 / 20%');
+ border-color: theme('colors.#{$color}.600');
+ }
+}
+
@mixin variants($color) {
&.variant-solid {
@include variant-solid($color);
@@ -62,6 +74,10 @@
&.variant-flat {
@include variant-flat($color);
}
+
+ &.variant-bordered {
+ @include variant-bordered($color);
+ }
}
.color-primary {
@@ -72,6 +88,10 @@
@include variants('base');
}
+.color-red {
+ @include variants('red');
+}
+
.radius-full {
border-radius: 100px;
}
diff --git a/src/shared/ui/Divider/Divider.tsx b/src/shared/ui/Divider/Divider.tsx
index 64187d3..5d728b7 100644
--- a/src/shared/ui/Divider/Divider.tsx
+++ b/src/shared/ui/Divider/Divider.tsx
@@ -4,8 +4,9 @@ import styles from './divider.module.scss';
interface IDivider {
className?: string
+ size?: 'none' | 'small'
}
-export const Divider = ({ className }: IDivider) => (
-
+export const Divider = ({ className, size = 'small' }: IDivider) => (
+
);
diff --git a/src/shared/ui/Divider/divider.module.scss b/src/shared/ui/Divider/divider.module.scss
index 7fae532..40b420f 100644
--- a/src/shared/ui/Divider/divider.module.scss
+++ b/src/shared/ui/Divider/divider.module.scss
@@ -1,7 +1,9 @@
.divider {
- margin-top: 6px;
- margin-bottom: 6px;
-
color: theme('colors.base.500 / 15%');
border-top: dashed 1px;
+}
+
+.size-small {
+ margin-top: 6px;
+ margin-bottom: 6px;
}
\ No newline at end of file
diff --git a/src/shared/ui/Empty/Empty.tsx b/src/shared/ui/Empty/Empty.tsx
index 7bbf1f3..0814bf3 100644
--- a/src/shared/ui/Empty/Empty.tsx
+++ b/src/shared/ui/Empty/Empty.tsx
@@ -1,5 +1,4 @@
-import Image from 'next/image';
-
+import { Image } from '../client';
import { Flex } from '../Flex';
import { Text } from '../Text';
diff --git a/src/shared/ui/Empty/empty.module.scss b/src/shared/ui/Empty/empty.module.scss
index f99b02d..03e530a 100644
--- a/src/shared/ui/Empty/empty.module.scss
+++ b/src/shared/ui/Empty/empty.module.scss
@@ -4,8 +4,8 @@
}
.icon {
- max-width: 64px;
- max-height: 64px;
+ width: 64px;
+ height: 64px;
pointer-events: none;
user-select: none;
}
\ No newline at end of file
diff --git a/src/shared/ui/Flex/Flex.tsx b/src/shared/ui/Flex/Flex.tsx
index b8f99a5..e201361 100644
--- a/src/shared/ui/Flex/Flex.tsx
+++ b/src/shared/ui/Flex/Flex.tsx
@@ -14,6 +14,7 @@ interface IFlex {
full?: boolean
gap?: 'small' | 'medium' | 'large' | number
justify?: TAlign | 'between'
+ style?: React.CSSProperties
vertical?: boolean
wrap?: boolean
}
@@ -26,6 +27,7 @@ export const Flex = ({
full = true,
gap,
justify,
+ style,
vertical,
wrap,
}: IFlex) => (
@@ -42,7 +44,7 @@ export const Flex = ({
},
className,
)}
- style={{ gap: typeof gap === 'number' ? `${gap}px` : undefined }}
+ style={{ gap: typeof gap === 'number' ? `${gap}px` : undefined, ...style }}
>
{children}
diff --git a/src/shared/ui/Header/Header.tsx b/src/shared/ui/Header/Header.tsx
index 730cb78..3a64a0e 100644
--- a/src/shared/ui/Header/Header.tsx
+++ b/src/shared/ui/Header/Header.tsx
@@ -1,8 +1,10 @@
import { clsx } from 'clsx';
-import { getCurrentDateInfo } from '@/shared/lib/utils';
+import { LogoIcon } from '@/shared/icons';
import { Background, Flex, Layout, Text } from '@/shared/ui';
+import { CurrentDate } from './utils/CurrentDate';
+
import styles from './header.module.scss';
interface IHeader {
@@ -10,6 +12,7 @@ interface IHeader {
leftActions?: React.ReactNode
placement?: 'center' | 'start'
rightActions?: React.ReactNode
+ showLogo?: boolean
title: string
}
@@ -18,42 +21,40 @@ export const Header = ({
leftActions,
placement = 'center',
rightActions,
+ showLogo,
title,
-}: IHeader) => {
- const { dayOfMonth, dayOfWeek, monthNumber } = getCurrentDateInfo();
-
- const currentDate = `Сегодня ${dayOfWeek} ${dayOfMonth}.${monthNumber}`;
-
- return (
-
-
- {leftActions ? (
-
- {leftActions}
-
- ) : null}
-
-
+}: IHeader) => (
+
+
+ {leftActions ? (
+
+ {leftActions}
+
+ ) : null}
+
+
+
{title}
+ {showLogo ? : null}
+
+
+ {description === null ? null : (
+
+ {description ?? }
- {description !== null && (
-
- {description ?? currentDate}
-
- )}
+ )}
+
+ {rightActions ? (
+
+ {rightActions}
- {rightActions ? (
-
- {rightActions}
-
- ) : null}
-
-
- );
-};
+ ) : null}
+
+
+);
diff --git a/src/shared/ui/Header/header.module.scss b/src/shared/ui/Header/header.module.scss
index 86c60e4..70fe247 100644
--- a/src/shared/ui/Header/header.module.scss
+++ b/src/shared/ui/Header/header.module.scss
@@ -10,8 +10,7 @@
.layout {
display: flex;
align-items: center;
- padding-top: 12px;
- padding-bottom: 12px;
+ height: 64px;
text-wrap: nowrap;
}
@@ -27,3 +26,8 @@
justify-content: end;
}
}
+
+.logo path {
+ fill: theme('colors.primary.500 / 25%');
+ stroke: theme('colors.primary.500');
+}
\ No newline at end of file
diff --git a/src/shared/ui/Header/utils/CurrentDate.tsx b/src/shared/ui/Header/utils/CurrentDate.tsx
new file mode 100644
index 0000000..78b1f45
--- /dev/null
+++ b/src/shared/ui/Header/utils/CurrentDate.tsx
@@ -0,0 +1,11 @@
+'use client';
+
+import { getCurrentDateInfo } from '@/shared/lib/utils';
+
+export const CurrentDate = () => {
+ const { dayOfMonth, dayOfWeek, monthNumber } = getCurrentDateInfo();
+
+ return <>{`Сегодня ${dayOfWeek} ${dayOfMonth}.${monthNumber}`}>;
+};
+
+export const dynamic = 'force-dynamic';
diff --git a/src/shared/ui/Image/Image.tsx b/src/shared/ui/Image/Image.tsx
new file mode 100644
index 0000000..a4f7987
--- /dev/null
+++ b/src/shared/ui/Image/Image.tsx
@@ -0,0 +1,42 @@
+'use client';
+
+import { clsx } from 'clsx';
+import NextImage from 'next/image';
+import { useState } from 'react';
+
+import { Skeleton } from '../Skeleton';
+
+import styles from './image.module.scss';
+
+interface IImage {
+ alt: string
+ className?: string
+ height: number
+ src: string
+ width: number
+}
+
+export const Image = ({
+ alt,
+ className,
+ height,
+ src,
+ width,
+}: IImage) => {
+ const [isLoaded, setIsLoaded] = useState(false);
+
+ return (
+
+ {isLoaded ? null : }
+ setIsLoaded(true)}
+ src={src}
+ width={width}
+ />
+
+ );
+};
diff --git a/src/shared/ui/Image/client.ts b/src/shared/ui/Image/client.ts
new file mode 100644
index 0000000..e463e46
--- /dev/null
+++ b/src/shared/ui/Image/client.ts
@@ -0,0 +1 @@
+export { Image } from './Image';
diff --git a/src/shared/ui/Image/image.module.scss b/src/shared/ui/Image/image.module.scss
new file mode 100644
index 0000000..d50bcdd
--- /dev/null
+++ b/src/shared/ui/Image/image.module.scss
@@ -0,0 +1,8 @@
+.wrapper {
+ position: relative;
+}
+
+.loading {
+ position: absolute;
+ height: 100%;
+}
\ No newline at end of file
diff --git a/src/shared/ui/Light/Light.tsx b/src/shared/ui/Light/Light.tsx
new file mode 100644
index 0000000..d016b8a
--- /dev/null
+++ b/src/shared/ui/Light/Light.tsx
@@ -0,0 +1,26 @@
+import styles from './light.module.scss';
+
+export const Light = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+);
diff --git a/src/shared/ui/Light/index.ts b/src/shared/ui/Light/index.ts
new file mode 100644
index 0000000..5dd45ee
--- /dev/null
+++ b/src/shared/ui/Light/index.ts
@@ -0,0 +1 @@
+export { Light } from './Light';
diff --git a/src/shared/ui/Light/light.module.scss b/src/shared/ui/Light/light.module.scss
new file mode 100644
index 0000000..7f10af4
--- /dev/null
+++ b/src/shared/ui/Light/light.module.scss
@@ -0,0 +1,32 @@
+.wrapper {
+ display: flex;
+ justify-content: center;
+ position: absolute;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: theme('zIndex.light');
+
+ overflow: clip;
+
+ animation: fade-in 1s ease-in-out;
+ pointer-events: none;
+ user-select: none;
+}
+
+.content {
+ width: 108rem;
+
+ opacity: .25;
+ flex: none;
+}
+
+@keyframes fade-in {
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
+}
\ No newline at end of file
diff --git a/src/shared/ui/Loading/Loading.tsx b/src/shared/ui/Loading/Loading.tsx
new file mode 100644
index 0000000..5ddcef6
--- /dev/null
+++ b/src/shared/ui/Loading/Loading.tsx
@@ -0,0 +1,30 @@
+import { clsx } from 'clsx';
+
+import { TailwindColors } from '@/shared/types';
+
+import styles from './loading.module.scss';
+
+interface ILoading {
+ className?: string
+ color?: TailwindColors
+ size?: 'small' | 'medium' | 'large'
+}
+
+export const Loading = ({
+ className,
+ color = 'text-primary-500',
+ size = 'small',
+}: ILoading) => (
+
+
+
+
+);
diff --git a/src/shared/ui/Loading/index.ts b/src/shared/ui/Loading/index.ts
new file mode 100644
index 0000000..5ee0d48
--- /dev/null
+++ b/src/shared/ui/Loading/index.ts
@@ -0,0 +1 @@
+export { Loading } from './Loading';
diff --git a/src/shared/ui/Loading/loading.module.scss b/src/shared/ui/Loading/loading.module.scss
new file mode 100644
index 0000000..dc54a84
--- /dev/null
+++ b/src/shared/ui/Loading/loading.module.scss
@@ -0,0 +1,65 @@
+.wrapper {
+ display: block;
+ position: relative;
+}
+
+.item {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ border-radius: 50%;
+ border-color: transparent;
+ border-bottom-color: currentcolor;
+ border-style: solid;
+
+ &.first {
+ animation: 0.8s ease 0s infinite normal none running rotate-360;
+ }
+
+ &.second {
+ opacity: 0.5;
+
+ animation: 0.8s linear 0s infinite normal none running rotate-360;
+ border-bottom-style: dotted;
+ }
+}
+
+@keyframes rotate-360 {
+ 0% {
+ transform: rotate(0deg);
+ }
+
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+.size-small {
+ width: 1.5rem;
+ height: 1.5rem;
+
+ .item {
+ border-width: 2px;
+ }
+}
+
+.size-medium {
+ width: 2.5rem;
+ height: 2.5rem;
+
+ .item {
+ border-width: 3px;
+ }
+}
+
+.size-large {
+ width: 4rem;
+ height: 4rem;
+
+ .item {
+ border-width: 4px;
+ }
+}
\ No newline at end of file
diff --git a/src/shared/ui/Modal/ui/Base/base.module.scss b/src/shared/ui/Modal/ui/Base/base.module.scss
index 811d135..0ff0eef 100644
--- a/src/shared/ui/Modal/ui/Base/base.module.scss
+++ b/src/shared/ui/Modal/ui/Base/base.module.scss
@@ -3,9 +3,6 @@
}
.title, .action, .content {
- opacity: 1;
-
- transform: scale(1);
animation: scale-in 0.15s ease-out;
}
@@ -24,11 +21,14 @@
}
body[data-closing="true"] {
- .title, .action, .content {
+ .title, .action {
opacity: 0;
transform: scale(1.05);
- animation: scale-out 0.1s ease-in;
+ }
+
+ .content {
+ opacity: 0;
}
}
@@ -44,18 +44,4 @@ body[data-closing="true"] {
transform: scale(1);
}
-}
-
-@keyframes scale-out {
- 0% {
- opacity: 1;
-
- transform: scale(1);
- }
-
- 100% {
- opacity: 0;
-
- transform: scale(1.05);
- }
}
\ No newline at end of file
diff --git a/src/shared/ui/Modal/ui/Modal/modal.global.scss b/src/shared/ui/Modal/ui/Modal/modal.global.scss
index c1a8e5f..ff2ca26 100644
--- a/src/shared/ui/Modal/ui/Modal/modal.global.scss
+++ b/src/shared/ui/Modal/ui/Modal/modal.global.scss
@@ -8,8 +8,11 @@
height: 100vh;
background: theme('colors.black / 20%');
+ opacity: 1;
- animation: fade-in .3s ease-out forwards;
+ transition: opacity ease .3s;
+
+ animation: var(--anim-fade-in);
backdrop-filter: blur(theme('backdropBlur.xl')) saturate(1.5);
}
@@ -20,27 +23,7 @@ body {
&[data-closing="true"] {
.overlay {
- animation: fade-out .1s linear forwards;
+ opacity: 0;
}
}
-}
-
-@keyframes fade-in {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-@keyframes fade-out {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0;
- }
}
\ No newline at end of file
diff --git a/src/shared/ui/Section/Section.tsx b/src/shared/ui/Section/Section.tsx
new file mode 100644
index 0000000..f8b6eae
--- /dev/null
+++ b/src/shared/ui/Section/Section.tsx
@@ -0,0 +1,73 @@
+import { Fragment } from 'react';
+
+import { ArrowDropdownIcon } from '@/shared/icons';
+import { TLinkOrClick } from '@/shared/types';
+import { Button, Divider, Flex, Text } from '@/shared/ui';
+
+import { Background } from '../Background';
+
+import styles from './section.module.scss';
+
+interface ISectionBaseItem {
+ color: string
+ description?: string
+ icon: React.ReactNode
+ rightText?: string
+ title: string
+}
+
+type TSectionItem = TLinkOrClick;
+
+interface ISection {
+ items: TSectionItem[]
+}
+
+export const Section = ({ items }: ISection) => (
+
+ {items.map(({
+ color,
+ description,
+ href,
+ icon,
+ onClick,
+ rightText,
+ title,
+ }, index) => (
+
+
+ {index !== items.length - 1 ? : null}
+
+ ))}
+
+);
diff --git a/src/shared/ui/Section/index.ts b/src/shared/ui/Section/index.ts
new file mode 100644
index 0000000..8b51c65
--- /dev/null
+++ b/src/shared/ui/Section/index.ts
@@ -0,0 +1 @@
+export { Section } from './Section';
diff --git a/src/shared/ui/Section/section.module.scss b/src/shared/ui/Section/section.module.scss
new file mode 100644
index 0000000..1978826
--- /dev/null
+++ b/src/shared/ui/Section/section.module.scss
@@ -0,0 +1,36 @@
+.wrapper {
+ overflow: clip;
+
+ animation: var(--anim-scale-fade-in);
+}
+
+.item {
+ padding: 12px 16px;
+
+ &:hover {
+ background: theme('colors.base.800 / 75%');
+ }
+}
+
+.icon {
+ min-width: 28px;
+ max-width: 28px;
+ min-height: 28px;
+ max-height: 28px;
+
+ border-radius: theme('borderRadius.md');
+}
+
+.divider {
+ width: calc(100% - 56px);
+ margin-top: -0.5px;
+ margin-left: auto;
+}
+
+.title {
+ line-height: 1.25rem !important;
+}
+
+.arrow {
+ transform: rotate(90deg);
+}
\ No newline at end of file
diff --git a/src/shared/ui/Skeleton/Skeleton.tsx b/src/shared/ui/Skeleton/Skeleton.tsx
new file mode 100644
index 0000000..d21159f
--- /dev/null
+++ b/src/shared/ui/Skeleton/Skeleton.tsx
@@ -0,0 +1,12 @@
+import { clsx } from 'clsx';
+
+import styles from './skeleton.module.scss';
+
+interface ISkeleton {
+ className?: string
+ style?: React.CSSProperties
+}
+
+export const Skeleton = ({ className, style }: ISkeleton) => (
+
+);
diff --git a/src/shared/ui/Skeleton/index.ts b/src/shared/ui/Skeleton/index.ts
new file mode 100644
index 0000000..3f34073
--- /dev/null
+++ b/src/shared/ui/Skeleton/index.ts
@@ -0,0 +1 @@
+export { Skeleton } from './Skeleton';
diff --git a/src/shared/ui/Skeleton/skeleton.module.scss b/src/shared/ui/Skeleton/skeleton.module.scss
new file mode 100644
index 0000000..090361a
--- /dev/null
+++ b/src/shared/ui/Skeleton/skeleton.module.scss
@@ -0,0 +1,38 @@
+.skeleton {
+ position: relative;
+ width: 100%;
+ height: 32px;
+
+ background: theme('colors.base.800 / 75%');
+ border-radius: theme('borderRadius.lg');
+ overflow: hidden;
+
+ transition: all ease 1s;
+ animation: var(--anim-scale-fade-in);
+
+ @apply border border-base-600/15;
+
+ &::before {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: -100%;
+ width: 100%;
+ height: 100%;
+
+ background: linear-gradient(to right, transparent 0%, theme('colors.base.700 / 50%') 50%, transparent 100%);
+
+ animation: load 1s cubic-bezier(0.4, 0, 0.2, 1) infinite;
+ content: "";
+ }
+}
+
+@keyframes load {
+ 0% {
+ left: -100%;
+ }
+
+ 100% {
+ left: 100%;
+ }
+}
diff --git a/src/shared/ui/client.ts b/src/shared/ui/client.ts
index abc5e00..7f30899 100644
--- a/src/shared/ui/client.ts
+++ b/src/shared/ui/client.ts
@@ -1 +1,2 @@
+export * from './Image/client';
export * from './Modal/client';
diff --git a/src/shared/ui/index.ts b/src/shared/ui/index.ts
index 0d0f1da..9ac2c33 100644
--- a/src/shared/ui/index.ts
+++ b/src/shared/ui/index.ts
@@ -1,3 +1,4 @@
+export * from './Avatar';
export * from './Background';
export * from './Button';
export * from './Chip';
@@ -8,5 +9,9 @@ export * from './Header';
export * from './Icon';
export * from './Input';
export * from './Layout';
+export * from './Light';
+export * from './Loading';
export * from './Modal';
+export * from './Section';
+export * from './Skeleton';
export * from './Text';
diff --git a/src/widgets/AddExerciseModal/ui/AddExerciseModal.tsx b/src/widgets/AddExerciseModal/ui/AddExerciseModal.tsx
index b685d00..a243efc 100644
--- a/src/widgets/AddExerciseModal/ui/AddExerciseModal.tsx
+++ b/src/widgets/AddExerciseModal/ui/AddExerciseModal.tsx
@@ -1,11 +1,11 @@
'use client';
import { clsx } from 'clsx';
-import Image from 'next/image';
import { useState } from 'react';
import { AddOutlineIcon, SettingsOutlineIcon } from '@/shared/icons';
import { Button, Flex, Text, Background, Input, ModalBase, Chip, Empty } from '@/shared/ui';
+import { Image } from '@/shared/ui/client';
import styles from './addExerciseModal.module.scss';
diff --git a/src/widgets/AddExerciseModal/ui/addExerciseModal.module.scss b/src/widgets/AddExerciseModal/ui/addExerciseModal.module.scss
index f881280..6a69264 100644
--- a/src/widgets/AddExerciseModal/ui/addExerciseModal.module.scss
+++ b/src/widgets/AddExerciseModal/ui/addExerciseModal.module.scss
@@ -21,10 +21,7 @@
}
.visible {
- opacity: 1;
-
- transform: scale(1);
- animation: fade-in .5s ease;
+ animation: var(--anim-scale-fade-in);
pointer-events: auto;
}
@@ -94,18 +91,4 @@
height: 100%;
font-size: theme('fontSize.sm');
-}
-
-@keyframes fade-in {
- 0% {
- opacity: 0;
-
- transform: scale(0.95);
- }
-
- 100% {
- opacity: 1;
-
- transform: scale(1);
- }
}
\ No newline at end of file
diff --git a/src/widgets/Headers/index.tsx b/src/widgets/Headers/index.tsx
index 8ec1564..b153283 100644
--- a/src/widgets/Headers/index.tsx
+++ b/src/widgets/Headers/index.tsx
@@ -1,5 +1,6 @@
-import { AllVees } from './ui/AllVees/AllVees';
import { ExercisesList } from './ui/ExercisesList/ExercisesList';
+import { Main } from './ui/Main/Main';
+import { Settings } from './ui/Settings/Settings';
export const Headers = () => {
throw new Error(
@@ -8,4 +9,5 @@ export const Headers = () => {
};
Headers.ExercisesList = ExercisesList;
-Headers.AllVees = AllVees;
+Headers.Main = Main;
+Headers.Settings = Settings;
diff --git a/src/widgets/Headers/ui/ExercisesList/ExercisesList.tsx b/src/widgets/Headers/ui/ExercisesList/ExercisesList.tsx
index 82a72f6..3778c56 100644
--- a/src/widgets/Headers/ui/ExercisesList/ExercisesList.tsx
+++ b/src/widgets/Headers/ui/ExercisesList/ExercisesList.tsx
@@ -1,6 +1,7 @@
import { AddExerciseModal } from '@/widgets/AddExerciseModal/client';
import { mockAddExerciseModal } from '@/widgets/AddExerciseModal/model/mock';
+import { AppRoutes } from '@/shared/constants';
import { ArrowLeftIcon, FinishOutlineIcon } from '@/shared/icons';
import { Button, Header } from '@/shared/ui';
@@ -8,7 +9,7 @@ export const ExercisesList = () => (
-