forked from templates/nextjs-template
chore: обновить инфраструктуру Mantine и шаблоны
- добавлен алиас infra и обновлены импорты Mantine - настроены тема, цветовая схема и CSS-переменные Mantine - добавлен переключатель цветовой схемы на главный экран - обновлены шаблоны бизнес- и infra-модулей под фабрики - добавлена команда генерации Petstore API и исключён generated-код из Biome - обновлены JSDoc-комментарии React-компонентов
This commit is contained in:
@@ -1,2 +1,5 @@
|
||||
# Базовый URL приложения
|
||||
NEXT_PUBLIC_APP_URL=http://localhost:3000
|
||||
|
||||
# Базовый URL локального Swagger Petstore API
|
||||
NEXT_PUBLIC_PET_STORE_API_URL=http://localhost:8080/api/v3
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export { {{name.pascalCase}}Business } from './{{name.kebabCase}}.business'
|
||||
export { {{name.camelCase}}Factory } from './{{name.kebabCase}}.factory';
|
||||
export type { {{name.pascalCase}} } from './types/{{name.kebabCase}}.type';
|
||||
export type { {{name.pascalCase}}Api } from './types/{{name.kebabCase}}-api.type';
|
||||
export type { {{name.pascalCase}}Deps } from './types/{{name.kebabCase}}-deps.type';
|
||||
export type { {{name.pascalCase}}Factory } from './types/{{name.kebabCase}}-factory.type';
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Публичный API бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Api = Record<string, never>;
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Зависимости бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Deps = Record<string, never>;
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { {{name.pascalCase}}Api } from './{{name.kebabCase}}-api.type';
|
||||
import type { {{name.pascalCase}}Deps } from './{{name.kebabCase}}-deps.type';
|
||||
|
||||
/**
|
||||
* Фабрика публичного API бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Factory = (deps: {{name.pascalCase}}Deps) => {{name.pascalCase}}Api;
|
||||
@@ -1,11 +1,4 @@
|
||||
import type { HTMLAttributes } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры бизнес-модуля {{name.pascalCase}}.
|
||||
* Доменная сущность {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}BusinessParams = {}
|
||||
|
||||
/** HTML-атрибуты корневого элемента. */
|
||||
type RootAttrs = HTMLAttributes<HTMLDivElement>
|
||||
|
||||
export type {{name.pascalCase}}BusinessProps = RootAttrs & {{name.pascalCase}}BusinessParams
|
||||
export type {{name.pascalCase}} = Record<string, never>;
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}BusinessProps } from './types/{{name.kebabCase}}.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение бизнес-модуля {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Business = (props: {{name.pascalCase}}BusinessProps) => {
|
||||
const { children, className, ...htmlAttr } = props
|
||||
|
||||
return (
|
||||
<div {...htmlAttr} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { {{name.pascalCase}}Factory } from './types/{{name.kebabCase}}-factory.type';
|
||||
|
||||
/**
|
||||
* Создаёт публичный API бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export const {{name.camelCase}}Factory: {{name.pascalCase}}Factory = () => {
|
||||
return {};
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
.root {
|
||||
}
|
||||
@@ -13,7 +13,8 @@
|
||||
"!.next",
|
||||
"!dist",
|
||||
"!build",
|
||||
"!.templates"
|
||||
"!.templates",
|
||||
"!src/infrastructure/**/generated"
|
||||
]
|
||||
},
|
||||
"formatter": {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"lint": "biome check",
|
||||
"format": "biome format --write",
|
||||
"sprite": "svg-sprites",
|
||||
"codegen:pet-store-api": "npx @gromlab/api-codegen@latest -i http://localhost:8080/api/v3/openapi.json -o src/infrastructure/pet-store-api/generated -n pet-store-api.generated",
|
||||
"predev": "svg-sprites",
|
||||
"prebuild": "svg-sprites"
|
||||
},
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import '@mantine/core/styles.css';
|
||||
import 'shared/styles/global.css';
|
||||
|
||||
import { ColorSchemeScript } from '@mantine/core';
|
||||
import { MantineProvider } from 'infrastructure/mantine';
|
||||
import {
|
||||
MantineColorSchemeScript,
|
||||
MantineProvider,
|
||||
mantineHtmlProps,
|
||||
} from 'infra/mantine';
|
||||
import type { Metadata } from 'next';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
@@ -31,11 +34,18 @@ export const metadata: Metadata = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Корневой layout приложения Next.js.
|
||||
*
|
||||
* Используется для:
|
||||
* - подключения провайдеров приложения
|
||||
* - установки базовой HTML-структуры всех страниц
|
||||
*/
|
||||
export default function RootLayout({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<html lang="ru" suppressHydrationWarning>
|
||||
<html lang="ru" {...mantineHtmlProps}>
|
||||
<head>
|
||||
<ColorSchemeScript />
|
||||
<MantineColorSchemeScript />
|
||||
<link rel="preload" href="/sprites/icons.sprite.svg" as="image" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
@@ -6,6 +6,13 @@ export const metadata: Metadata = {
|
||||
description: 'Главная страница приложения',
|
||||
};
|
||||
|
||||
/**
|
||||
* Страница главного маршрута приложения.
|
||||
*
|
||||
* Используется для:
|
||||
* - отображения главного экрана в корневом маршруте
|
||||
* - композиции контента главной страницы
|
||||
*/
|
||||
export default function HomePage() {
|
||||
return <HomeScreen />;
|
||||
}
|
||||
|
||||
4
src/infra/mantine/config/color-scheme.config.ts
Normal file
4
src/infra/mantine/config/color-scheme.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import type { MantineColorScheme } from '@mantine/core';
|
||||
|
||||
export const MANTINE_COLOR_SCHEME_STORAGE_KEY = 'app-color-scheme';
|
||||
export const MANTINE_DEFAULT_COLOR_SCHEME: MantineColorScheme = 'auto';
|
||||
12
src/infra/mantine/config/dark-theme.config.ts
Normal file
12
src/infra/mantine/config/dark-theme.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const darkTheme = {
|
||||
primary: '#60a5fa',
|
||||
background: '#0f172a',
|
||||
backgroundHover: '#1e293b',
|
||||
surface: '#111827',
|
||||
text: '#f8fafc',
|
||||
textSecondary: '#cbd5e1',
|
||||
border: '#334155',
|
||||
error: '#ff6b6b',
|
||||
success: '#69db7c',
|
||||
warning: '#ffd43b',
|
||||
} as const;
|
||||
12
src/infra/mantine/config/light-theme.config.ts
Normal file
12
src/infra/mantine/config/light-theme.config.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export const lightTheme = {
|
||||
primary: '#2563eb',
|
||||
background: '#ffffff',
|
||||
backgroundHover: '#f8fafc',
|
||||
surface: '#ffffff',
|
||||
text: '#111827',
|
||||
textSecondary: '#64748b',
|
||||
border: '#e2e8f0',
|
||||
error: '#e03131',
|
||||
success: '#2f9e44',
|
||||
warning: '#e67700',
|
||||
} as const;
|
||||
@@ -0,0 +1,52 @@
|
||||
import type { CSSVariablesResolver } from '@mantine/core';
|
||||
import { darkTheme } from './dark-theme.config';
|
||||
import { lightTheme } from './light-theme.config';
|
||||
|
||||
type ThemePalette = {
|
||||
primary: string;
|
||||
background: string;
|
||||
backgroundHover: string;
|
||||
surface: string;
|
||||
text: string;
|
||||
textSecondary: string;
|
||||
border: string;
|
||||
error: string;
|
||||
success: string;
|
||||
warning: string;
|
||||
};
|
||||
|
||||
type ThemeCssVariables = {
|
||||
'--color-primary': string;
|
||||
'--color-bg': string;
|
||||
'--color-bg-hover': string;
|
||||
'--color-surface': string;
|
||||
'--color-text': string;
|
||||
'--color-text-secondary': string;
|
||||
'--color-border': string;
|
||||
'--color-error': string;
|
||||
'--color-success': string;
|
||||
'--color-warning': string;
|
||||
};
|
||||
|
||||
const getThemeCssVariables = (theme: ThemePalette): ThemeCssVariables => {
|
||||
return {
|
||||
'--color-primary': theme.primary,
|
||||
'--color-bg': theme.background,
|
||||
'--color-bg-hover': theme.backgroundHover,
|
||||
'--color-surface': theme.surface,
|
||||
'--color-text': theme.text,
|
||||
'--color-text-secondary': theme.textSecondary,
|
||||
'--color-border': theme.border,
|
||||
'--color-error': theme.error,
|
||||
'--color-success': theme.success,
|
||||
'--color-warning': theme.warning,
|
||||
};
|
||||
};
|
||||
|
||||
export const mantineCssVariablesResolver: CSSVariablesResolver = () => {
|
||||
return {
|
||||
variables: {},
|
||||
light: getThemeCssVariables(lightTheme),
|
||||
dark: getThemeCssVariables(darkTheme),
|
||||
};
|
||||
};
|
||||
56
src/infra/mantine/config/mantine-theme.config.ts
Normal file
56
src/infra/mantine/config/mantine-theme.config.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type { MantineColorsTuple, MantineThemeOverride } from '@mantine/core';
|
||||
import { createTheme, virtualColor } from '@mantine/core';
|
||||
import { darkTheme } from './dark-theme.config';
|
||||
import { lightTheme } from './light-theme.config';
|
||||
|
||||
const brandLightColors = [
|
||||
'#eff6ff',
|
||||
'#dbeafe',
|
||||
'#bfdbfe',
|
||||
'#93c5fd',
|
||||
'#60a5fa',
|
||||
'#3b82f6',
|
||||
'#2563eb',
|
||||
'#1d4ed8',
|
||||
'#1e40af',
|
||||
'#1e3a8a',
|
||||
] satisfies MantineColorsTuple;
|
||||
|
||||
const brandDarkColors = [
|
||||
'#dbeafe',
|
||||
'#bfdbfe',
|
||||
'#93c5fd',
|
||||
'#60a5fa',
|
||||
'#3b82f6',
|
||||
'#2563eb',
|
||||
'#1d4ed8',
|
||||
'#1e40af',
|
||||
'#1e3a8a',
|
||||
'#172554',
|
||||
] satisfies MantineColorsTuple;
|
||||
|
||||
export const mantineTheme: MantineThemeOverride = createTheme({
|
||||
black: darkTheme.background,
|
||||
white: lightTheme.background,
|
||||
primaryColor: 'brand',
|
||||
primaryShade: { light: 6, dark: 4 },
|
||||
defaultRadius: 'md',
|
||||
autoContrast: true,
|
||||
cursorType: 'pointer',
|
||||
colors: {
|
||||
brand: virtualColor({
|
||||
name: 'brand',
|
||||
light: 'brandLight',
|
||||
dark: 'brandDark',
|
||||
}),
|
||||
brandLight: brandLightColors,
|
||||
brandDark: brandDarkColors,
|
||||
},
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
headings: {
|
||||
fontFamily:
|
||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
|
||||
fontWeight: '700',
|
||||
},
|
||||
});
|
||||
5
src/infra/mantine/index.ts
Normal file
5
src/infra/mantine/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { mantineHtmlProps } from '@mantine/core';
|
||||
|
||||
export { MantineProvider } from './providers/mantine-provider';
|
||||
export { MantineColorSchemeScript } from './ui/mantine-color-scheme-script';
|
||||
export { MantineColorSchemeSwitch } from './ui/mantine-color-scheme-switch';
|
||||
39
src/infra/mantine/providers/mantine-provider.tsx
Normal file
39
src/infra/mantine/providers/mantine-provider.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import {
|
||||
MantineProvider as BaseMantineProvider,
|
||||
localStorageColorSchemeManager,
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
MANTINE_COLOR_SCHEME_STORAGE_KEY,
|
||||
MANTINE_DEFAULT_COLOR_SCHEME,
|
||||
} from '../config/color-scheme.config';
|
||||
import { mantineCssVariablesResolver } from '../config/mantine-css-variables-resolver.config';
|
||||
import { mantineTheme } from '../config/mantine-theme.config';
|
||||
import type { MantineProviderProps } from '../types/mantine-provider.type';
|
||||
|
||||
const colorSchemeManager = localStorageColorSchemeManager({
|
||||
key: MANTINE_COLOR_SCHEME_STORAGE_KEY,
|
||||
});
|
||||
|
||||
/**
|
||||
* Провайдер темы и цветовой схемы Mantine.
|
||||
*
|
||||
* Используется для:
|
||||
* - подключения глобальной темы Mantine
|
||||
* - синхронизации цветовой схемы приложения
|
||||
*/
|
||||
export const MantineProvider = (props: MantineProviderProps) => {
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<BaseMantineProvider
|
||||
colorSchemeManager={colorSchemeManager}
|
||||
cssVariablesResolver={mantineCssVariablesResolver}
|
||||
defaultColorScheme={MANTINE_DEFAULT_COLOR_SCHEME}
|
||||
theme={mantineTheme}
|
||||
>
|
||||
{children}
|
||||
</BaseMantineProvider>
|
||||
);
|
||||
};
|
||||
8
src/infra/mantine/types/mantine-provider.type.ts
Normal file
8
src/infra/mantine/types/mantine-provider.type.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* Пропсы провайдера Mantine UI.
|
||||
*/
|
||||
export type MantineProviderProps = {
|
||||
children: ReactNode;
|
||||
};
|
||||
22
src/infra/mantine/ui/mantine-color-scheme-script.tsx
Normal file
22
src/infra/mantine/ui/mantine-color-scheme-script.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ColorSchemeScript } from '@mantine/core';
|
||||
import type { ReactNode } from 'react';
|
||||
import {
|
||||
MANTINE_COLOR_SCHEME_STORAGE_KEY,
|
||||
MANTINE_DEFAULT_COLOR_SCHEME,
|
||||
} from '../config/color-scheme.config';
|
||||
|
||||
/**
|
||||
* Скрипт инициализации цветовой схемы Mantine до гидрации.
|
||||
*
|
||||
* Используется для:
|
||||
* - применения сохраненной цветовой схемы до загрузки React
|
||||
* - предотвращения мигания темы при первом рендере
|
||||
*/
|
||||
export const MantineColorSchemeScript = (): ReactNode => {
|
||||
return (
|
||||
<ColorSchemeScript
|
||||
defaultColorScheme={MANTINE_DEFAULT_COLOR_SCHEME}
|
||||
localStorageKey={MANTINE_COLOR_SCHEME_STORAGE_KEY}
|
||||
/>
|
||||
);
|
||||
};
|
||||
127
src/infra/mantine/ui/mantine-color-scheme-switch.tsx
Normal file
127
src/infra/mantine/ui/mantine-color-scheme-switch.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
'use client';
|
||||
|
||||
import type { MantineColorScheme } from '@mantine/core';
|
||||
import {
|
||||
Center,
|
||||
SegmentedControl,
|
||||
useMantineColorScheme,
|
||||
VisuallyHidden,
|
||||
} from '@mantine/core';
|
||||
import { useMounted } from '@mantine/hooks';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
const colorSchemeValues = [
|
||||
'auto',
|
||||
'light',
|
||||
'dark',
|
||||
] as const satisfies readonly MantineColorScheme[];
|
||||
|
||||
type ColorSchemeValue = (typeof colorSchemeValues)[number];
|
||||
|
||||
const systemThemeLabel = (
|
||||
<Center component="span" h={24} w={28}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.8"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8A2.5 2.5 0 0 1 17.5 16h-11A2.5 2.5 0 0 1 4 13.5z" />
|
||||
<path d="M9 21h6" />
|
||||
<path d="M12 16v5" />
|
||||
</svg>
|
||||
<VisuallyHidden>Системная тема</VisuallyHidden>
|
||||
</Center>
|
||||
);
|
||||
|
||||
const lightThemeLabel = (
|
||||
<Center component="span" h={24} w={28}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
height="18"
|
||||
stroke="currentColor"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="1.8"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M12 2v2" />
|
||||
<path d="M12 20v2" />
|
||||
<path d="m4.93 4.93 1.41 1.41" />
|
||||
<path d="m17.66 17.66 1.41 1.41" />
|
||||
<path d="M2 12h2" />
|
||||
<path d="M20 12h2" />
|
||||
<path d="m6.34 17.66-1.41 1.41" />
|
||||
<path d="m19.07 4.93-1.41 1.41" />
|
||||
</svg>
|
||||
<VisuallyHidden>Светлая тема</VisuallyHidden>
|
||||
</Center>
|
||||
);
|
||||
|
||||
const darkThemeLabel = (
|
||||
<Center component="span" h={24} w={28}>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
height="18"
|
||||
viewBox="0 0 24 24"
|
||||
width="18"
|
||||
>
|
||||
<path d="M21 14.31A8.5 8.5 0 0 1 9.69 3a7 7 0 1 0 11.3 11.3Z" />
|
||||
</svg>
|
||||
<VisuallyHidden>Темная тема</VisuallyHidden>
|
||||
</Center>
|
||||
);
|
||||
|
||||
const colorSchemeItems: { label: ReactNode; value: ColorSchemeValue }[] = [
|
||||
{ label: systemThemeLabel, value: 'auto' },
|
||||
{ label: lightThemeLabel, value: 'light' },
|
||||
{ label: darkThemeLabel, value: 'dark' },
|
||||
];
|
||||
|
||||
const isColorSchemeValue = (value: string): value is ColorSchemeValue => {
|
||||
return colorSchemeValues.some((item) => item === value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Переключатель цветовой схемы Mantine.
|
||||
*
|
||||
* Используется для:
|
||||
* - выбора системной, светлой или темной темы
|
||||
* - управления пользовательской цветовой схемой приложения
|
||||
*/
|
||||
export const MantineColorSchemeSwitch = (): ReactNode => {
|
||||
const mounted = useMounted();
|
||||
const { colorScheme, setColorScheme } = useMantineColorScheme();
|
||||
const value =
|
||||
mounted && isColorSchemeValue(colorScheme) ? colorScheme : 'auto';
|
||||
|
||||
const handleChange = (nextValue: string): void => {
|
||||
if (!isColorSchemeValue(nextValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setColorScheme(nextValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<SegmentedControl
|
||||
aria-label="Цветовая схема"
|
||||
color="brand"
|
||||
data={colorSchemeItems}
|
||||
onChange={handleChange}
|
||||
radius="xl"
|
||||
size="sm"
|
||||
value={value}
|
||||
withItemsBorders={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export { MantineProvider } from './mantine-provider';
|
||||
@@ -1,11 +0,0 @@
|
||||
'use client';
|
||||
|
||||
import { MantineProvider as BaseMantineProvider } from '@mantine/core';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
|
||||
/**
|
||||
* Провайдер Mantine UI.
|
||||
*/
|
||||
export const MantineProvider = ({ children }: PropsWithChildren) => {
|
||||
return <BaseMantineProvider>{children}</BaseMantineProvider>;
|
||||
};
|
||||
@@ -1,8 +1,13 @@
|
||||
import { Container, Image, Stack, Text, Title } from '@mantine/core';
|
||||
import { MantineColorSchemeSwitch } from 'infra/mantine';
|
||||
import styles from './styles/home.module.css';
|
||||
|
||||
/**
|
||||
* Главный экран приложения.
|
||||
* Главный экран стартовой страницы приложения.
|
||||
*
|
||||
* Используется для:
|
||||
* - отображения приветственного содержимого шаблона
|
||||
* - предоставления быстрого переключения цветовой схемы
|
||||
*/
|
||||
export const HomeScreen = () => {
|
||||
return (
|
||||
@@ -18,6 +23,7 @@ export const HomeScreen = () => {
|
||||
/>
|
||||
<Title order={1}>Добро пожаловать</Title>
|
||||
<Text c="dimmed">Шаблон приложения на Next.js и TypeScript.</Text>
|
||||
<MantineColorSchemeSwitch />
|
||||
</Stack>
|
||||
</Container>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"paths": {
|
||||
"app/*": ["./src/app/*"],
|
||||
"business/*": ["./src/business/*"],
|
||||
"infrastructure/*": ["./src/infrastructure/*"],
|
||||
"infra/*": ["./src/infra/*"],
|
||||
"layouts/*": ["./src/layouts/*"],
|
||||
"screens/*": ["./src/screens/*"],
|
||||
"ui/*": ["./src/ui/*"],
|
||||
|
||||
Reference in New Issue
Block a user