├── .docker ├── Dockerfile └── docker-entrypoint.sh ├── .env.example ├── .gitignore ├── .husky └── _ │ ├── pre-commit │ └── prepare-commit-msg ├── .jscpd.json ├── .node-version ├── .npmrc ├── .prettierignore ├── .prettierrc.yml ├── .storybook ├── main.ts └── preview.tsx ├── README.md ├── app ├── (public) │ ├── (home) │ │ ├── _components │ │ │ └── hero.tsx │ │ ├── error.tsx │ │ └── page.tsx │ └── ui-demo │ │ └── page.tsx ├── _shared │ └── utilities │ │ ├── error-boundary │ │ ├── error-boundary.stories.tsx │ │ ├── error-boundary.test.tsx │ │ ├── error-boundary.tsx │ │ └── index.ts │ │ ├── providers │ │ └── react-query.tsx │ │ └── responsive.tsx ├── api │ ├── health │ │ └── route.ts │ ├── metrics │ │ └── route.ts │ └── ready │ │ └── route.ts ├── favicon.ico ├── global-error.tsx ├── globals.css ├── layout.tsx ├── manifest.ts ├── not-found.tsx ├── robots.ts └── sitemap.ts ├── bunfig.toml ├── eslint.config.mjs ├── instrumentation.ts ├── knip.jsonc ├── lefthook.yml ├── lint-staged.config.mjs ├── middleware.ts ├── next-env.d.ts ├── next.config.ts ├── package-lock.json ├── package.json ├── packages ├── api │ ├── .gitignore │ ├── axios-client.ts │ ├── codegen │ │ └── .gitkeep │ ├── kubb.config.ts │ ├── ky-client.ts │ └── package.json ├── core │ ├── button │ │ ├── button.stories.tsx │ │ ├── button.test.tsx │ │ ├── button.tsx │ │ └── index.ts │ ├── checkbox │ │ ├── checkbox.stories.tsx │ │ ├── checkbox.test.tsx │ │ ├── checkbox.tsx │ │ └── index.ts │ ├── cn.ts │ ├── components.json │ ├── dialog │ │ ├── dialog.stories.tsx │ │ ├── dialog.tsx │ │ └── index.ts │ ├── form │ │ ├── form.tsx │ │ └── index.ts │ ├── input │ │ ├── index.ts │ │ ├── input.stories.tsx │ │ ├── input.test.tsx │ │ └── input.tsx │ ├── label │ │ ├── index.ts │ │ ├── label.stories.tsx │ │ ├── label.test.tsx │ │ └── label.tsx │ ├── package.json │ ├── radio-group │ │ ├── index.ts │ │ ├── radio-group.stories.tsx │ │ ├── radio-group.test.tsx │ │ └── radio-group.tsx │ ├── select │ │ ├── index.ts │ │ ├── select.stories.tsx │ │ └── select.tsx │ ├── skeleton │ │ ├── index.ts │ │ ├── skeleton.stories.tsx │ │ ├── skeleton.test.tsx │ │ └── skeleton.tsx │ ├── sonner │ │ └── index.tsx │ ├── switch │ │ ├── index.ts │ │ ├── switch.stories.tsx │ │ ├── switch.test.tsx │ │ └── switch.tsx │ ├── tabs │ │ ├── index.ts │ │ ├── tabs.stories.tsx │ │ ├── tabs.test.tsx │ │ └── tabs.tsx │ ├── textarea │ │ ├── index.ts │ │ ├── textarea.stories.tsx │ │ ├── textarea.test.tsx │ │ └── textarea.tsx │ ├── tooltip │ │ ├── index.ts │ │ ├── tooltip.stories.tsx │ │ ├── tooltip.test.tsx │ │ └── tooltip.tsx │ ├── tsconfig.json │ └── typography │ │ ├── index.ts │ │ ├── typography.stories.tsx │ │ ├── typography.test.tsx │ │ └── typography.tsx ├── design-tokens │ ├── json │ │ └── .gitkeep │ ├── package.json │ └── tokens.config.json ├── logger │ ├── index.ts │ └── package.json ├── metrics │ ├── index.ts │ └── package.json ├── routes │ ├── README.md │ ├── declarative-routing.config.json │ ├── package.json │ └── src │ │ ├── hooks.ts │ │ ├── index.ts │ │ ├── makeRoute.tsx │ │ └── utils.ts └── ts-config │ ├── base.json │ └── package.json ├── playwright.config.ts ├── postcss.config.mjs ├── public └── images │ └── svg │ └── logo.svg ├── sentry.client.config.js ├── src ├── env │ ├── client.ts │ └── server.ts ├── fonts │ ├── geist.ts │ └── source │ │ └── geist.woff2 ├── hooks │ └── use-responsive.ts ├── tests │ └── e2e │ │ └── example.spec.ts ├── types │ └── global.d.ts └── utils │ ├── cn.ts │ └── get-url.ts ├── tsconfig.json └── vitest.config.ts /.docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.16 AS deps-front 2 | 3 | ENV LOCALTIME Europe/Moscow 4 | RUN ln -snf /usr/share/zoneinfo/$LOCALTIME /etc/localtime && echo $LOCALTIME > /etc/timezone 5 | 6 | RUN set -ex && \ 7 | mkdir -p /app && \ 8 | chown -R node:node /app && \ 9 | chown node:node /app 10 | 11 | WORKDIR /app 12 | 13 | USER node 14 | 15 | COPY --chown=node:node app/. /app/ 16 | 17 | RUN npm ci 18 | 19 | FROM node:22.14 AS build-front 20 | 21 | ENV LOCALTIME Europe/Moscow 22 | RUN ln -snf /usr/share/zoneinfo/$LOCALTIME /etc/localtime && echo $LOCALTIME > /etc/timezone 23 | 24 | # LOCAL | WORK | RC | PROD 25 | ARG NEXT_PUBLIC_APP_ENV 26 | # публичный урл front приложения 27 | ARG NEXT_PUBLIC_FRONT_URL 28 | # публичный урл back приложения (опционален) 29 | ARG NEXT_PUBLIC_BACK_URL 30 | # DSN для доступа к Sentry 31 | ARG NEXT_PUBLIC_SENTRY_DSN 32 | 33 | RUN set -ex && \ 34 | mkdir -p /app && \ 35 | chown -R node:node /app && \ 36 | chown node:node /app 37 | 38 | WORKDIR /app 39 | 40 | COPY .docker/docker-entrypoint.sh /entrypoint.sh 41 | 42 | COPY --from=deps-front --chown=node:node /app/. /app/ 43 | 44 | USER node 45 | 46 | RUN npm run build 47 | 48 | ENTRYPOINT ["/entrypoint.sh"] 49 | -------------------------------------------------------------------------------- /.docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | if [[ ! -z "$1" ]]; then 5 | echo ${*} 6 | exec ${*} 7 | else 8 | exec node -v 9 | fi 10 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # server 2 | APP_NAME=nextjs_starter 3 | APP_ENV=LOCAL 4 | FRONT_HOST=front 5 | FRONT_PORT=3000 6 | BACK_INTERNAL_URL=http://localhost:3000 7 | HTTP_AUTH_LOGIN=demo 8 | HTTP_AUTH_PASS=demo 9 | CACHE_PUBLIC_MAX_AGE=3600 10 | SENTRY_DSN=https://sentry.w6p.ru/ 11 | SENTRY_AUTH_TOKEN=secret 12 | SENTRY_ORG=webpractik 13 | SENTRY_URL=https://sentry.w6p.ru 14 | CI=false 15 | 16 | # client 17 | NEXT_PUBLIC_APP_ENV=LOCAL 18 | NEXT_PUBLIC_FRONT_URL=http://localhost:3000 19 | NEXT_PUBLIC_BFF_PATH=/bff-api 20 | NEXT_PUBLIC_BACK_URL=http://localhost:8080 21 | NEXT_PUBLIC_SENTRY_DSN=https://sentry.w6p.ru/ 22 | 23 | 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # custom 4 | .idea 5 | .vscode 6 | .turbo 7 | dist 8 | .env 9 | .eslintcache 10 | .million 11 | storybook-static 12 | coverage-ts 13 | swagger.json 14 | swagger.yaml 15 | openapi.json 16 | openapi.yaml 17 | docs.html 18 | dependency-graph.svg 19 | packages/*/node_modules 20 | storybook.log 21 | report 22 | 23 | # dependencies 24 | /node_modules 25 | /.pnp 26 | .pnp.js 27 | 28 | # testing 29 | /coverage 30 | 31 | # next.js 32 | /.next/ 33 | /out/ 34 | 35 | # production 36 | /build 37 | 38 | # misc 39 | .DS_Store 40 | *.pem 41 | 42 | # debug 43 | npm-debug.log* 44 | yarn-debug.log* 45 | yarn-error.log* 46 | 47 | # local env files 48 | .env*.local 49 | 50 | # vercel 51 | .vercel 52 | 53 | # typescript 54 | *.tsbuildinfo 55 | next-env.d.ts 56 | /test-results/ 57 | /playwright-report/ 58 | /blob-report/ 59 | /playwright/.cache/ 60 | 61 | *storybook.log -------------------------------------------------------------------------------- /.husky/_/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif go tool lefthook -h >/dev/null 2>&1 37 | then 38 | go tool lefthook "$@" 39 | elif bundle exec lefthook -h >/dev/null 2>&1 40 | then 41 | bundle exec lefthook "$@" 42 | elif yarn lefthook -h >/dev/null 2>&1 43 | then 44 | yarn lefthook "$@" 45 | elif pnpm lefthook -h >/dev/null 2>&1 46 | then 47 | pnpm lefthook "$@" 48 | elif swift package lefthook >/dev/null 2>&1 49 | then 50 | swift package --build-path .build/lefthook --disable-sandbox lefthook "$@" 51 | elif command -v mint >/dev/null 2>&1 52 | then 53 | mint run csjones/lefthook-plugin "$@" 54 | elif uv run lefthook -h >/dev/null 2>&1 55 | then 56 | uv run lefthook "$@" 57 | elif mise exec -- lefthook -h >/dev/null 2>&1 58 | then 59 | mise exec -- lefthook "$@" 60 | else 61 | echo "Can't find lefthook in PATH" 62 | fi 63 | fi 64 | } 65 | 66 | call_lefthook run "pre-commit" "$@" 67 | -------------------------------------------------------------------------------- /.husky/_/prepare-commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "$LEFTHOOK_VERBOSE" = "1" -o "$LEFTHOOK_VERBOSE" = "true" ]; then 4 | set -x 5 | fi 6 | 7 | if [ "$LEFTHOOK" = "0" ]; then 8 | exit 0 9 | fi 10 | 11 | call_lefthook() 12 | { 13 | if test -n "$LEFTHOOK_BIN" 14 | then 15 | "$LEFTHOOK_BIN" "$@" 16 | elif lefthook -h >/dev/null 2>&1 17 | then 18 | lefthook "$@" 19 | else 20 | dir="$(git rev-parse --show-toplevel)" 21 | osArch=$(uname | tr '[:upper:]' '[:lower:]') 22 | cpuArch=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/') 23 | if test -f "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" 24 | then 25 | "$dir/node_modules/lefthook-${osArch}-${cpuArch}/bin/lefthook" "$@" 26 | elif test -f "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" 27 | then 28 | "$dir/node_modules/@evilmartians/lefthook/bin/lefthook-${osArch}-${cpuArch}/lefthook" "$@" 29 | elif test -f "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" 30 | then 31 | "$dir/node_modules/@evilmartians/lefthook-installer/bin/lefthook" "$@" 32 | elif test -f "$dir/node_modules/lefthook/bin/index.js" 33 | then 34 | "$dir/node_modules/lefthook/bin/index.js" "$@" 35 | 36 | elif go tool lefthook -h >/dev/null 2>&1 37 | then 38 | go tool lefthook "$@" 39 | elif bundle exec lefthook -h >/dev/null 2>&1 40 | then 41 | bundle exec lefthook "$@" 42 | elif yarn lefthook -h >/dev/null 2>&1 43 | then 44 | yarn lefthook "$@" 45 | elif pnpm lefthook -h >/dev/null 2>&1 46 | then 47 | pnpm lefthook "$@" 48 | elif swift package lefthook >/dev/null 2>&1 49 | then 50 | swift package --build-path .build/lefthook --disable-sandbox lefthook "$@" 51 | elif command -v mint >/dev/null 2>&1 52 | then 53 | mint run csjones/lefthook-plugin "$@" 54 | elif uv run lefthook -h >/dev/null 2>&1 55 | then 56 | uv run lefthook "$@" 57 | elif mise exec -- lefthook -h >/dev/null 2>&1 58 | then 59 | mise exec -- lefthook "$@" 60 | else 61 | echo "Can't find lefthook in PATH" 62 | fi 63 | fi 64 | } 65 | 66 | call_lefthook run "prepare-commit-msg" "$@" 67 | -------------------------------------------------------------------------------- /.jscpd.json: -------------------------------------------------------------------------------- 1 | { 2 | "threshold": 2, 3 | "reporters": ["html"], 4 | "ignore": [ 5 | "**/node_modules/**", 6 | "**/.next/**", 7 | "**/.git/**", 8 | "**/.github/**", 9 | "**/.gitlab/**", 10 | "**/.docker/**", 11 | "**/.idea/**", 12 | "**/report/**", 13 | "**/*.svg" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 22.16 -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | engine-strict=true 3 | legacy-peer-deps=true 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | storybook-static 3 | .next 4 | public 5 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | arrowParens: avoid 2 | plugins: 3 | - prettier-plugin-tailwindcss 4 | printWidth: 100 5 | singleQuote: true 6 | tabWidth: 4 7 | tailwindFunctions: 8 | - cn 9 | - cva 10 | tailwindStylesheet: ./app/globals.css 11 | trailingComma: es5 12 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/nextjs'; 2 | 3 | import { join, dirname } from 'path'; 4 | 5 | function getAbsolutePath(value: string) { 6 | return dirname(require.resolve(join(value, 'package.json'))); 7 | } 8 | 9 | const config: StorybookConfig = { 10 | stories: ['../app/**/*.stories.tsx', '../packages/core/**/*.stories.tsx'], 11 | addons: [ 12 | getAbsolutePath('@storybook/addon-onboarding'), 13 | getAbsolutePath('@storybook/addon-links'), 14 | getAbsolutePath('@storybook/addon-essentials'), 15 | getAbsolutePath('@storybook/addon-interactions'), 16 | getAbsolutePath('@storybook/addon-storysource'), 17 | getAbsolutePath('@storybook/addon-designs'), 18 | ], 19 | framework: { 20 | name: getAbsolutePath('@storybook/nextjs'), 21 | options: {}, 22 | }, 23 | typescript: { 24 | check: false, 25 | reactDocgen: 'react-docgen-typescript', 26 | reactDocgenTypescriptOptions: { 27 | shouldRemoveUndefinedFromOptional: true, 28 | shouldExtractLiteralValuesFromEnum: true, 29 | shouldExtractValuesFromUnion: true, 30 | propFilter: prop => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true), 31 | }, 32 | }, 33 | features: { 34 | experimentalRSC: true, 35 | }, 36 | staticDirs: ['../public'], 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /.storybook/preview.tsx: -------------------------------------------------------------------------------- 1 | import '../app/globals.css'; 2 | import type { Preview } from '@storybook/react'; 3 | 4 | import { geistSans } from '../src/fonts/geist'; 5 | 6 | const preview: Preview = { 7 | parameters: { 8 | actions: { 9 | argTypesRegex: '^on[A-Z].*', 10 | }, 11 | controls: { 12 | expanded: true, 13 | matchers: { 14 | color: /(background|color)$/i, 15 | date: /Date$/i, 16 | }, 17 | }, 18 | tags: ['autodocs'], 19 | docs: { toc: true }, 20 | layout: 'centered', 21 | }, 22 | decorators: [ 23 | Story => ( 24 |
25 | 26 |
27 | ), 28 | ], 29 | }; 30 | 31 | export default preview; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NextJS Starter 2 | 3 | A robust boilerplate for quickly building web applications with Next.js. 4 | 5 | ## 🪄 Features: 6 | 7 | - Next 15+ (app router, server components) 8 | - React 19 9 | - Typescript 10 | - Tailwind 11 | - ESLint 12 | - Prettier 13 | - Husky 14 | - Commitizen (git-cz) 15 | - Vitest 16 | - Playwright 17 | - Lint-staged 18 | - Storybook 19 | - Sentry 20 | - Bundle analyzer 21 | - React Query 22 | - Kubb API Codegen 23 | - Figma tokens 24 | - Env validation 25 | 26 | ## 🚀 Get started 27 | 28 | 1. Install the project using `npx create-next-app -e https://github.com/webpractik/nextjs-starter --use-npm` 29 | 2. Copy environment variables to .env (`cp .env.example .env`) and configure them. 30 | 3. Start the development server with `npm run dev` 31 | 32 | ## 🎯 Deploy 33 | 34 | - **Node:** `^22` 35 | - **Npm:** `^10` 36 | - **App Port:** `3000` 37 | - **Healthcheck:** `/api/health` 38 | - **Ready:** `/api/ready` 39 | - **Prometheus Metrics:** `/api/metrics` 40 | 41 | ## Run production mode: 42 | 43 | - `npm ci` 44 | - `npm run build` 45 | - `npm run prod` 46 | 47 | ## 📦 Additional utilities: 48 | 49 | - [nanoid](https://www.npmjs.com/package/nanoid) - Generate unique IDs 50 | - [lodash-es](https://lodash.com/docs) - Utility library 51 | - [react-use](https://github.com/streamich/react-use#readme) - Collection of hooks for React 52 | - [isomorphic-dompurify](https://www.npmjs.com/package/isomorphic-dompurify) - DOM sanitization library 53 | - [clsx](https://www.npmjs.com/package/clsx) - Utility for constructing CSS class names 54 | -------------------------------------------------------------------------------- /app/(public)/(home)/_components/hero.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Typography } from '@repo/core/typography'; 3 | import { motion } from 'framer-motion'; 4 | 5 | const initialState = { filter: 'blur(10px)', opacity: 0, y: 40 }; 6 | 7 | const transition = { 8 | delay: 0.7, 9 | duration: 1, 10 | ease: 'easeInOut', 11 | }; 12 | 13 | const whileInView = { filter: 'blur(0px)', opacity: 1, y: 0 }; 14 | 15 | export function Hero() { 16 | return ( 17 |
18 | 24 | 28 | Next Starter 29 | 30 |
31 | Этот стартовый комплект нацелен на предоставление разработчикам надежной основы 32 | для создания приложений на Next.js, обеспечивая соблюдение лучших практик по 33 | качеству кода, стилю и эффективности рабочих процессов. 34 |
35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /app/(public)/(home)/error.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { ErrorFallback } from '@/_shared/utilities/error-boundary'; 4 | import * as Sentry from '@sentry/nextjs'; 5 | import { useEffect } from 'react'; 6 | 7 | type ErrorProps = { 8 | error: { digest?: string } & Error; 9 | reset: () => void; 10 | }; 11 | 12 | export default function ErrorPage({ error, reset }: Readonly) { 13 | useEffect(() => { 14 | Sentry.captureException(error); 15 | }, [error]); 16 | 17 | return ; 18 | } 19 | -------------------------------------------------------------------------------- /app/(public)/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import { Hero } from './_components/hero'; 2 | 3 | export default function HomePage() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /app/(public)/ui-demo/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { Button } from '@repo/core/button'; 3 | import { Checkbox } from '@repo/core/checkbox'; 4 | import { 5 | Dialog, 6 | DialogClose, 7 | DialogContent, 8 | DialogDescription, 9 | DialogFooter, 10 | DialogHeader, 11 | DialogTitle, 12 | DialogTrigger, 13 | } from '@repo/core/dialog'; 14 | import { Input } from '@repo/core/input'; 15 | import { Label } from '@repo/core/label'; 16 | import { RadioGroup, RadioGroupItem } from '@repo/core/radio-group'; 17 | import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@repo/core/select'; 18 | import { Skeleton } from '@repo/core/skeleton'; 19 | import { toast } from '@repo/core/sonner'; 20 | import { Switch } from '@repo/core/switch'; 21 | import { Tabs } from '@repo/core/tabs'; 22 | import { TabsContent, TabsList, TabsTrigger } from '@repo/core/tabs/tabs'; 23 | import { Textarea } from '@repo/core/textarea'; 24 | import { 25 | Tooltip, 26 | TooltipContent, 27 | TooltipProvider, 28 | TooltipTrigger, 29 | } from '@repo/core/tooltip/tooltip'; 30 | import { Typography } from '@repo/core/typography'; 31 | import { CircleHelp } from 'lucide-react'; 32 | 33 | const gridItemClassName = 34 | 'flex place-content-center items-center col-span-1 border border-dashed border-slate-200 p-4 rounded-2xl'; 35 | 36 | export default function UiPage() { 37 | return ( 38 |
39 |
40 |
41 |
42 | Заголовок 43 | 44 | Простой текст 45 | 46 |
47 |
48 | 49 |
50 |
51 | 52 | 55 | 56 |
57 |
58 | 59 |
60 |
61 | 62 |