feat: AppLayout (оболочка приложения) — 0.8.0
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@tnsu/ui-kit",
|
"name": "@tnsu/ui-kit",
|
||||||
"version": "0.7.0",
|
"version": "0.8.0",
|
||||||
"description": "TNSU shared UI components for SPFx web parts",
|
"description": "TNSU shared UI components for SPFx web parts",
|
||||||
"author": "Timur Gorodetskiy",
|
"author": "Timur Gorodetskiy",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
@import '../tokens/tokens';
|
||||||
|
|
||||||
|
.layout {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch; // боковая панель тянется на высоту контента
|
||||||
|
width: 100%;
|
||||||
|
min-height: 78vh; // «присутствие» рейла даже когда контента мало
|
||||||
|
font-family: $font-sans;
|
||||||
|
}
|
||||||
|
|
||||||
|
.side {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 0; // чтобы контент не распирал колонку
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: $sp-5 $sp-6 $sp-8;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Бургер: виден только на мобилке (над контентом).
|
||||||
|
.mobileBar {
|
||||||
|
display: none;
|
||||||
|
margin-bottom: $sp-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
// < 768px: боковая панель превращается в drawer (position:fixed внутри Sidebar) —
|
||||||
|
// колонка .side схлопывается сама, контент на всю ширину. Показываем бургер.
|
||||||
|
@include below($bp-md) {
|
||||||
|
.layout {
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobileBar {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding: $sp-4;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import type { Meta, StoryObj } from '@storybook/react-vite';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { AppLayout } from './AppLayout';
|
||||||
|
import { Sidebar, ISidebarItem } from '../Sidebar/Sidebar';
|
||||||
|
import { SidebarToggle } from '../Sidebar/SidebarToggle';
|
||||||
|
|
||||||
|
const ITEMS: ISidebarItem[] = [
|
||||||
|
{ id: 'ct', title: 'СТ ОМТС', icon: 'clipboard', count: 1 },
|
||||||
|
{ id: 'oid', title: 'СТ ОИД', icon: 'chart' },
|
||||||
|
{ id: 'kps', title: 'СТ КПС', icon: 'building' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const meta: Meta<typeof AppLayout> = {
|
||||||
|
title: 'Раскладка/AppLayout',
|
||||||
|
component: AppLayout,
|
||||||
|
tags: ['autodocs']
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
|
||||||
|
// Оболочка: сайдбар слева + контент справа. Сузь окно < 768px — сайдбар уйдёт в
|
||||||
|
// drawer, появится бургер, контент займёт всю ширину.
|
||||||
|
export const SidebarWithContent: StoryObj<typeof AppLayout> = {
|
||||||
|
render: () => {
|
||||||
|
const [active, setActive] = useState<string | number>('ct');
|
||||||
|
const [collapsed, setCollapsed] = useState(false);
|
||||||
|
const [mobileOpen, setMobileOpen] = useState(false);
|
||||||
|
return (
|
||||||
|
<AppLayout
|
||||||
|
sidebar={
|
||||||
|
<Sidebar
|
||||||
|
items={ITEMS}
|
||||||
|
activeId={active}
|
||||||
|
onChange={setActive}
|
||||||
|
brand="Договора"
|
||||||
|
collapsed={collapsed}
|
||||||
|
onToggleCollapsed={() => setCollapsed((c) => !c)}
|
||||||
|
mobileOpen={mobileOpen}
|
||||||
|
onMobileClose={() => setMobileOpen(false)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
mobileToggle={<SidebarToggle onClick={() => setMobileOpen(true)} />}
|
||||||
|
>
|
||||||
|
<h2 style={{ margin: 0, fontFamily: "'Lora', Georgia, serif", color: '#0F2440' }}>Контентная область</h2>
|
||||||
|
<p style={{ color: '#6B7280' }}>Здесь — содержимое активного раздела (реестр, форма и т.д.).</p>
|
||||||
|
</AppLayout>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import styles from './AppLayout.module.scss';
|
||||||
|
|
||||||
|
export interface IAppLayoutProps {
|
||||||
|
/** Боковая панель — обычно <Sidebar/>. */
|
||||||
|
sidebar: React.ReactNode;
|
||||||
|
/**
|
||||||
|
* Кнопка-бургер для мобильного drawer — обычно <SidebarToggle/>. Видна только
|
||||||
|
* на мобилке (< 768px), над контентом. Необязательна.
|
||||||
|
*/
|
||||||
|
mobileToggle?: React.ReactNode;
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Оболочка приложения — один из видов раскладки: боковая панель слева + контентная
|
||||||
|
* область справа. Переиспользуемый app-shell.
|
||||||
|
* Адаптивно: на < 768px боковая панель уходит в выезжающий drawer (за это отвечает
|
||||||
|
* сам Sidebar), контент занимает всю ширину, сверху показывается бургер (mobileToggle).
|
||||||
|
*/
|
||||||
|
export const AppLayout: React.FC<IAppLayoutProps> = ({ sidebar, mobileToggle, children }) => (
|
||||||
|
<div className={styles.layout}>
|
||||||
|
<div className={styles.side}>{sidebar}</div>
|
||||||
|
<main className={styles.main}>
|
||||||
|
{mobileToggle && <div className={styles.mobileBar}>{mobileToggle}</div>}
|
||||||
|
{children}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
@@ -49,6 +49,10 @@ export { ConfirmHost } from './ConfirmDialog/ConfirmDialog';
|
|||||||
export { confirmModal } from './ConfirmDialog/confirmModal';
|
export { confirmModal } from './ConfirmDialog/confirmModal';
|
||||||
export type { IConfirmOptions } from './ConfirmDialog/confirmModal';
|
export type { IConfirmOptions } from './ConfirmDialog/confirmModal';
|
||||||
|
|
||||||
|
// --- Раскладка приложения ---
|
||||||
|
export { AppLayout } from './AppLayout/AppLayout';
|
||||||
|
export type { IAppLayoutProps } from './AppLayout/AppLayout';
|
||||||
|
|
||||||
// --- Навигация ---
|
// --- Навигация ---
|
||||||
export { Tabs } from './Tabs/Tabs';
|
export { Tabs } from './Tabs/Tabs';
|
||||||
export type { ITab, ITabsProps } from './Tabs/Tabs';
|
export type { ITab, ITabsProps } from './Tabs/Tabs';
|
||||||
|
|||||||
Reference in New Issue
Block a user