front init
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import { mkdir, rename, rm } from "node:fs/promises"
|
||||
|
||||
const segments = "ui,model,api"
|
||||
const args = Bun.argv.slice(2)
|
||||
|
||||
function printUsageAndExit(): never {
|
||||
console.error(
|
||||
"Ошибка: Необходимо указать <название слоя> и <название компонента>",
|
||||
)
|
||||
console.log("Пример: bun gc features MyButton")
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
function runShell(command: string): void {
|
||||
console.log(`Выполняется: ${command}`)
|
||||
|
||||
const result = Bun.spawnSync({
|
||||
cmd: ["sh", "-lc", command],
|
||||
stdin: "inherit",
|
||||
stdout: "inherit",
|
||||
stderr: "inherit",
|
||||
})
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
const suffix = result.signalCode ? ` (signal ${result.signalCode})` : ""
|
||||
throw new Error(`Команда завершилась с кодом ${result.exitCode}${suffix}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length < 2) {
|
||||
printUsageAndExit()
|
||||
}
|
||||
|
||||
const layer = args[0]
|
||||
const component = args[1]
|
||||
const otherArgs = args.slice(2).join(" ")
|
||||
|
||||
let componentPath = "./src/"
|
||||
|
||||
if (layer === "shared" || layer === "app") {
|
||||
componentPath += `${layer}/${component}`
|
||||
} else if (layer === "entity" || layer === "entities") {
|
||||
componentPath += `entities/${component}`
|
||||
} else {
|
||||
componentPath += `${layer}s/${component}`
|
||||
}
|
||||
|
||||
const fsdCommand = `fsd ${layer} ${component} ${otherArgs} --segments ${segments} -r src`
|
||||
|
||||
try {
|
||||
runShell(fsdCommand)
|
||||
} catch (error) {
|
||||
console.error(`Ошибка выполнения команды: ${(error as Error).message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log(`Удаление индекс файла: ${componentPath}/index.ts`)
|
||||
|
||||
try {
|
||||
await rm(`${componentPath}/index.ts`)
|
||||
} catch (error) {
|
||||
console.error(`Ошибка выполнения команды: ${(error as Error).message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const createTsx = `bunx generate-react-cli component ${component} --flat --path ${componentPath}`
|
||||
|
||||
try {
|
||||
runShell(createTsx)
|
||||
} catch (error) {
|
||||
console.error(`Ошибка выполнения команды: ${(error as Error).message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log("Перемещение файлов...")
|
||||
|
||||
const oldPath = `${componentPath}/${component}`
|
||||
const newPath = `${componentPath}`
|
||||
|
||||
try {
|
||||
await mkdir(`${newPath}/ui`, { recursive: true })
|
||||
await mkdir(`${newPath}/model`, { recursive: true })
|
||||
|
||||
await rename(`${oldPath}.tsx`, `${newPath}/ui/${component}.tsx`)
|
||||
console.log("TSX файл перемещен")
|
||||
|
||||
await rename(
|
||||
`${oldPath}.module.scss`,
|
||||
`${newPath}/ui/${component}.module.scss`,
|
||||
)
|
||||
console.log("SCSS файл перемещен")
|
||||
|
||||
await rename(`${oldPath}.d.ts`, `${newPath}/model/${component}.d.ts`)
|
||||
console.log("D.TS файл перемещен")
|
||||
} catch (error) {
|
||||
console.error(`Ошибка выполнения команды: ${(error as Error).message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
export interface ITemplateNameProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
.root {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
|
||||
import { ITemplateNameProps } from "../model/TemplateName.d"
|
||||
import styles from "./TemplateName.module.scss"
|
||||
|
||||
export const TemplateName: FunctionComponent<
|
||||
ITemplateNameProps
|
||||
> = (): JSX.Element => {
|
||||
return (
|
||||
<div className={styles.root} data-testid="TemplateName">
|
||||
TemplateName Component
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./ui/TemplateName"
|
||||
@@ -0,0 +1,217 @@
|
||||
# AGENTS.md — Coffee Project Frontend
|
||||
|
||||
## Project Overview
|
||||
|
||||
Next.js 16 application using **Feature-Sliced Design (FSD)** architecture, powered by **Bun** runtime and package manager.
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack
|
||||
|
||||
| Category | Technology |
|
||||
| ------------- | ------------------------------------------ |
|
||||
| Runtime | Bun 1.3.5 |
|
||||
| Framework | Next.js 16.1.1 (App Router) |
|
||||
| Language | TypeScript 5.9 |
|
||||
| UI Library | React 19 |
|
||||
| Styling | SCSS Modules, normalize.css |
|
||||
| State/Fetch | TanStack React Query 5, Axios, Xior |
|
||||
| Animation | Framer Motion |
|
||||
| Utilities | Lodash, Moment.js, classnames, usehooks-ts |
|
||||
| Icons | Lucide React, SVGR (custom icons) |
|
||||
| Notifications | React Toastify |
|
||||
| File Upload | React Dropzone |
|
||||
| Linting | ESLint 9, Prettier, Stylelint |
|
||||
| Testing | Jest, Testing Library |
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
bun dev # Start dev server
|
||||
bun run build # Production build
|
||||
bun run start # Start production server
|
||||
bun run lint # Run ESLint + Prettier
|
||||
bun run gc <layer> <ComponentName> # Generate FSD component
|
||||
bun run gicons # Convert SVGs to React components
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure (FSD)
|
||||
|
||||
```
|
||||
app/ # Next.js App Router entry
|
||||
pages/ # Keep empty (Next.js requires it)
|
||||
public/ # Static assets
|
||||
src/
|
||||
├── app/ # App layer: global styles, providers
|
||||
├── pages/ # Page compositions
|
||||
├── widgets/ # Large UI blocks (header, sidebar)
|
||||
├── features/ # User interactions (auth, search)
|
||||
├── entities/ # Business entities (user, product)
|
||||
└── shared/ # Reusable: UI kit, utils, API, assets
|
||||
├── ui/ # Button, Input, Icons...
|
||||
├── api/ # API clients
|
||||
├── lib/ # Utilities
|
||||
└── assets/ # Images, raw-icons
|
||||
```
|
||||
|
||||
### Path Aliases
|
||||
|
||||
```ts
|
||||
@app/* → ./src/app/*
|
||||
@pages/* → ./src/pages/*
|
||||
@widgets/* → ./src/widgets/*
|
||||
@features/* → ./src/features/*
|
||||
@entities/* → ./src/entities/*
|
||||
@shared/* → ./src/shared/*
|
||||
@/* → ./src/*
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Structure
|
||||
|
||||
Each component follows FSD slice structure:
|
||||
|
||||
```
|
||||
ComponentName/
|
||||
├── index.ts # Public API (re-export)
|
||||
├── ui/
|
||||
│ ├── ComponentName.tsx
|
||||
│ └── ComponentName.module.scss
|
||||
├── model/
|
||||
│ └── ComponentName.d.ts # Props interface
|
||||
└── api/ # Optional: component-specific API
|
||||
```
|
||||
|
||||
### Component Template
|
||||
|
||||
```tsx
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { FunctionComponent } from "react"
|
||||
|
||||
import { IComponentNameProps } from "../model/ComponentName.d"
|
||||
import styles from "./ComponentName.module.scss"
|
||||
|
||||
export const ComponentName: FunctionComponent<
|
||||
IComponentNameProps
|
||||
> = (): JSX.Element => {
|
||||
return (
|
||||
<div className={styles.root} data-testid="ComponentName">
|
||||
ComponentName
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Generate Component
|
||||
|
||||
```bash
|
||||
bun run gc <layer> <ComponentName>
|
||||
# Examples:
|
||||
bun run gc shared Button
|
||||
bun run gc features AuthForm
|
||||
bun run gc entities UserCard
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Code Style
|
||||
|
||||
1. **Small Functions** — max 20-30 lines; extract helpers
|
||||
2. **Single Responsibility** — one function = one purpose
|
||||
3. **Descriptive Names** — `getUserById` not `getData`
|
||||
4. **Early Returns** — reduce nesting with guard clauses
|
||||
5. **No Magic Values** — use constants: `const MAX_ITEMS = 10`
|
||||
|
||||
### TypeScript
|
||||
|
||||
```ts
|
||||
// ✅ Use interfaces for props
|
||||
interface IButtonProps {
|
||||
variant: "primary" | "secondary"
|
||||
disabled?: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
// ✅ Prefer explicit types over `any`
|
||||
// ✅ Use `type` for unions/intersections, `interface` for objects
|
||||
```
|
||||
|
||||
### React Patterns
|
||||
|
||||
```tsx
|
||||
// ✅ Functional components with explicit return type
|
||||
export const Button: FC<IButtonProps> = ({ variant, onClick }): JSX.Element => { ... }
|
||||
|
||||
// ✅ Destructure props
|
||||
// ✅ Use data-testid for testing
|
||||
// ✅ Colocate styles with components (CSS Modules)
|
||||
```
|
||||
|
||||
### FSD Rules
|
||||
|
||||
1. **Import Direction** — only downward: `features → entities → shared`
|
||||
2. **Public API** — export only through `index.ts`
|
||||
3. **No Cross-Slice Imports** — features cannot import from other features
|
||||
4. **Shared is Agnostic** — no business logic in shared layer
|
||||
|
||||
### File Naming
|
||||
|
||||
| Type | Convention | Example |
|
||||
| --------- | ----------------- | ---------------------- |
|
||||
| Component | PascalCase | `UserCard.tsx` |
|
||||
| Module | PascalCase.module | `UserCard.module.scss` |
|
||||
| Types | PascalCase.d | `UserCard.d.ts` |
|
||||
| Hook | camelCase (use-) | `useAuth.ts` |
|
||||
| Utility | camelCase | `formatDate.ts` |
|
||||
| Constant | UPPER_SNAKE_CASE | `API_ENDPOINTS.ts` |
|
||||
|
||||
### Performance
|
||||
|
||||
1. Use `React.memo` for expensive renders
|
||||
2. Use `useMemo` / `useCallback` for derived data and callbacks
|
||||
3. Lazy load pages/heavy components with `next/dynamic`
|
||||
4. Prefer server components where possible (Next.js App Router)
|
||||
|
||||
### Testing
|
||||
|
||||
- Place tests next to components: `ComponentName.test.tsx`
|
||||
- Use `data-testid` attributes for queries
|
||||
- Test behavior, not implementation
|
||||
|
||||
---
|
||||
|
||||
## Icons Workflow
|
||||
|
||||
1. Place raw SVG in `src/shared/assets/raw-icons/`
|
||||
2. Run `bun run gicons`
|
||||
3. Import from `@shared/ui/Icons/IconName`
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- **Keep `pages/` folder** in root — removing causes Next.js build errors
|
||||
- **Bun only** — use `bun` commands, not npm/yarn
|
||||
- **SCSS Modules** — all styles are scoped via `.module.scss`
|
||||
- **Strict TypeScript** — `strict: true` in tsconfig
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
| Task | Command / Location |
|
||||
| ------------------ | ------------------------------- |
|
||||
| Add dependency | `bun add <package>` |
|
||||
| Add dev dependency | `bun add -d <package>` |
|
||||
| Create component | `bun run gc <layer> <Name>` |
|
||||
| Global styles | `src/app/styles/global.scss` |
|
||||
| API client | Use Axios/Xior with React Query |
|
||||
| State management | TanStack Query (server state) |
|
||||
@@ -1,23 +1,13 @@
|
||||
# Next.js + Feature-Sliced Design | Pure Template
|
||||
# Coffee Project Frontend
|
||||
|
||||
## IMPORTANT
|
||||
Next.js application structured with Feature-Sliced Design and configured to run with Bun.
|
||||
|
||||
Do not delete the pages\* folder even if you are using App Router, deleting the pages folder
|
||||
will result in a [build error](https://t.me/feature_sliced/1/107414).
|
||||
## Notes
|
||||
|
||||
In WebStorm you can mark a directory as [excluded](https://www.jetbrains.com/help/webstorm/configuring-project-structure.html#content-root). After that, you won’t see it in shared files (like, for example, node_modules or .next)
|
||||
- Keep the `pages` folder in the project root; removing it can cause a build error with Next.js.
|
||||
- Bun is the package manager: `bun install`, `bun dev`, `bun run build`, `bun run lint`.
|
||||
|
||||
## ADDITIONAL SOLUTION TO THE PROBLEM (NOT RECOMMENDED)
|
||||
|
||||
If you don't like the empty "pages" folder in the project root, you can rename the pages layer (./src/pages)
|
||||
for example to "pagesLayer" and then delete the pages folder from the project root
|
||||
(you will also have to change path aliases in tsconfig and similar).
|
||||
|
||||
**Use what you like best <3**
|
||||
|
||||
---
|
||||
|
||||
## Folders description
|
||||
## Folders
|
||||
|
||||
| Folder | Description |
|
||||
| ------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
@@ -31,31 +21,8 @@ for example to "pagesLayer" and then delete the pages folder from the project ro
|
||||
| src/entities | Entities FSD Layer |
|
||||
| src/shared | Shared FSD Layer |
|
||||
|
||||
## Remove junk _.gitkeep_ files
|
||||
## Tooling
|
||||
|
||||
### UNIX
|
||||
|
||||
```bash
|
||||
rm -rf .src/app/.gitkeep .src/entities/.gitkeep .src/features/.gitkeep .src/shared/.gitkeep .src/widgets/.gitkeep ./public/.gitkeep
|
||||
```
|
||||
|
||||
### WINDOWS
|
||||
|
||||
```bash
|
||||
del .\src\app\.gitkeep
|
||||
del .\src\entities\.gitkeep
|
||||
del .\src\features\.gitkeep
|
||||
del .\src\shared\.gitkeep
|
||||
del .\src\widgets\.gitkeep
|
||||
del .\public\.gitkeep
|
||||
```
|
||||
|
||||
This template uses such development assistants
|
||||
|
||||
- Eslint
|
||||
- ESLint
|
||||
- Prettier
|
||||
- Stylelint
|
||||
- Jest
|
||||
|
||||
If you don't need it, you can disable it at any time by removing the
|
||||
dependency from your _package.json_ and _.\*rc_ file.
|
||||
|
||||
+4
-3
@@ -1,11 +1,12 @@
|
||||
import type { Metadata } from "next"
|
||||
import type { ReactNode } from "react"
|
||||
|
||||
import "@app/styles/global.scss"
|
||||
import "@shared/styles/global.scss"
|
||||
import "bootstrap/dist/css/bootstrap.min.css"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Let's Develop!",
|
||||
description: "FSD Template with Next.js by yunglocokid",
|
||||
title: "Coffee Project",
|
||||
description: "Standalone Next.js app using FSD structure",
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"usesTypeScript": true,
|
||||
"usesStyledComponents": false,
|
||||
"usesCssModule": true,
|
||||
"cssPreprocessor": "scss",
|
||||
"testLibrary": "Testing Library",
|
||||
"component": {
|
||||
"default": {
|
||||
"path": "src/shared",
|
||||
"withStyle": true,
|
||||
"withTest": false,
|
||||
"withStory": false,
|
||||
"withLazy": false,
|
||||
"withIndex": true,
|
||||
"withTypeDeclare": true,
|
||||
"customTemplates": {
|
||||
"index": ".templates/component/index.ts",
|
||||
"component": ".templates/component/TemplateName.tsx",
|
||||
"typeDeclare": ".templates/component/TemplateName.d.ts",
|
||||
"style": ".templates/component/TemplateName.module.scss"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+16
-1
@@ -1,4 +1,19 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
import path from "path"
|
||||
import { fileURLToPath } from "url"
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const stylesPath = path.join(dirname, "src/shared/styles")
|
||||
console.log("dirname", dirname)
|
||||
|
||||
const nextConfig = {
|
||||
sassOptions: {
|
||||
includePaths: [stylesPath],
|
||||
additionalData: `@use "${path.join(stylesPath, "_variables.scss")}";
|
||||
@use "${path.join(stylesPath, "_breakpoints.scss")}";
|
||||
@use "${path.join(stylesPath, "_typography.scss")}";
|
||||
@use "${path.join(stylesPath, "_mixins.scss")}";`,
|
||||
},
|
||||
}
|
||||
|
||||
export default nextConfig
|
||||
|
||||
+49
-41
@@ -1,54 +1,62 @@
|
||||
{
|
||||
"name": "fsd-nest-template",
|
||||
"description": "Pure Next.js FSD template with jest, react testing lib, stylelint, prettier and eslint",
|
||||
"author": {
|
||||
"name": "@yunglocokid (Dmitriy Bratchikov)",
|
||||
"url": "https://github.com/yunglocokid"
|
||||
},
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"packageManager": "bun@1.3.5",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "concurrently \"yarn lint:es\" \"yarn lint:prettier\"",
|
||||
"lint:es": "next lint",
|
||||
"lint:prettier": "prettier . --write",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch"
|
||||
"dev": "bun --bun next dev",
|
||||
"build": "bun --bun next build",
|
||||
"start": "bun --bun next start",
|
||||
"lint": "concurrently \"bun run lint:es\" \"bun run lint:prettier\"",
|
||||
"create-component": "npx generate-react-cli component",
|
||||
"gc": "bun run .scripts/create-fsd-component.ts",
|
||||
"gicons": "npx @svgr/cli --ext tsx --typescript --no-prettier --icon --ref --no-svgo ./src/shared/assets/raw-icons/ --out-dir ./src/shared/ui/Icons/"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "14.2.3",
|
||||
"@tanstack/react-query": "^5.90.14",
|
||||
"@tanstack/react-query-devtools": "^5.91.2",
|
||||
"axios": "^1.13.2",
|
||||
"bootstrap": "^5.3.8",
|
||||
"classnames": "^2.5.1",
|
||||
"framer-motion": "^12.23.26",
|
||||
"lodash": "^4.17.21",
|
||||
"lucide-react": "^0.562.0",
|
||||
"moment": "^2.30.1",
|
||||
"next": "16.1.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18",
|
||||
"react-dom": "^18"
|
||||
"openapi-react-query": "^0.5.1",
|
||||
"react": "^19.2.3",
|
||||
"react-aria-components": "^1.14.0",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.3",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-toastify": "^11.0.5",
|
||||
"usehooks-ts": "^3.1.1",
|
||||
"xior": "^0.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.2.1",
|
||||
"@testing-library/jest-dom": "^6.4.5",
|
||||
"@testing-library/react": "^15.0.7",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18",
|
||||
"@types/react-dom": "^18",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8",
|
||||
"eslint-config-next": "14.2.3",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-boundaries": "^4.2.1",
|
||||
"eslint-plugin-react-hooks": "^4.6.2",
|
||||
"eslint-plugin-react-refresh": "^0.4.7",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.77.1",
|
||||
"stylelint": "^16.5.0",
|
||||
"stylelint-config-standard": "^36.0.0",
|
||||
"stylelint-config-standard-scss": "^13.1.0",
|
||||
"stylelint-order": "^6.0.4",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||
"@svgr/cli": "^8.1.0",
|
||||
"@types/bun": "^1.3.5",
|
||||
"@types/node": "^25.0.3",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.1.1",
|
||||
"eslint-import-resolver-typescript": "^4.4.4",
|
||||
"eslint-plugin-boundaries": "^5.3.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.26",
|
||||
"prettier": "^3.7.4",
|
||||
"sass": "^1.97.1",
|
||||
"stylelint": "^16.26.1",
|
||||
"stylelint-config-standard": "^39.0.1",
|
||||
"stylelint-config-standard-scss": "^16.0.0",
|
||||
"stylelint-order": "^7.0.0",
|
||||
"stylelint-order-config-standard": "^0.1.3",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5",
|
||||
"typescript-eslint": "^7.10.0"
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.50.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
@import "normalize.css";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
border: 0;
|
||||
}
|
||||
@@ -13,15 +13,15 @@
|
||||
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 2vw;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.path {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.font {
|
||||
font-size: 5vw;
|
||||
.title {
|
||||
font-size: 4vw;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hint {
|
||||
@@ -34,32 +34,3 @@
|
||||
|
||||
font-size: 1vw;
|
||||
}
|
||||
|
||||
.testHint {
|
||||
margin: 0 30px;
|
||||
}
|
||||
|
||||
.link {
|
||||
transition: 0.3s;
|
||||
animation: shine 4s linear infinite;
|
||||
|
||||
color: #000;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
#020024 10%,
|
||||
#5b0979 40%,
|
||||
#5b0979 60%,
|
||||
#020024 80%
|
||||
);
|
||||
background-clip: text;
|
||||
background-size: 200% auto;
|
||||
|
||||
font-size: 5vw;
|
||||
-webkit-text-fill-color: transparent;
|
||||
|
||||
@keyframes shine {
|
||||
to {
|
||||
background-position: 200% center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
import Link from "next/link"
|
||||
import { Button } from "@shared/ui"
|
||||
|
||||
import cls from "./HomePage.module.scss"
|
||||
|
||||
const HomePage = () => {
|
||||
return (
|
||||
<div className={cls.homepage}>
|
||||
<p className={cls.font}>
|
||||
Hello from{" "}
|
||||
<Link
|
||||
href="https://github.com/yunglocokid"
|
||||
target="_blank"
|
||||
className={cls.link}
|
||||
>
|
||||
yunglocokid
|
||||
</Link>
|
||||
</p>
|
||||
<pre className={cls.hint} data-testid="hint-code">
|
||||
You can edit <span className={cls.path}>src/pages/HomePage</span> to
|
||||
start {"<3"}!<br />
|
||||
<small className={cls.testHint}>
|
||||
You can also test your application using Jest :D. Try it!
|
||||
</small>
|
||||
<p className={cls.title}>Coffee Project Starter</p>
|
||||
<pre className={cls.hint}>
|
||||
Edit <span className={cls.path}>src/pages/HomePage</span> to begin
|
||||
building your features.
|
||||
</pre>
|
||||
<Button variant="primary">Get Started</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
@use "sass:map";
|
||||
@use "sass:meta";
|
||||
|
||||
$mobile: 767;
|
||||
$tablet: 1439;
|
||||
$desktopSecond: 1919;
|
||||
|
||||
$mobileMin: "mobileMin";
|
||||
$mobileMax: "mobileMax";
|
||||
$tabletMin: "tabletMin";
|
||||
$tabletMax: "tabletMax";
|
||||
$desktopSecondMin: "desktopSecondMin";
|
||||
$desktopSecondMax: "desktopSecondMax";
|
||||
|
||||
$breakpoints: (
|
||||
"mobileMin": (
|
||||
min-width: $mobile + 1 + px
|
||||
),
|
||||
"mobileMax": (
|
||||
max-width: $mobile + px
|
||||
),
|
||||
"tabletMin": (
|
||||
min-width: $tablet + 1 + px
|
||||
),
|
||||
"tabletMax": (
|
||||
max-width: $tablet + px
|
||||
),
|
||||
"desktopSecondMin": (
|
||||
min-width: $desktopSecond + 1 + px
|
||||
),
|
||||
"desktopSecondMax": (
|
||||
max-width: $desktopSecond + px
|
||||
)
|
||||
) !default;
|
||||
|
||||
@mixin respond-to($breakpoint) {
|
||||
@if map.has-key($breakpoints, $breakpoint) {
|
||||
@media #{meta.inspect(map.get($breakpoints, $breakpoint))} {
|
||||
@content;
|
||||
}
|
||||
} @else {
|
||||
@warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
|
||||
+ "Available breakpoints are: #{map.keys($breakpoints)}.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Common mixins for the project
|
||||
|
||||
// Flexbox center
|
||||
@mixin flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
// Flexbox column
|
||||
@mixin flex-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// Truncate text with ellipsis
|
||||
@mixin text-ellipsis {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Visually hidden (accessible)
|
||||
@mixin visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
// Reset button styles
|
||||
@mixin reset-button {
|
||||
padding: 0;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
// Reset list styles
|
||||
@mixin reset-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
@mixin font-header-l {
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
letter-spacing: -0.41px;
|
||||
}
|
||||
|
||||
@mixin font-subheader-l {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-m {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-mm {
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-mr {
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-body-s {
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
|
||||
@mixin font-caption-m {
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
line-height: 18px;
|
||||
letter-spacing: 0px;
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
$purple-50: var(--purple-50);
|
||||
$purple-100: var(--purple-100);
|
||||
$purple-200: var(--purple-200);
|
||||
$purple-300: var(--purple-300);
|
||||
$purple-400: var(--purple-400);
|
||||
$purple-500: var(--purple-500);
|
||||
$purple-600: var(--purple-600);
|
||||
$purple-700: var(--purple-700);
|
||||
$purple-800: var(--purple-800);
|
||||
$purple-900: var(--purple-900);
|
||||
|
||||
$green-50: var(--green-50);
|
||||
$green-100: var(--green-100);
|
||||
$green-200: var(--green-200);
|
||||
$green-300: var(--green-300);
|
||||
$green-400: var(--green-400);
|
||||
$green-500: var(--green-500);
|
||||
$green-600: var(--green-600);
|
||||
$green-700: var(--green-700);
|
||||
$green-800: var(--green-800);
|
||||
$green-900: var(--green-900);
|
||||
|
||||
$color-success: var(--color-success);
|
||||
$color-danger: var(--color-danger);
|
||||
$color-warning: var(--color-warning);
|
||||
|
||||
$color-primary: var(--color-primary);
|
||||
$color-secondary: var(--color-secondary);
|
||||
$color-white: var(--color-white);
|
||||
$color-black: var(--color-black);
|
||||
|
||||
$header-height: var(--header-height);
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
@import "normalize.css";
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-family: var(--font-roboto);
|
||||
font-variant-numeric: lining-nums proportional-nums;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-variation-settings: "opsz" 10;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* Dark Fuchsia Palette (HSL) */
|
||||
--purple-50: hsl(300, 52%, 93%);
|
||||
--purple-100: hsl(298, 46%, 82%);
|
||||
--purple-200: hsl(298, 51%, 69%);
|
||||
--purple-300: hsl(297, 53%, 56%);
|
||||
--purple-400: hsl(297, 70%, 44%); /* Primary */
|
||||
--purple-500: hsl(296, 100%, 35%);
|
||||
--purple-600: hsl(293, 100%, 34%);
|
||||
--purple-700: hsl(288, 100%, 33%);
|
||||
--purple-800: hsl(283, 100%, 32%);
|
||||
--purple-900: hsl(272, 100%, 30%);
|
||||
|
||||
/* Deep Lime Green Palette (HSL) */
|
||||
--green-50: hsl(84, 50%, 94%);
|
||||
--green-100: hsl(85, 48%, 83%);
|
||||
--green-200: hsl(86, 47%, 73%);
|
||||
--green-300: hsl(85, 47%, 61%);
|
||||
--green-400: hsl(85, 51%, 53%);
|
||||
--green-500: hsl(84, 67%, 43%);
|
||||
--green-600: hsl(87, 71%, 39%);
|
||||
--green-700: hsl(88, 79%, 33%);
|
||||
--green-800: hsl(90, 93%, 26%); /* Secondary */
|
||||
--green-900: hsl(104, 100%, 19%);
|
||||
|
||||
--color-success: #22c55e;
|
||||
--color-danger: #ef4444;
|
||||
--color-warning: #facc15;
|
||||
|
||||
--color-primary: var(--purple-400);
|
||||
--color-secondary: var(--green-800);
|
||||
--color-white: #ffffff;
|
||||
--color-black: #000000;
|
||||
|
||||
--header-height: 56px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
.app {
|
||||
width: 100%;
|
||||
// max-width: 1192px;
|
||||
margin: 0 auto;
|
||||
padding: 16px;
|
||||
padding-top: 24px;
|
||||
box-sizing: border-box;
|
||||
height: calc(100vh - var(--header-height) - 16px);
|
||||
margin-top: var(--header-height);
|
||||
|
||||
@include breakpoints.respond-to(breakpoints.$mobileMax) {
|
||||
padding-top: 12px;
|
||||
height: calc(100svh - var(--header-height));
|
||||
}
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
export interface IButtonProps {
|
||||
message?: string
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import BootstrapButton, { ButtonProps } from "react-bootstrap/Button"
|
||||
|
||||
export const Button = (props: ButtonProps) => (
|
||||
<BootstrapButton variant="primary" {...props}>
|
||||
{props.children}
|
||||
</BootstrapButton>
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./Button"
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./Button"
|
||||
+10
-4
@@ -10,7 +10,7 @@
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"jsx": "react-jsx",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
@@ -24,10 +24,16 @@
|
||||
"@pages/*": ["./src/pages/*"],
|
||||
"@shared/*": ["./src/shared/*"],
|
||||
"@widgets/*": ["./src/widgets/*"],
|
||||
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"target": "ES2017"
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"include": [
|
||||
"next-env.d.ts",
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
".next/types/**/*.ts",
|
||||
".next/dev/types/**/*.ts"
|
||||
],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user