feat(foundry): add managed data consumers and agent settings
This commit is contained in:
@@ -1085,6 +1085,135 @@ textarea.nodedc-field__control {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-menu__trigger {
|
||||
display: inline-flex;
|
||||
min-height: var(--nodedc-header-control-height);
|
||||
align-items: center;
|
||||
gap: 0.35rem;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-circle);
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 0;
|
||||
font: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-menu__trigger:hover,
|
||||
.nodedc-user-profile-menu__trigger:focus-visible,
|
||||
.nodedc-user-profile-menu__trigger[aria-expanded="true"] {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-user-profile-menu__trigger-label {
|
||||
max-width: 10rem;
|
||||
overflow: hidden;
|
||||
padding-inline: 0.95rem 0.35rem;
|
||||
font-size: var(--nodedc-font-size-sm);
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-dropdown-surface.nodedc-user-profile-menu {
|
||||
padding: 0.65rem;
|
||||
border-radius: 1.25rem;
|
||||
overflow: hidden auto;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card {
|
||||
display: grid;
|
||||
gap: 0.32rem;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__cover {
|
||||
position: relative;
|
||||
isolation: isolate;
|
||||
display: grid;
|
||||
min-height: 8rem;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
gap: 0.1rem;
|
||||
overflow: hidden;
|
||||
border-radius: 0.9rem;
|
||||
background:
|
||||
linear-gradient(180deg, rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.46)),
|
||||
radial-gradient(circle at 70% 15%, rgb(var(--nodedc-accent-rgb) / 0.34), transparent 38%),
|
||||
var(--nodedc-panel-bg);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__cover .nodedc-header__avatar {
|
||||
width: 3.35rem;
|
||||
height: 3.35rem;
|
||||
flex-basis: 3.35rem;
|
||||
box-shadow: 0 0 0 1px rgb(255 255 255 / 0.18), 0 0.45rem 1.6rem rgb(0 0 0 / 0.3);
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__cover strong,
|
||||
.nodedc-user-profile-card__cover > span:not(.nodedc-header__avatar) {
|
||||
display: block;
|
||||
max-width: 86%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__cover strong {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__cover > span:not(.nodedc-header__avatar) {
|
||||
color: rgb(255 255 255 / 0.78);
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__actions {
|
||||
display: grid;
|
||||
gap: 0.12rem;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__action {
|
||||
display: grid;
|
||||
min-height: 2.65rem;
|
||||
grid-template-columns: 1rem minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 0.58rem;
|
||||
border: 0;
|
||||
border-radius: 0.85rem;
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 0 0.72rem;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
font: inherit;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__action:hover:not(:disabled),
|
||||
.nodedc-user-profile-card__action:focus-visible {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__action[data-tone="danger"]:hover:not(:disabled) {
|
||||
background: rgb(var(--nodedc-danger-rgb) / 0.12);
|
||||
color: rgb(var(--nodedc-danger-rgb));
|
||||
}
|
||||
|
||||
.nodedc-user-profile-card__action:disabled {
|
||||
cursor: default;
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.nodedc-header__workspace {
|
||||
display: inline-grid;
|
||||
width: var(--nodedc-header-row-height);
|
||||
@@ -1991,6 +2120,120 @@ textarea.nodedc-field__control {
|
||||
width: min(58rem, calc(100vw - 2rem));
|
||||
}
|
||||
|
||||
.nodedc-window.nodedc-feature-settings-window {
|
||||
width: min(78rem, calc(100vw - 2rem));
|
||||
height: min(48rem, calc(100vh - 2rem));
|
||||
}
|
||||
|
||||
.nodedc-feature-settings-window .nodedc-window__body {
|
||||
padding: 0.35rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings {
|
||||
display: grid;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
grid-template-columns: minmax(14rem, 16rem) minmax(0, 1fr);
|
||||
gap: 1.2rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__navigation {
|
||||
display: grid;
|
||||
min-height: 0;
|
||||
align-content: start;
|
||||
gap: 1.1rem;
|
||||
border-radius: var(--nodedc-radius-panel);
|
||||
background: var(--nodedc-nested-bg);
|
||||
padding: 0.9rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__identity {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
min-height: 4.25rem;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
border-radius: var(--nodedc-radius-control);
|
||||
background: var(--nodedc-panel-bg);
|
||||
padding: 0.7rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__identity > span {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.15rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__identity strong,
|
||||
.nodedc-feature-settings__identity small {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__identity strong {
|
||||
font-size: var(--nodedc-font-size-sm);
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__identity small {
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: var(--nodedc-font-size-xs);
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__navigation nav,
|
||||
.nodedc-feature-settings__navigation-entry {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__group {
|
||||
padding: 0.35rem 0.65rem 0.15rem;
|
||||
color: var(--nodedc-text-muted);
|
||||
font-size: 0.66rem;
|
||||
font-weight: 850;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__section {
|
||||
display: grid;
|
||||
min-height: 2.8rem;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
border: 0;
|
||||
border-radius: var(--nodedc-radius-control-compact);
|
||||
background: transparent;
|
||||
color: var(--nodedc-text-secondary);
|
||||
padding: 0 0.75rem;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
font-size: var(--nodedc-font-size-sm);
|
||||
font-weight: 750;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__section:hover:not(:disabled),
|
||||
.nodedc-feature-settings__section[data-active="true"] {
|
||||
background: var(--nodedc-glass-control-hover);
|
||||
color: var(--nodedc-text-primary);
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__section[data-active="true"] {
|
||||
background: rgb(var(--nodedc-accent-rgb) / 0.14);
|
||||
color: var(--nodedc-accent);
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__content {
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
border-radius: var(--nodedc-radius-panel);
|
||||
background: var(--nodedc-panel-bg);
|
||||
padding: clamp(1.1rem, 2.4vw, 2rem);
|
||||
}
|
||||
|
||||
.nodedc-window[data-placement="end"] {
|
||||
width: min(var(--nodedc-inspector-width), calc(100vw - 3rem));
|
||||
max-height: calc(100vh - 7.25rem);
|
||||
@@ -2947,6 +3190,24 @@ textarea.nodedc-field__control {
|
||||
padding-inline: 0.75rem;
|
||||
}
|
||||
|
||||
.nodedc-window.nodedc-feature-settings-window {
|
||||
width: calc(100vw - 1rem);
|
||||
height: calc(100vh - 1rem);
|
||||
}
|
||||
|
||||
.nodedc-feature-settings {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.nodedc-feature-settings__navigation {
|
||||
max-height: 14rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.nodedc-user-profile-menu__trigger-label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nodedc-share-access__member {
|
||||
grid-template-columns: 2.25rem minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { HeaderAvatar } from "./AppHeader.js";
|
||||
import { Icon, type IconName } from "./Icon.js";
|
||||
import { Window } from "./Window.js";
|
||||
|
||||
export interface FeatureSettingsSection<T extends string = string> {
|
||||
id: T;
|
||||
label: ReactNode;
|
||||
group?: string;
|
||||
icon?: IconName;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface FeatureSettingsWindowProps<T extends string = string> {
|
||||
open: boolean;
|
||||
title: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
identity: {
|
||||
title: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
avatarLabel: string;
|
||||
avatarUrl?: string;
|
||||
};
|
||||
sections: readonly FeatureSettingsSection<T>[];
|
||||
activeSection: T;
|
||||
onSectionChange: (section: T) => void;
|
||||
onClose: () => void;
|
||||
children: ReactNode;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
export function FeatureSettingsWindow<T extends string>({
|
||||
open,
|
||||
title,
|
||||
subtitle,
|
||||
identity,
|
||||
sections,
|
||||
activeSection,
|
||||
onSectionChange,
|
||||
onClose,
|
||||
children,
|
||||
footer,
|
||||
}: FeatureSettingsWindowProps<T>) {
|
||||
return (
|
||||
<Window
|
||||
open={open}
|
||||
title={title}
|
||||
subtitle={subtitle}
|
||||
size="lg"
|
||||
className="nodedc-feature-settings-window"
|
||||
footer={footer}
|
||||
closeOnBackdrop={false}
|
||||
onClose={onClose}
|
||||
>
|
||||
<div className="nodedc-feature-settings">
|
||||
<aside className="nodedc-feature-settings__navigation" aria-label="Разделы настроек">
|
||||
<div className="nodedc-feature-settings__identity">
|
||||
<HeaderAvatar label={identity.avatarLabel} imageUrl={identity.avatarUrl} />
|
||||
<span><strong>{identity.title}</strong>{identity.subtitle ? <small>{identity.subtitle}</small> : null}</span>
|
||||
</div>
|
||||
<nav>
|
||||
{sections.map((section, index) => {
|
||||
const showGroup = section.group && section.group !== sections[index - 1]?.group;
|
||||
return (
|
||||
<div key={section.id} className="nodedc-feature-settings__navigation-entry">
|
||||
{showGroup ? <span className="nodedc-feature-settings__group">{section.group}</span> : null}
|
||||
<button
|
||||
type="button"
|
||||
className="nodedc-feature-settings__section"
|
||||
data-active={section.id === activeSection ? "true" : undefined}
|
||||
disabled={section.disabled}
|
||||
onClick={() => onSectionChange(section.id)}
|
||||
>
|
||||
{section.icon ? <Icon name={section.icon} size={16} /> : null}
|
||||
<span>{section.label}</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</nav>
|
||||
</aside>
|
||||
<section className="nodedc-feature-settings__content">{children}</section>
|
||||
</div>
|
||||
</Window>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { Dropdown } from "./Dropdown.js";
|
||||
import { HeaderAvatar } from "./AppHeader.js";
|
||||
import { Icon, type IconName } from "./Icon.js";
|
||||
|
||||
export interface UserProfileMenuAction {
|
||||
id: string;
|
||||
label: ReactNode;
|
||||
icon?: IconName;
|
||||
href?: string;
|
||||
disabled?: boolean;
|
||||
tone?: "default" | "danger";
|
||||
onSelect?: () => void;
|
||||
}
|
||||
|
||||
export interface UserProfileMenuProps {
|
||||
displayName: string;
|
||||
subtitle?: string;
|
||||
avatarUrl?: string;
|
||||
coverImageUrl?: string;
|
||||
triggerLabel?: ReactNode;
|
||||
actions: readonly UserProfileMenuAction[];
|
||||
}
|
||||
|
||||
export function UserProfileMenu({
|
||||
displayName,
|
||||
subtitle,
|
||||
avatarUrl,
|
||||
coverImageUrl,
|
||||
triggerLabel,
|
||||
actions,
|
||||
}: UserProfileMenuProps) {
|
||||
const label = displayName || "NODE.DC";
|
||||
return (
|
||||
<Dropdown
|
||||
placement="bottom-end"
|
||||
width={296}
|
||||
offset={10}
|
||||
surfaceClassName="nodedc-user-profile-menu"
|
||||
trigger={({ open, toggle, setTriggerRef, surfaceId }) => (
|
||||
<button
|
||||
ref={setTriggerRef}
|
||||
type="button"
|
||||
className="nodedc-user-profile-menu__trigger"
|
||||
title={label}
|
||||
aria-label={`Меню пользователя ${label}`}
|
||||
aria-expanded={open}
|
||||
aria-controls={surfaceId}
|
||||
onClick={toggle}
|
||||
>
|
||||
{triggerLabel === null ? null : <span className="nodedc-user-profile-menu__trigger-label">{triggerLabel ?? label}</span>}
|
||||
<HeaderAvatar label={label} imageUrl={avatarUrl} />
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{({ close }) => (
|
||||
<div className="nodedc-user-profile-card">
|
||||
<div
|
||||
className="nodedc-user-profile-card__cover"
|
||||
style={coverImageUrl ? { backgroundImage: `linear-gradient(180deg, rgb(0 0 0 / 0.08), rgb(0 0 0 / 0.52)), url(${JSON.stringify(coverImageUrl)})` } : undefined}
|
||||
>
|
||||
<HeaderAvatar label={label} imageUrl={avatarUrl} />
|
||||
<strong>{label}</strong>
|
||||
{subtitle ? <span>{subtitle}</span> : null}
|
||||
</div>
|
||||
<div className="nodedc-user-profile-card__actions">
|
||||
{actions.map((action) => {
|
||||
const content = (
|
||||
<>
|
||||
{action.icon ? <Icon name={action.icon} size={16} /> : <span />}
|
||||
<span>{action.label}</span>
|
||||
</>
|
||||
);
|
||||
if (action.href && !action.disabled) {
|
||||
return (
|
||||
<a
|
||||
key={action.id}
|
||||
className="nodedc-user-profile-card__action"
|
||||
data-tone={action.tone === "danger" ? "danger" : undefined}
|
||||
href={action.href}
|
||||
onClick={() => close()}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
key={action.id}
|
||||
type="button"
|
||||
className="nodedc-user-profile-card__action"
|
||||
data-tone={action.tone === "danger" ? "danger" : undefined}
|
||||
disabled={action.disabled}
|
||||
onClick={() => {
|
||||
action.onSelect?.();
|
||||
close();
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export * from "./ConfirmationModal.js";
|
||||
export * from "./Dropdown.js";
|
||||
export * from "./DragDrop.js";
|
||||
export * from "./Field.js";
|
||||
export * from "./FeatureSettingsWindow.js";
|
||||
export * from "./Glass.js";
|
||||
export * from "./Inspector.js";
|
||||
export * from "./Icon.js";
|
||||
@@ -19,5 +20,6 @@ export * from "./StatusBadge.js";
|
||||
export * from "./Settings.js";
|
||||
export * from "./SharingModals.js";
|
||||
export * from "./Toolbar.js";
|
||||
export * from "./UserProfileMenu.js";
|
||||
export * from "./Window.js";
|
||||
export * from "./WorkspaceWindow.js";
|
||||
|
||||
Reference in New Issue
Block a user