develop #3
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
tailwind.config.ts
|
@ -7,10 +7,15 @@
|
|||||||
"rules": {
|
"rules": {
|
||||||
"react-hooks/rules-of-hooks": "off"
|
"react-hooks/rules-of-hooks": "off"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
"src/app/**/*.stories.tsx"
|
||||||
],
|
],
|
||||||
"plugins": [
|
"rules": {
|
||||||
"prettier"
|
"react/jsx-props-no-spreading": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"settings": {
|
"settings": {
|
||||||
"react": {
|
"react": {
|
||||||
@ -35,6 +40,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"plugins": [
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"extends": [
|
||||||
|
"airbnb",
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"next/typescript",
|
||||||
|
"prettier",
|
||||||
|
"plugin:storybook/recommended"
|
||||||
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-use-before-define": "off",
|
"no-use-before-define": "off",
|
||||||
"class-methods-use-this": "off",
|
"class-methods-use-this": "off",
|
||||||
@ -43,13 +58,24 @@
|
|||||||
"no-promise-executor-return": "off",
|
"no-promise-executor-return": "off",
|
||||||
"@typescript-eslint/no-shadow": "off",
|
"@typescript-eslint/no-shadow": "off",
|
||||||
"react/require-default-props": "off",
|
"react/require-default-props": "off",
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"pathGroups": [
|
||||||
|
{
|
||||||
|
"pattern": "@/**",
|
||||||
|
"group": "external"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-shadow": "off",
|
"no-shadow": "off",
|
||||||
"prettier/prettier": [
|
"prettier/prettier": [
|
||||||
"warn",
|
"warn",
|
||||||
{
|
{
|
||||||
"printWidth": 80,
|
"printWidth": 80,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"endOfLine":"auto",
|
"endOfLine": "auto",
|
||||||
"useTabs": false,
|
"useTabs": false,
|
||||||
"semi": true,
|
"semi": true,
|
||||||
"singleQuote": false,
|
"singleQuote": false,
|
||||||
@ -85,11 +111,5 @@
|
|||||||
"devDependencies": true
|
"devDependencies": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
"extends": [
|
|
||||||
"airbnb",
|
|
||||||
"next/core-web-vitals",
|
|
||||||
"next/typescript",
|
|
||||||
"prettier"
|
|
||||||
]
|
|
||||||
}
|
}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -38,3 +38,5 @@ yarn-error.log*
|
|||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
|
*storybook.log
|
||||||
|
18
.storybook/main.ts
Normal file
18
.storybook/main.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import type { StorybookConfig } from "@storybook/nextjs";
|
||||||
|
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||||
|
addons: [
|
||||||
|
"@storybook/addon-onboarding",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@chromatic-com/storybook",
|
||||||
|
"@storybook/addon-interactions",
|
||||||
|
"storybook-dark-mode"
|
||||||
|
],
|
||||||
|
framework: {
|
||||||
|
name: "@storybook/nextjs",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
staticDirs: ["../public"],
|
||||||
|
};
|
||||||
|
export default config;
|
134
.storybook/preview.tsx
Normal file
134
.storybook/preview.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import React, { useRef } from "react";
|
||||||
|
import { themes } from '@storybook/theming';
|
||||||
|
import { ThemeProvider } from "../src/app/[lang]/dashboard/components/client/theme-provider/theme-provider";
|
||||||
|
import { DARK_MODE_EVENT_NAME, UPDATE_DARK_MODE_EVENT_NAME } from 'storybook-dark-mode';
|
||||||
|
import { initI18next, LANGS } from "../src/bootstrap/i18n/i18n"
|
||||||
|
import { addons } from '@storybook/preview-api';
|
||||||
|
import { i18n } from "i18next";
|
||||||
|
import { I18nextProvider } from "react-i18next";
|
||||||
|
const channel = addons.getChannel();
|
||||||
|
import "../src/app/globals.css"
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* This function will expand the object with nested properties
|
||||||
|
* @param obj refrence Object to that
|
||||||
|
* @param leftKeys keys to be nested object keys
|
||||||
|
* @param value value to be nested
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const recursiveNestedProps = (
|
||||||
|
obj: Record<string, unknown>,
|
||||||
|
leftKeys: string[],
|
||||||
|
value: unknown,
|
||||||
|
): Record<string, unknown> => {
|
||||||
|
if (leftKeys.length <= 0) return obj;
|
||||||
|
if (leftKeys.length === 1) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
obj[leftKeys[0]] = value;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
const key = leftKeys.shift();
|
||||||
|
if (!key) return obj;
|
||||||
|
|
||||||
|
if (!obj[key]) {
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
obj[key] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return recursiveNestedProps(
|
||||||
|
obj[key] as Record<string, unknown>,
|
||||||
|
leftKeys,
|
||||||
|
value,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const preview = {
|
||||||
|
decorators: [
|
||||||
|
(Story, data) => {
|
||||||
|
const [isDark, setDark] = React.useState(true);
|
||||||
|
const [i18n, setI18n] = React.useState<i18n>()
|
||||||
|
const parsedProps = {} as Record<string, unknown>;
|
||||||
|
const { locale } = data.globals
|
||||||
|
const props = data.allArgs;
|
||||||
|
Object.entries(props).forEach((prop) => {
|
||||||
|
const [key, value] = prop;
|
||||||
|
if (!key.includes("vm")) {
|
||||||
|
parsedProps[key] = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const splitedKey = key.split(".");
|
||||||
|
|
||||||
|
recursiveNestedProps(parsedProps, splitedKey, value);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
channel.on(DARK_MODE_EVENT_NAME, setDark);
|
||||||
|
return () => channel.removeListener(DARK_MODE_EVENT_NAME, setDark);
|
||||||
|
}, [channel, setDark]);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
setI18n((await initI18next({ lng: locale })).i18n);
|
||||||
|
})()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
i18n?.changeLanguage(locale);
|
||||||
|
}, [locale]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
forcedTheme={isDark ? "dark" : "light"}
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
{
|
||||||
|
i18n && (
|
||||||
|
<I18nextProvider
|
||||||
|
i18n={i18n}
|
||||||
|
>
|
||||||
|
<Story parsedProps={parsedProps} />
|
||||||
|
</I18nextProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
],
|
||||||
|
darkMode: {
|
||||||
|
// Override the default dark theme
|
||||||
|
dark: { ...themes.dark, appBg: 'black' },
|
||||||
|
// Override the default light theme
|
||||||
|
classTarget: 'html',
|
||||||
|
light: { ...themes.normal, appBg: 'red' },
|
||||||
|
},
|
||||||
|
parameters: {
|
||||||
|
nextjs: {
|
||||||
|
appDirectory: true,
|
||||||
|
},
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/i,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globalTypes: {
|
||||||
|
locale: {
|
||||||
|
name: 'Locale',
|
||||||
|
description: 'Internationalization locale',
|
||||||
|
toolbar: {
|
||||||
|
icon: 'globe',
|
||||||
|
items: [
|
||||||
|
{ value: LANGS.EN, title: 'English' },
|
||||||
|
{ value: LANGS.RU, title: 'Russian' },
|
||||||
|
],
|
||||||
|
showName: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
@ -11,7 +11,6 @@ services:
|
|||||||
POSTGRES_USER: admin
|
POSTGRES_USER: admin
|
||||||
POSTGRES_DB: nextbp
|
POSTGRES_DB: nextbp
|
||||||
|
|
||||||
|
|
||||||
app:
|
app:
|
||||||
build: .
|
build: .
|
||||||
ports:
|
ports:
|
||||||
|
16
package.json
16
package.json
@ -8,7 +8,9 @@
|
|||||||
"start": "next start --port 4000",
|
"start": "next start --port 4000",
|
||||||
"lint": "next lint --fix",
|
"lint": "next lint --fix",
|
||||||
"test": "vitest",
|
"test": "vitest",
|
||||||
"seed": "node -r dotenv/config ./src/bootstrap/boundaries/db/seed.js"
|
"seed": "node -r dotenv/config ./src/bootstrap/boundaries/db/seed.js",
|
||||||
|
"storybook": "storybook dev -p 6006 --no-open",
|
||||||
|
"build-storybook": "storybook build"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/react": "^2.1.5",
|
"@heroicons/react": "^2.1.5",
|
||||||
@ -24,6 +26,7 @@
|
|||||||
"lucide-react": "^0.454.0",
|
"lucide-react": "^0.454.0",
|
||||||
"next": "15.0.2",
|
"next": "15.0.2",
|
||||||
"next-i18n-router": "^5.5.1",
|
"next-i18n-router": "^5.5.1",
|
||||||
|
"next-themes": "^0.4.3",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"react": "19.0.0-rc-69d4b800-20241021",
|
"react": "19.0.0-rc-69d4b800-20241021",
|
||||||
"react-cookie": "^7.2.2",
|
"react-cookie": "^7.2.2",
|
||||||
@ -37,7 +40,15 @@
|
|||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@chromatic-com/storybook": "^3.2.2",
|
||||||
"@faker-js/faker": "^9.1.0",
|
"@faker-js/faker": "^9.1.0",
|
||||||
|
"@storybook/addon-essentials": "^8.4.5",
|
||||||
|
"@storybook/addon-interactions": "^8.4.5",
|
||||||
|
"@storybook/addon-onboarding": "^8.4.5",
|
||||||
|
"@storybook/blocks": "^8.4.5",
|
||||||
|
"@storybook/nextjs": "^8.4.5",
|
||||||
|
"@storybook/react": "^8.4.5",
|
||||||
|
"@storybook/test": "^8.4.5",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/react": "^16.0.1",
|
"@testing-library/react": "^16.0.1",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
@ -53,10 +64,13 @@
|
|||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-import-resolver-typescript": "^3.6.3",
|
"eslint-import-resolver-typescript": "^3.6.3",
|
||||||
"eslint-plugin-prettier": "^5.2.1",
|
"eslint-plugin-prettier": "^5.2.1",
|
||||||
|
"eslint-plugin-storybook": "^0.11.1",
|
||||||
"jsdom": "^25.0.1",
|
"jsdom": "^25.0.1",
|
||||||
"moq.ts": "^10.0.8",
|
"moq.ts": "^10.0.8",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
"storybook": "^8.4.5",
|
||||||
|
"storybook-dark-mode": "^4.0.2",
|
||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
"vitest": "^2.1.4"
|
"vitest": "^2.1.4"
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof NextThemesProvider>) {
|
||||||
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
|
}
|
@ -6,11 +6,11 @@ import CardWrapper from "@/app/[lang]/dashboard/components/server/cards/cards";
|
|||||||
import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices/latest-invoices";
|
import LatestInvoices from "@/app/[lang]/dashboard/components/server/latest-invoices/latest-invoices";
|
||||||
import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart/revenue-chart";
|
import RevenueChart from "@/app/[lang]/dashboard/components/server/revenue-chart/revenue-chart";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
import { getServerTranslation } from "@/bootstrap/i18n/i18n";
|
import { getServerTranslation, LANGS } from "@/bootstrap/i18n/i18n";
|
||||||
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
|
import langKey from "@/bootstrap/i18n/dictionaries/lang-key";
|
||||||
|
|
||||||
export default async function Dashboard(props: {
|
export default async function Dashboard(props: {
|
||||||
params: Promise<{ lang: string }>;
|
params: Promise<{ lang: LANGS }>;
|
||||||
}) {
|
}) {
|
||||||
const { params } = props;
|
const { params } = props;
|
||||||
const { lang } = await params;
|
const { lang } = await params;
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { initI18next } from "@/bootstrap/i18n/i18n";
|
import { ThemeProvider } from "@/app/[lang]/dashboard/components/client/theme-provider/theme-provider";
|
||||||
|
import { initI18next, LANGS } from "@/bootstrap/i18n/i18n";
|
||||||
import TranslationsProvider from "@/bootstrap/i18n/i18n-provider";
|
import TranslationsProvider from "@/bootstrap/i18n/i18n-provider";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
@ -15,19 +16,26 @@ const geistMono = localFont({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default async function layout(
|
export default async function layout(
|
||||||
props: PropsWithChildren & { params: Promise<{ lang: string }> },
|
props: PropsWithChildren & { params: Promise<{ lang: LANGS }> },
|
||||||
) {
|
) {
|
||||||
const { params, children } = props;
|
const { params, children } = props;
|
||||||
const { lang } = await params;
|
const { lang } = await params;
|
||||||
const { resources } = await initI18next({ lng: lang });
|
const { resources } = await initI18next({ lng: lang });
|
||||||
return (
|
return (
|
||||||
<html lang={lang}>
|
<html lang={lang} suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="light"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<TranslationsProvider lng={lang} resources={resources}>
|
<TranslationsProvider lng={lang} resources={resources}>
|
||||||
{children}
|
{children}
|
||||||
</TranslationsProvider>
|
</TranslationsProvider>
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,6 @@ import { cn } from "@/bootstrap/helpers/lib/ui-utils";
|
|||||||
export default class Button extends BaseView<ButtonVm> {
|
export default class Button extends BaseView<ButtonVm> {
|
||||||
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
protected Build(props: BuildProps<ButtonVm>): ReactNode {
|
||||||
const { vm } = props;
|
const { vm } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ButtonUi disabled={vm.props.isDisable} onClick={vm.onClick}>
|
<ButtonUi disabled={vm.props.isDisable} onClick={vm.onClick}>
|
||||||
{vm.props.title}
|
{vm.props.title}
|
||||||
|
65
src/app/components/button/stories/Button.stories.tsx
Normal file
65
src/app/components/button/stories/Button.stories.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import CreateRandomInvoiceButtonVM from "@/app/[lang]/dashboard/vm/create-random-invoice-button-vm";
|
||||||
|
import Button from "@/app/components/button/button";
|
||||||
|
import { DiContext, useDI } from "@/bootstrap/di/di-context";
|
||||||
|
import mockedModuleDi from "@/bootstrap/di/mocked-module-di";
|
||||||
|
import Story from "@/bootstrap/helpers/view/storybook-base-template-type";
|
||||||
|
import getArgVM from "@/bootstrap/helpers/view/storybook-with-arg-vm";
|
||||||
|
import createInvoiceUsecase from "@/feature/core/invoice/domain/usecase/create-invoice-usecase";
|
||||||
|
import type { Meta } from "@storybook/react";
|
||||||
|
import { useRef } from "react";
|
||||||
|
|
||||||
|
const meta: Meta = {
|
||||||
|
title: "general/Button",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
export const Primary: Story = {
|
||||||
|
argTypes: {
|
||||||
|
"vm.props.isDisable": {
|
||||||
|
control: "boolean",
|
||||||
|
},
|
||||||
|
"vm.props.title": {
|
||||||
|
control: "text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
"vm.props.title": "Button",
|
||||||
|
"vm.props.isDisable": false,
|
||||||
|
},
|
||||||
|
render: (_props, globalData) => {
|
||||||
|
const vm = getArgVM(globalData.parsedProps.vm); // You can use parsed props to access your vm properties.
|
||||||
|
return <Button vm={vm} memoizedByVM={false} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithVM: Story = {
|
||||||
|
decorators: [
|
||||||
|
(Story) => {
|
||||||
|
const di = mockedModuleDi([
|
||||||
|
{
|
||||||
|
token: CreateRandomInvoiceButtonVM,
|
||||||
|
provider: CreateRandomInvoiceButtonVM,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
token: createInvoiceUsecase.name,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-console
|
||||||
|
provider: (args: any) => console.log("clicked", args),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
return <Story di={di} />;
|
||||||
|
},
|
||||||
|
],
|
||||||
|
render: (_, globalProps) => {
|
||||||
|
function Child() {
|
||||||
|
const di = useDI();
|
||||||
|
const vm = useRef(di.resolve(CreateRandomInvoiceButtonVM));
|
||||||
|
return <Button vm={vm.current} memoizedByVM={false} />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<DiContext.Provider value={globalProps.di}>
|
||||||
|
<Child />
|
||||||
|
</DiContext.Provider>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
@ -34,7 +34,7 @@ body {
|
|||||||
--chart-5: 27 87% 67%;
|
--chart-5: 27 87% 67%;
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
.dark {
|
:root[class~="dark"] {
|
||||||
--background: 240 10% 3.9%;
|
--background: 240 10% 3.9%;
|
||||||
--foreground: 0 0% 98%;
|
--foreground: 0 0% 98%;
|
||||||
--card: 240 10% 3.9%;
|
--card: 240 10% 3.9%;
|
||||||
|
33
src/bootstrap/di/mocked-module-di.ts
Normal file
33
src/bootstrap/di/mocked-module-di.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { container, DependencyContainer } from "tsyringe";
|
||||||
|
import { InjectionToken } from "tsyringe/dist/typings/providers";
|
||||||
|
import constructor from "tsyringe/dist/typings/types/constructor";
|
||||||
|
import { isClass } from "../helpers/global-helpers";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides mocked di for test cases and using instead of real di
|
||||||
|
* @param providers List of providers
|
||||||
|
* @returns Mocked di with registered providers
|
||||||
|
*/
|
||||||
|
const mockedModuleDi = (
|
||||||
|
providers: {
|
||||||
|
token: InjectionToken<unknown>;
|
||||||
|
provider: unknown | constructor<unknown>;
|
||||||
|
}[],
|
||||||
|
): DependencyContainer => {
|
||||||
|
const di = container.createChildContainer();
|
||||||
|
|
||||||
|
providers.forEach((provider) => {
|
||||||
|
if (isClass(provider.provider)) {
|
||||||
|
di.register(provider.token, provider.provider);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
di.register(provider.token, {
|
||||||
|
useValue: provider.provider,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return di;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default mockedModuleDi;
|
@ -1 +1,8 @@
|
|||||||
|
import { constructor } from "tsyringe/dist/typings/types";
|
||||||
|
|
||||||
export const isServer = typeof window === "undefined";
|
export const isServer = typeof window === "undefined";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function isClass(fn: any): fn is constructor<unknown> {
|
||||||
|
return typeof fn === "function" && /^(class|function [A-Z])/.test(fn);
|
||||||
|
}
|
||||||
|
@ -11,11 +11,11 @@ export default function useThrottle<T extends () => unknown>(
|
|||||||
callback: T,
|
callback: T,
|
||||||
time: number = 2000,
|
time: number = 2000,
|
||||||
) {
|
) {
|
||||||
const lastRun = useRef(Date.now());
|
const lastRun = useRef<number>();
|
||||||
|
|
||||||
// eslint-disable-next-line func-names
|
// eslint-disable-next-line func-names
|
||||||
return function () {
|
return function () {
|
||||||
if (Date.now() - lastRun.current <= time) return;
|
if (lastRun.current && Date.now() - lastRun.current <= time) return;
|
||||||
lastRun.current = Date.now();
|
lastRun.current = Date.now();
|
||||||
callback();
|
callback();
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import { Meta, StoryObj } from "@storybook/react";
|
||||||
|
|
||||||
|
type Story = StoryObj<Meta>;
|
||||||
|
|
||||||
|
export default Story;
|
14
src/bootstrap/helpers/view/storybook-with-arg-vm.ts
Normal file
14
src/bootstrap/helpers/view/storybook-with-arg-vm.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import IBaseVM from "@/bootstrap/helpers/vm/i-base-vm";
|
||||||
|
|
||||||
|
const getArgVM = <IVM>(vmObj: IVM) => {
|
||||||
|
class VM implements IBaseVM<IVM> {
|
||||||
|
useVM(): IVM {
|
||||||
|
return {
|
||||||
|
...vmObj,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new VM();
|
||||||
|
};
|
||||||
|
|
||||||
|
export default getArgVM;
|
@ -1,18 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { I18nextProvider } from "react-i18next";
|
import { I18nextProvider } from "react-i18next";
|
||||||
import { initI18next } from "@/bootstrap/i18n/i18n";
|
import { i18nInstance, initI18next, LANGS } from "@/bootstrap/i18n/i18n";
|
||||||
import { createInstance, Resource } from "i18next";
|
import { Resource } from "i18next";
|
||||||
import { PropsWithChildren } from "react";
|
import { PropsWithChildren } from "react";
|
||||||
|
|
||||||
export default function TranslationsProvider({
|
export default function TranslationsProvider({
|
||||||
children,
|
children,
|
||||||
lng,
|
lng,
|
||||||
resources,
|
resources,
|
||||||
}: PropsWithChildren & { lng: string; resources: Resource }) {
|
}: PropsWithChildren & { lng: LANGS; resources: Resource }) {
|
||||||
const i18n = createInstance();
|
if (!resources) return children;
|
||||||
|
initI18next({ lng, resources });
|
||||||
|
|
||||||
initI18next({ lng, i18n, resources });
|
return <I18nextProvider i18n={i18nInstance}>{children}</I18nextProvider>;
|
||||||
|
|
||||||
return <I18nextProvider i18n={i18n}>{children}</I18nextProvider>;
|
|
||||||
}
|
}
|
||||||
|
@ -1,23 +1,26 @@
|
|||||||
import { getOptions, languages } from "@/bootstrap/i18n/settings";
|
import { getOptions, languages } from "@/bootstrap/i18n/settings";
|
||||||
import { createInstance, i18n, Resource } from "i18next";
|
import { createInstance, Resource } from "i18next";
|
||||||
import resourcesToBackend from "i18next-resources-to-backend";
|
import resourcesToBackend from "i18next-resources-to-backend";
|
||||||
import { initReactI18next } from "react-i18next/initReactI18next";
|
import { initReactI18next } from "react-i18next/initReactI18next";
|
||||||
|
|
||||||
const initI18nextInstance = createInstance();
|
export const i18nInstance = createInstance();
|
||||||
|
|
||||||
|
export enum LANGS {
|
||||||
|
EN = "en",
|
||||||
|
RU = "ru",
|
||||||
|
}
|
||||||
|
|
||||||
export const initI18next = async (params: {
|
export const initI18next = async (params: {
|
||||||
lng: string;
|
lng: LANGS;
|
||||||
i18n?: i18n;
|
|
||||||
resources?: Resource;
|
resources?: Resource;
|
||||||
ns?: string;
|
ns?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { lng, i18n, ns, resources } = params;
|
const { lng, ns, resources } = params;
|
||||||
const i18nInstance = i18n || initI18nextInstance;
|
|
||||||
await i18nInstance
|
await i18nInstance
|
||||||
.use(initReactI18next)
|
.use(initReactI18next)
|
||||||
.use(
|
.use(
|
||||||
resourcesToBackend(
|
resourcesToBackend(
|
||||||
(language: string) => import(`./dictionaries/${language}.ts`),
|
(language: LANGS) => import(`./dictionaries/${language}.ts`),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.init({
|
.init({
|
||||||
@ -36,18 +39,17 @@ export const initI18next = async (params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function getServerTranslation(
|
export async function getServerTranslation(
|
||||||
lng: string,
|
lng: LANGS,
|
||||||
ns?: string,
|
ns?: string,
|
||||||
options: { keyPrefix?: string } = {},
|
options: { keyPrefix?: string } = {},
|
||||||
) {
|
) {
|
||||||
const i18nextInstance = (await initI18next({ lng, ns })).i18n;
|
await initI18next({ lng });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
t: i18nextInstance.getFixedT(
|
t: i18nInstance.getFixedT(
|
||||||
lng,
|
lng,
|
||||||
Array.isArray(ns) ? ns[0] : ns,
|
Array.isArray(ns) ? ns[0] : ns,
|
||||||
options?.keyPrefix,
|
options?.keyPrefix,
|
||||||
),
|
),
|
||||||
i18n: i18nextInstance,
|
i18n: i18nInstance,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -11,53 +11,53 @@ const config: Config = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
background: 'hsl(var(--background))',
|
background: "hsl(var(--background))",
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: "hsl(var(--foreground))",
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: "hsl(var(--card))",
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
foreground: "hsl(var(--card-foreground))",
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: "hsl(var(--popover))",
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: "hsl(var(--primary))",
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: "hsl(var(--muted))",
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: "hsl(var(--accent))",
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
},
|
},
|
||||||
border: 'hsl(var(--border))',
|
border: "hsl(var(--border))",
|
||||||
input: 'hsl(var(--input))',
|
input: "hsl(var(--input))",
|
||||||
ring: 'hsl(var(--ring))',
|
ring: "hsl(var(--ring))",
|
||||||
chart: {
|
chart: {
|
||||||
'1': 'hsl(var(--chart-1))',
|
"1": "hsl(var(--chart-1))",
|
||||||
'2': 'hsl(var(--chart-2))',
|
"2": "hsl(var(--chart-2))",
|
||||||
'3': 'hsl(var(--chart-3))',
|
"3": "hsl(var(--chart-3))",
|
||||||
'4': 'hsl(var(--chart-4))',
|
"4": "hsl(var(--chart-4))",
|
||||||
'5': 'hsl(var(--chart-5))'
|
"5": "hsl(var(--chart-5))",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: "var(--radius)",
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: "calc(var(--radius) - 2px)",
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: "calc(var(--radius) - 4px)",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user