mirror of
https://github.com/gromlab-ru/slm-design.git
synced 2026-08-22 15:30:16 +03:00
feat: add example
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
'use client'
|
||||
|
||||
import { useContext } from 'react'
|
||||
import { {{name.pascalCase}}BusinessContext } from '../providers/{{name.kebabCase}}-business.provider'
|
||||
import type { {{name.pascalCase}}Business } from '../types/{{name.kebabCase}}-business.type'
|
||||
|
||||
/**
|
||||
* Возвращает business API, доступный внутри композиционного модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export const use{{name.pascalCase}}Business = (): {{name.pascalCase}}Business => {
|
||||
const business = useContext({{name.pascalCase}}BusinessContext)
|
||||
|
||||
if (!business) {
|
||||
throw new Error('use{{name.pascalCase}}Business must be used within {{name.pascalCase}}BusinessProvider')
|
||||
}
|
||||
|
||||
return business
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export { {{name.pascalCase}}BusinessProvider } from './providers/{{name.kebabCase}}-business.provider'
|
||||
export { use{{name.pascalCase}}Business } from './hooks/use-{{name.kebabCase}}-business.hook'
|
||||
export type { {{name.pascalCase}}Business } from './types/{{name.kebabCase}}-business.type'
|
||||
export type { {{name.pascalCase}}BusinessProviderProps } from './types/{{name.kebabCase}}-business-provider-props.type'
|
||||
@@ -0,0 +1,27 @@
|
||||
'use client'
|
||||
|
||||
import { createContext } from 'react'
|
||||
import type { {{name.pascalCase}}Business } from '../types/{{name.kebabCase}}-business.type'
|
||||
import type { {{name.pascalCase}}BusinessProviderProps } from '../types/{{name.kebabCase}}-business-provider-props.type'
|
||||
|
||||
/**
|
||||
* Context business API для композиционного модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export const {{name.pascalCase}}BusinessContext = createContext<{{name.pascalCase}}Business | null>(null)
|
||||
|
||||
/**
|
||||
* Провайдер business API для композиционного модуля {{name.pascalCase}}.
|
||||
*
|
||||
* Используется для:
|
||||
* - передачи собранных business-фабрик вложенным модулям
|
||||
* - сохранения единой client boundary для business API
|
||||
*/
|
||||
export const {{name.pascalCase}}BusinessProvider = (props: {{name.pascalCase}}BusinessProviderProps) => {
|
||||
const { children, value } = props
|
||||
|
||||
return (
|
||||
<{{name.pascalCase}}BusinessContext.Provider value={value}>
|
||||
{children}
|
||||
</{{name.pascalCase}}BusinessContext.Provider>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { {{name.pascalCase}}Business } from './{{name.kebabCase}}-business.type'
|
||||
|
||||
/**
|
||||
* Параметры провайдера business API для {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}BusinessProviderProps = {
|
||||
/** Вложенное дерево композиционного модуля. */
|
||||
children: ReactNode
|
||||
/** Собранный business API. */
|
||||
value: {{name.pascalCase}}Business
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Business API, доступный внутри композиционного модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Business = object
|
||||
@@ -0,0 +1,5 @@
|
||||
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 = object
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Зависимости бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Deps = object
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Доменная сущность {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}} = object
|
||||
@@ -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 = (_deps) => {
|
||||
return {}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
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}}Factory } from './types/{{name.kebabCase}}-factory.type'
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Публичный API бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Api = object
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { {{name.pascalCase}}Api } from './{{name.kebabCase}}-api.type'
|
||||
|
||||
/**
|
||||
* Фабрика публичного API бизнес-модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Factory = () => {{name.pascalCase}}Api
|
||||
@@ -0,0 +1,4 @@
|
||||
/**
|
||||
* Доменная сущность {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}} = object
|
||||
@@ -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 {}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
||||
export type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}-props.type'
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Params = object
|
||||
|
||||
/**
|
||||
* Атрибуты корневого элемента {{name.pascalCase}}.
|
||||
*/
|
||||
type RootAttrs = ComponentPropsWithoutRef<'div'>
|
||||
|
||||
/**
|
||||
* Props компонента {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||
@@ -0,0 +1,20 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}-props.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||
const { children, className, ...rootAttrs } = props
|
||||
|
||||
return (
|
||||
<div {...rootAttrs} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { {{name.pascalCase}}Layout } from './{{name.kebabCase}}.layout'
|
||||
export type { {{name.pascalCase}}LayoutProps } from './types/{{name.kebabCase}}-layout-props.type'
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры layout {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}LayoutParams = object
|
||||
|
||||
/**
|
||||
* Атрибуты корневого элемента layout {{name.pascalCase}}.
|
||||
*/
|
||||
type RootAttrs = ComponentPropsWithoutRef<'div'>
|
||||
|
||||
/**
|
||||
* Props layout {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}LayoutProps = RootAttrs & {{name.pascalCase}}LayoutParams
|
||||
@@ -0,0 +1,20 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}LayoutProps } from './types/{{name.kebabCase}}-layout-props.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение layout {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Layout = (props: {{name.pascalCase}}LayoutProps) => {
|
||||
const { children, className, ...rootAttrs } = props
|
||||
|
||||
return (
|
||||
<div {...rootAttrs} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { {{name.pascalCase}} } from './{{name.kebabCase}}'
|
||||
export type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}-props.type'
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Params = object
|
||||
|
||||
/**
|
||||
* Атрибуты корневого элемента модуля {{name.pascalCase}}.
|
||||
*/
|
||||
type RootAttrs = ComponentPropsWithoutRef<'div'>
|
||||
|
||||
/**
|
||||
* Props модуля {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Props = RootAttrs & {{name.pascalCase}}Params
|
||||
@@ -0,0 +1,20 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}Props } from './types/{{name.kebabCase}}-props.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение компонента {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}} = (props: {{name.pascalCase}}Props) => {
|
||||
const { children, className, ...rootAttrs } = props
|
||||
|
||||
return (
|
||||
<div {...rootAttrs} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { {{name.pascalCase}}PageEntry } from './{{name.kebabCase}}.entry'
|
||||
@@ -0,0 +1,23 @@
|
||||
import { ComponentRenderer } from 'infra/cms/component-renderer'
|
||||
import { digitalPlatformApi } from '@biocadless/digital-platform-api'
|
||||
import type { CmsPageEntryProps } from 'infra/cms/cms-page-entry-registry'
|
||||
|
||||
/**
|
||||
* Входная точка CMS-страницы {{name.pascalCase}}.
|
||||
*
|
||||
* Используется для:
|
||||
* - загрузки дерева CMS-компонентов страницы
|
||||
* - подключения страницы к dynamic CMS routing
|
||||
*/
|
||||
export const {{name.pascalCase}}PageEntry = async (props: CmsPageEntryProps) => {
|
||||
const { pageId } = props
|
||||
const components = await digitalPlatformApi.componentInstances.listV1({ pageId })
|
||||
|
||||
return (
|
||||
<>
|
||||
{components.map((component) => (
|
||||
<ComponentRenderer key={component.id} data={component} />
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { {{name.pascalCase}}Screen } from './{{name.kebabCase}}.screen'
|
||||
export type { {{name.pascalCase}}ScreenProps } from './types/{{name.kebabCase}}-screen-props.type'
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры экрана {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}ScreenParams = object
|
||||
|
||||
/**
|
||||
* Атрибуты корневого элемента экрана {{name.pascalCase}}.
|
||||
*/
|
||||
type RootAttrs = ComponentPropsWithoutRef<'main'>
|
||||
|
||||
/**
|
||||
* Props экрана {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}ScreenProps = RootAttrs & {{name.pascalCase}}ScreenParams
|
||||
@@ -0,0 +1,20 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}ScreenProps } from './types/{{name.kebabCase}}-screen-props.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение экрана {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Screen = (props: {{name.pascalCase}}ScreenProps) => {
|
||||
const { children, className, ...rootAttrs } = props
|
||||
|
||||
return (
|
||||
<main {...rootAttrs} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</main>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { create } from 'zustand'
|
||||
import type { {{name.pascalCase}}Store } from './{{name.kebabCase}}.type'
|
||||
|
||||
/**
|
||||
* Стор {{name.pascalCase}}.
|
||||
*/
|
||||
export const use{{name.pascalCase}}Store = create<{{name.pascalCase}}Store>()(() => ({}))
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Состояние {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}State = object
|
||||
|
||||
/**
|
||||
* Действия {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}Actions = object
|
||||
|
||||
/**
|
||||
* Стор {{name.pascalCase}} с состоянием и действиями.
|
||||
*/
|
||||
export type {{name.pascalCase}}Store = {{name.pascalCase}}State & {{name.pascalCase}}Actions
|
||||
@@ -0,0 +1,2 @@
|
||||
export { {{name.pascalCase}}Widget } from './{{name.kebabCase}}.widget'
|
||||
export type { {{name.pascalCase}}WidgetProps } from './types/{{name.kebabCase}}-widget-props.type'
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
display: block;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import type { ComponentPropsWithoutRef } from 'react'
|
||||
|
||||
/**
|
||||
* Параметры виджета {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}WidgetParams = object
|
||||
|
||||
/**
|
||||
* Атрибуты корневого элемента виджета {{name.pascalCase}}.
|
||||
*/
|
||||
type RootAttrs = ComponentPropsWithoutRef<'div'>
|
||||
|
||||
/**
|
||||
* Props виджета {{name.pascalCase}}.
|
||||
*/
|
||||
export type {{name.pascalCase}}WidgetProps = RootAttrs & {{name.pascalCase}}WidgetParams
|
||||
@@ -0,0 +1,20 @@
|
||||
import cl from 'clsx'
|
||||
import type { {{name.pascalCase}}WidgetProps } from './types/{{name.kebabCase}}-widget-props.type'
|
||||
import styles from './styles/{{name.kebabCase}}.module.css'
|
||||
|
||||
/**
|
||||
* <Назначение виджета {{name.pascalCase}} в 1 строке>.
|
||||
*
|
||||
* Используется для:
|
||||
* - <сценарий 1>
|
||||
* - <сценарий 2>
|
||||
*/
|
||||
export const {{name.pascalCase}}Widget = (props: {{name.pascalCase}}WidgetProps) => {
|
||||
const { children, className, ...rootAttrs } = props
|
||||
|
||||
return (
|
||||
<div {...rootAttrs} className={cl(styles.root, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Шаблоны Next.js App Router + SLM
|
||||
|
||||
Пример содержит рабочую папку `.templates/` для генерации повторяемых SLM-сущностей через `@gromlab/create` или VS Code Template File Generator.
|
||||
|
||||
Перед использованием скопируй `.templates/` в нужную область проекта: приложение, пакет или самостоятельный участок монорепозитория.
|
||||
|
||||
Запускай CLI из каталога области, где лежит `.templates/`:
|
||||
|
||||
```bash
|
||||
npx @gromlab/create <template> <name> [path]
|
||||
```
|
||||
|
||||
Позиционный `[path]` - путь вывода относительно текущей рабочей директории, а не путь к шаблонам.
|
||||
|
||||
## Шаблоны
|
||||
|
||||
| Шаблон | Для чего | Куда генерировать |
|
||||
|---|---|---|
|
||||
| `component` | Презентационный компонент внутри `ui/` родительского модуля. Не владеет данными, сценариями и вложенной архитектурой. | `*/ui` |
|
||||
| `module` | Обычный SLM-модуль с корневым `.tsx`, стилями, типами и публичным API. Подходит для `parts/` и UI-модулей слоя `src/ui`. | `*/parts`, `src/ui` |
|
||||
| `screen` | Корневой screen-модуль страницы. | `src/compositions/screens` |
|
||||
| `layout` | Layout-модуль композиции страницы. | `src/compositions/layouts` |
|
||||
| `widget` | Переиспользуемый composition widget, не привязанный к одной странице. | `src/compositions/widgets` |
|
||||
| `business` | Business-домен без runtime-зависимостей на другие домены. | `src/business` |
|
||||
| `business-with-deps` | Business-домен с runtime-зависимостями, которые передаются через аргумент фабрики. | `src/business` |
|
||||
| `store` | Zustand store внутри сегмента `stores/` конкретного модуля. | `*/stores` |
|
||||
| `page-entry` | CMS-specific entry point для страницы, которая рендерится через dynamic routing. | `src/compositions/page-entries` |
|
||||
| `business-composition` | Provider, hook и типы для передачи собранного business API внутри composition module. | внутри `src/compositions/**/<module>` |
|
||||
|
||||
## Компонент И Модуль
|
||||
|
||||
`component` создаёт презентационную единицу внутри сегмента `ui/`. Такой компонент работает только в границе родительского модуля и не импортирует проектный код за его пределами.
|
||||
|
||||
`module` создаёт архитектурную единицу SLM. У модуля есть публичный API через `index.ts`; он может иметь свои `hooks/`, `stores/`, `services/`, `parts/`, `ui/`, `types/` и `styles/` по мере необходимости.
|
||||
|
||||
Если UI-сущности нужны данные, сценарная логика, вложенные модули или собственные зависимости, используй `module`, а не `component`.
|
||||
|
||||
## CMS Page Entry
|
||||
|
||||
Шаблон `page-entry` намеренно содержит CMS-specific импорты из реального проекта:
|
||||
|
||||
- `infra/cms/component-renderer`;
|
||||
- `@biocadless/digital-platform-api`;
|
||||
- `infra/cms/cms-page-entry-registry`.
|
||||
|
||||
Перед копированием этого шаблона в другой проект адаптируй импорты, тип props и способ загрузки дерева компонентов под локальную CMS-интеграцию.
|
||||
|
||||
## Примеры CLI
|
||||
|
||||
```bash
|
||||
npx @gromlab/create screen cabinet src/compositions/screens
|
||||
npx @gromlab/create module hero-section src/compositions/screens/home/parts
|
||||
npx @gromlab/create component header-nav src/compositions/layouts/default-layout/ui
|
||||
npx @gromlab/create widget clinic-map src/compositions/widgets
|
||||
npx @gromlab/create business auth src/business
|
||||
npx @gromlab/create business-with-deps user src/business
|
||||
npx @gromlab/create store auth src/business/auth/stores
|
||||
npx @gromlab/create page-entry knv src/compositions/page-entries
|
||||
npx @gromlab/create business-composition knv-page src/compositions/page-entries/knv
|
||||
```
|
||||
Reference in New Issue
Block a user