Приложение и дашбордappdashboardsaassettingsformaccount
Settings Section
Блок настроек: заголовок и подсказка рядом с карточкой полей, кнопки «Сохранить/Отмена» и подтверждение.
Живое превью
Profile
This information is visible to your team.
Использование
Скопируйте и вставьте этот код в ваш React-компонент:
tsx
import { SettingsSection } from 'react-component-kit-ui/app';
<SettingsSection title="Notifications" description="Choose what reaches you.">
<SwitchRow name="bookings" label="New bookings" />
</SettingsSection>Ручная установка
Если вы не используете пакет npm, создайте файл и вставьте следующий код: src/components/SettingsSection.tsx + SettingsSection.css:
SettingsSection.tsx
'use client';
import { useEffect, useRef } from 'react';
import type { ReactNode } from 'react';
import './SettingsSection.css';
/** class:list → className */
function cx(...args: unknown[]): string {
const out: string[] = [];
const walk = (v: unknown) => {
if (!v) return;
if (typeof v === 'string' || typeof v === 'number') out.push(String(v));
else if (Array.isArray(v)) v.forEach(walk);
else if (typeof v === 'object') for (const [k, on] of Object.entries(v as Record<string, unknown>)) if (on) out.push(k);
};
args.forEach(walk);
return out.join(' ');
}
/**
* SettingsSection — Settings page block: title and description on the left, fields in a card on
* the right, with an optional footer holding Save / Cancel and a "saved" confirmation.
*
* @param {string} title - Section title. Default 'Profile'.
* @param {string} description - Help text under the title.
* @param {boolean} form - Wrap the fields in a <form> with Save/Cancel. Default true.
* @param {string} saveLabel - Save button label. Default 'Save changes'.
* @param {string} savedText - Confirmation after saving. Default 'Changes saved'.
* @param {string} id - Base id. Default 'settings-section'.
* @param {string} variant - Layout (split|stacked). Default 'split'.
* @param {string} className - Extra class.
*
* Slots: default (fields — SwitchRow, AnimatedInput, ModernSelect…), `footer-start` (extra text).
*
* Accessibility: a labelled section; the form validates natively and announces the saved state
* in a polite live region. Emits `app:settings-save` with the FormData.
*/
export interface SettingsSectionProps {
title?: string;
description?: string;
form?: boolean;
saveLabel?: string;
savedText?: string;
id?: string;
variant?: 'split' | 'stacked';
className?: string;
children?: ReactNode;
footerStart?: ReactNode;
}
export function SettingsSection({
title = 'Profile',
description = 'This information is visible to your team.',
form = true,
saveLabel = 'Save changes',
savedText = 'Changes saved',
id = 'settings-section',
variant = 'split',
className = '',
children,
footerStart
}: SettingsSectionProps) {
const Body = form ? 'form' : 'div';
// Ported from the Astro <script>: runs once after mount (DOM logic kept as in the Astro kit).
const mounted = useRef(false);
useEffect(() => {
if (mounted.current) return;
mounted.current = true;
document.querySelectorAll<HTMLFormElement>('form[data-settings-form]:not([data-bound])').forEach((f) => {
f.dataset.bound = '';
const saved = f.querySelector<HTMLElement>('[data-saved]');
f.addEventListener('submit', (e) => {
e.preventDefault();
if (!f.checkValidity()) {
f.querySelector<HTMLElement>(':invalid')?.focus();
return;
}
if (saved) {
saved.textContent = `✓ ${saved.dataset.text}`;
window.setTimeout(() => { saved.textContent = ''; }, 2600);
}
f.dispatchEvent(new CustomEvent('app:settings-save', { bubbles: true, detail: new FormData(f) }));
});
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<>
<section className={cx(['set', `set--${variant}`, className])} aria-labelledby={`${id}-title`} data-rk-d579d6="">
<div className="set__intro" data-rk-d579d6="">
<h2 className="set__title" id={`${id}-title`} data-rk-d579d6="">{title}</h2>
{description && <p className="set__desc" data-rk-d579d6="">{description}</p>}
</div>
<Body className="set__card" data-settings-form={form ? '' : undefined} noValidate={form ? true : undefined} data-rk-d579d6="">
<div className="set__fields" data-rk-d579d6="">{children}</div>
{form && (
<div className="set__foot" data-rk-d579d6="">
{footerStart}
<p className="set__saved" aria-live="polite" data-saved data-text={savedText} data-rk-d579d6=""></p>
<button type="reset" className="set__btn" data-rk-d579d6="">Cancel</button>
<button type="submit" className="set__btn set__btn--primary" data-rk-d579d6="">{saveLabel}</button>
</div>
)}
</Body>
</section>
</>
);
}
export default SettingsSection;SettingsSection.css
/* SettingsSection — ported from astro-component-kit (app/SettingsSection.astro) */
.set:where([data-rk-d579d6]) { container-type: inline-size; display: grid; grid-template-columns: minmax(0, 0.8fr) minmax(0, 2fr); gap: var(--sp-6, 1.5rem); padding-block: var(--sp-6, 1.5rem); border-bottom: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); }
.set--stacked:where([data-rk-d579d6]) { grid-template-columns: 1fr; }
.set__title:where([data-rk-d579d6]) { margin: 0; font-size: 1rem; font-weight: 700; color: var(--c-text-1, #fff); }
.set__desc:where([data-rk-d579d6]) { margin: 4px 0 0; font-size: 0.85rem; line-height: 1.55; color: var(--c-text-2, #a3a3a3); }
.set__card:where([data-rk-d579d6]) { border-radius: var(--r-lg, 16px); border: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); background: var(--c-bg-1, #0a0a0a); overflow: hidden; }
.set__fields:where([data-rk-d579d6]) { display: grid; gap: var(--sp-4, 1rem); padding: var(--sp-5, 1.25rem); }
.set__foot:where([data-rk-d579d6]) { display: flex; align-items: center; justify-content: flex-end; gap: var(--sp-2, 0.5rem); padding: var(--sp-3, 0.75rem) var(--sp-5, 1.25rem); border-top: 1px solid var(--c-border, rgba(255, 255, 255, 0.08)); background: rgb(var(--ink-rgb, 255 255 255) / 0.02); }
.set__saved:where([data-rk-d579d6]) { margin: 0 auto 0 0; font-size: 0.8rem; font-weight: 600; color: var(--c-success, #34d399); }
.set__btn:where([data-rk-d579d6]) { min-height: 38px; padding: 0 0.9rem; border-radius: 10px; border: 1px solid var(--c-border, rgba(255, 255, 255, 0.12)); background: transparent; color: var(--c-text-1, #fff); font: inherit; font-size: 0.82rem; font-weight: 600; cursor: pointer; }
.set__btn--primary:where([data-rk-d579d6]) { border-color: transparent; background: var(--app-accent, var(--c-primary-light, #d4a017)); color: var(--c-on-primary, #050505); }
.set__btn:focus-visible:where([data-rk-d579d6]) { outline: 2px solid var(--app-accent, var(--c-primary-light, #d4a017)); outline-offset: 2px; }
.set:where([data-rk-d579d6]) .anim-input, .set:where([data-rk-d579d6]) .custom-select { background: var(--c-bg, #050505); }
@container (max-width: 640px) { .set__intro:where([data-rk-d579d6]), .set__card:where([data-rk-d579d6]) { grid-column: 1 / -1; } }Краткая информация
- Категория
- Приложение и дашборд
- Имя файла
SettingsSection.tsx- Зависимости
- Нет — чистый React + CSS
- Теги
- appdashboardsaassettingsformaccount