@mindlogic-ai/logician-ui 4.0.0-alpha.13 → 4.0.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Button/Button.styles.d.ts.map +1 -1
- package/dist/components/Button/Button.styles.js +18 -12
- package/dist/components/Button/Button.styles.js.map +1 -1
- package/dist/components/Button/Button.styles.mjs +18 -12
- package/dist/components/Button/Button.styles.mjs.map +1 -1
- package/dist/components/Card/Card.styles.d.ts +4 -0
- package/dist/components/Card/Card.styles.d.ts.map +1 -1
- package/dist/components/Card/Card.styles.js +8 -0
- package/dist/components/Card/Card.styles.js.map +1 -1
- package/dist/components/Card/Card.styles.mjs +8 -0
- package/dist/components/Card/Card.styles.mjs.map +1 -1
- package/dist/components/Card/Card.types.d.ts +1 -1
- package/dist/components/Card/Card.types.d.ts.map +1 -1
- package/dist/components/Form/FieldError/FieldError.d.ts +15 -0
- package/dist/components/Form/FieldError/FieldError.d.ts.map +1 -0
- package/dist/components/Form/FieldError/FieldError.js +22 -0
- package/dist/components/Form/FieldError/FieldError.js.map +1 -0
- package/dist/components/Form/FieldError/FieldError.mjs +20 -0
- package/dist/components/Form/FieldError/FieldError.mjs.map +1 -0
- package/dist/components/Form/FieldError/index.d.ts +3 -0
- package/dist/components/Form/FieldError/index.d.ts.map +1 -0
- package/dist/components/Form/FieldHelp/FieldHelp.d.ts +14 -0
- package/dist/components/Form/FieldHelp/FieldHelp.d.ts.map +1 -0
- package/dist/components/Form/FieldHelp/FieldHelp.js +19 -0
- package/dist/components/Form/FieldHelp/FieldHelp.js.map +1 -0
- package/dist/components/Form/FieldHelp/FieldHelp.mjs +17 -0
- package/dist/components/Form/FieldHelp/FieldHelp.mjs.map +1 -0
- package/dist/components/Form/FieldHelp/index.d.ts +3 -0
- package/dist/components/Form/FieldHelp/index.d.ts.map +1 -0
- package/dist/components/Form/FieldLabel/FieldLabel.d.ts +12 -0
- package/dist/components/Form/FieldLabel/FieldLabel.d.ts.map +1 -0
- package/dist/components/Form/FieldLabel/FieldLabel.js +32 -0
- package/dist/components/Form/FieldLabel/FieldLabel.js.map +1 -0
- package/dist/components/Form/FieldLabel/FieldLabel.mjs +30 -0
- package/dist/components/Form/FieldLabel/FieldLabel.mjs.map +1 -0
- package/dist/components/Form/FieldLabel/FieldLabel.types.d.ts +21 -0
- package/dist/components/Form/FieldLabel/FieldLabel.types.d.ts.map +1 -0
- package/dist/components/Form/FieldLabel/index.d.ts +3 -0
- package/dist/components/Form/FieldLabel/index.d.ts.map +1 -0
- package/dist/components/Form/FieldRow/FieldRow.d.ts +15 -0
- package/dist/components/Form/FieldRow/FieldRow.d.ts.map +1 -0
- package/dist/components/Form/FieldRow/FieldRow.js +27 -0
- package/dist/components/Form/FieldRow/FieldRow.js.map +1 -0
- package/dist/components/Form/FieldRow/FieldRow.mjs +25 -0
- package/dist/components/Form/FieldRow/FieldRow.mjs.map +1 -0
- package/dist/components/Form/FieldRow/FieldRow.types.d.ts +27 -0
- package/dist/components/Form/FieldRow/FieldRow.types.d.ts.map +1 -0
- package/dist/components/Form/FieldRow/index.d.ts +3 -0
- package/dist/components/Form/FieldRow/index.d.ts.map +1 -0
- package/dist/components/Form/FormFooter/FormFooter.d.ts +11 -0
- package/dist/components/Form/FormFooter/FormFooter.d.ts.map +1 -0
- package/dist/components/Form/FormFooter/FormFooter.js +20 -0
- package/dist/components/Form/FormFooter/FormFooter.js.map +1 -0
- package/dist/components/Form/FormFooter/FormFooter.mjs +18 -0
- package/dist/components/Form/FormFooter/FormFooter.mjs.map +1 -0
- package/dist/components/Form/FormFooter/FormFooter.types.d.ts +23 -0
- package/dist/components/Form/FormFooter/FormFooter.types.d.ts.map +1 -0
- package/dist/components/Form/FormFooter/index.d.ts +3 -0
- package/dist/components/Form/FormFooter/index.d.ts.map +1 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.d.ts +19 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.d.ts.map +1 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.js +42 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.js.map +1 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.mjs +40 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.mjs.map +1 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.types.d.ts +49 -0
- package/dist/components/Form/FormModalFooter/FormModalFooter.types.d.ts.map +1 -0
- package/dist/components/Form/FormModalFooter/index.d.ts +3 -0
- package/dist/components/Form/FormModalFooter/index.d.ts.map +1 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.d.ts +14 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.d.ts.map +1 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.js +18 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.js.map +1 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.mjs +16 -0
- package/dist/components/Form/FormScrollArea/FormScrollArea.mjs.map +1 -0
- package/dist/components/Form/FormScrollArea/index.d.ts +2 -0
- package/dist/components/Form/FormScrollArea/index.d.ts.map +1 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.d.ts +10 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.d.ts.map +1 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.js +43 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.js.map +1 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.mjs +41 -0
- package/dist/components/Form/FormSection/CollapsibleFormSection.mjs.map +1 -0
- package/dist/components/Form/FormSection/FlatFormSection.d.ts +6 -0
- package/dist/components/Form/FormSection/FlatFormSection.d.ts.map +1 -0
- package/dist/components/Form/FormSection/FlatFormSection.js +12 -0
- package/dist/components/Form/FormSection/FlatFormSection.js.map +1 -0
- package/dist/components/Form/FormSection/FlatFormSection.mjs +10 -0
- package/dist/components/Form/FormSection/FlatFormSection.mjs.map +1 -0
- package/dist/components/Form/FormSection/FormSection.d.ts +13 -0
- package/dist/components/Form/FormSection/FormSection.d.ts.map +1 -0
- package/dist/components/Form/FormSection/FormSection.js +21 -0
- package/dist/components/Form/FormSection/FormSection.js.map +1 -0
- package/dist/components/Form/FormSection/FormSection.mjs +19 -0
- package/dist/components/Form/FormSection/FormSection.mjs.map +1 -0
- package/dist/components/Form/FormSection/FormSection.types.d.ts +34 -0
- package/dist/components/Form/FormSection/FormSection.types.d.ts.map +1 -0
- package/dist/components/Form/FormSection/index.d.ts +3 -0
- package/dist/components/Form/FormSection/index.d.ts.map +1 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.d.ts +13 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.d.ts.map +1 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.js +30 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.js.map +1 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.mjs +28 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.mjs.map +1 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.types.d.ts +17 -0
- package/dist/components/Form/FormSectionHeader/FormSectionHeader.types.d.ts.map +1 -0
- package/dist/components/Form/FormSectionHeader/index.d.ts +3 -0
- package/dist/components/Form/FormSectionHeader/index.d.ts.map +1 -0
- package/dist/components/Form/index.d.ts +18 -0
- package/dist/components/Form/index.d.ts.map +1 -0
- package/dist/components/Tag/Tag.styles.js +5 -5
- package/dist/components/Tag/Tag.styles.js.map +1 -1
- package/dist/components/Tag/Tag.styles.mjs +5 -5
- package/dist/components/Tag/Tag.styles.mjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +9 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/components/Button/Button.styles.ts +18 -12
- package/src/components/Card/Card.stories.tsx +7 -0
- package/src/components/Card/Card.styles.ts +8 -0
- package/src/components/Card/Card.types.ts +1 -1
- package/src/components/Form/FieldError/FieldError.test.tsx +33 -0
- package/src/components/Form/FieldError/FieldError.tsx +20 -0
- package/src/components/Form/FieldError/index.ts +2 -0
- package/src/components/Form/FieldHelp/FieldHelp.tsx +18 -0
- package/src/components/Form/FieldHelp/index.ts +2 -0
- package/src/components/Form/FieldLabel/FieldLabel.tsx +46 -0
- package/src/components/Form/FieldLabel/FieldLabel.types.ts +21 -0
- package/src/components/Form/FieldLabel/index.ts +2 -0
- package/src/components/Form/FieldRow/FieldRow.stories.tsx +85 -0
- package/src/components/Form/FieldRow/FieldRow.test.tsx +89 -0
- package/src/components/Form/FieldRow/FieldRow.tsx +46 -0
- package/src/components/Form/FieldRow/FieldRow.types.ts +32 -0
- package/src/components/Form/FieldRow/index.ts +2 -0
- package/src/components/Form/FormFooter/FormFooter.stories.tsx +70 -0
- package/src/components/Form/FormFooter/FormFooter.test.tsx +67 -0
- package/src/components/Form/FormFooter/FormFooter.tsx +46 -0
- package/src/components/Form/FormFooter/FormFooter.types.ts +23 -0
- package/src/components/Form/FormFooter/index.ts +2 -0
- package/src/components/Form/FormModalFooter/FormModalFooter.stories.tsx +64 -0
- package/src/components/Form/FormModalFooter/FormModalFooter.test.tsx +155 -0
- package/src/components/Form/FormModalFooter/FormModalFooter.tsx +83 -0
- package/src/components/Form/FormModalFooter/FormModalFooter.types.ts +53 -0
- package/src/components/Form/FormModalFooter/index.ts +2 -0
- package/src/components/Form/FormScrollArea/FormScrollArea.tsx +27 -0
- package/src/components/Form/FormScrollArea/index.ts +1 -0
- package/src/components/Form/FormSection/CollapsibleFormSection.tsx +35 -0
- package/src/components/Form/FormSection/FlatFormSection.tsx +41 -0
- package/src/components/Form/FormSection/FormSection.stories.tsx +115 -0
- package/src/components/Form/FormSection/FormSection.test.tsx +89 -0
- package/src/components/Form/FormSection/FormSection.tsx +33 -0
- package/src/components/Form/FormSection/FormSection.types.ts +34 -0
- package/src/components/Form/FormSection/index.ts +2 -0
- package/src/components/Form/FormSectionHeader/FormSectionHeader.stories.tsx +53 -0
- package/src/components/Form/FormSectionHeader/FormSectionHeader.test.tsx +74 -0
- package/src/components/Form/FormSectionHeader/FormSectionHeader.tsx +48 -0
- package/src/components/Form/FormSectionHeader/FormSectionHeader.types.ts +17 -0
- package/src/components/Form/FormSectionHeader/index.ts +2 -0
- package/src/components/Form/index.ts +17 -0
- package/src/components/Tag/Tag.styles.ts +5 -5
- package/src/index.ts +1 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { FormSection } from './FormSection';
|
|
6
|
+
|
|
7
|
+
// Stub the layout chrome and the collapsible delegate so the test asserts the
|
|
8
|
+
// flat-vs-collapsible branching and prop forwarding, not logician internals.
|
|
9
|
+
vi.mock('@chakra-ui/react', () => ({
|
|
10
|
+
Box: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
|
11
|
+
Flex: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
|
12
|
+
VStack: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@/components/Typography', () => ({
|
|
16
|
+
H5: ({ children }: { children?: ReactNode }) => <h5>{children}</h5>,
|
|
17
|
+
Subtext: ({ children }: { children?: ReactNode }) => <span>{children}</span>,
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
vi.mock('@/components/Workflow', () => ({
|
|
21
|
+
CollapsibleSection: ({
|
|
22
|
+
label,
|
|
23
|
+
hasError,
|
|
24
|
+
children,
|
|
25
|
+
}: {
|
|
26
|
+
label: string;
|
|
27
|
+
hasError?: boolean;
|
|
28
|
+
children?: ReactNode;
|
|
29
|
+
}) => (
|
|
30
|
+
<div
|
|
31
|
+
data-testid="collapsible"
|
|
32
|
+
data-label={label}
|
|
33
|
+
data-haserror={String(Boolean(hasError))}
|
|
34
|
+
>
|
|
35
|
+
{children}
|
|
36
|
+
</div>
|
|
37
|
+
),
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
describe('FormSection', () => {
|
|
41
|
+
it('renders a flat section with title, description and children by default', () => {
|
|
42
|
+
render(
|
|
43
|
+
<FormSection title="Profile" description="How it introduces itself.">
|
|
44
|
+
<div>field</div>
|
|
45
|
+
</FormSection>
|
|
46
|
+
);
|
|
47
|
+
expect(
|
|
48
|
+
screen.getByRole('heading', { name: 'Profile' })
|
|
49
|
+
).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByText('How it introduces itself.')).toBeInTheDocument();
|
|
51
|
+
expect(screen.getByText('field')).toBeInTheDocument();
|
|
52
|
+
expect(screen.queryByTestId('collapsible')).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('delegates to CollapsibleSection when collapsible, forwarding title and hasError', () => {
|
|
56
|
+
render(
|
|
57
|
+
<FormSection title="Behavior" collapsible hasError>
|
|
58
|
+
<div>field</div>
|
|
59
|
+
</FormSection>
|
|
60
|
+
);
|
|
61
|
+
const collapsible = screen.getByTestId('collapsible');
|
|
62
|
+
expect(collapsible).toHaveAttribute('data-label', 'Behavior');
|
|
63
|
+
expect(collapsible).toHaveAttribute('data-haserror', 'true');
|
|
64
|
+
expect(screen.getByText('field')).toBeInTheDocument();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('stays flat when collapsible is set without a title', () => {
|
|
68
|
+
render(
|
|
69
|
+
<FormSection collapsible>
|
|
70
|
+
<div>field</div>
|
|
71
|
+
</FormSection>
|
|
72
|
+
);
|
|
73
|
+
expect(screen.queryByTestId('collapsible')).toBeNull();
|
|
74
|
+
expect(screen.getByText('field')).toBeInTheDocument();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('renders the inline title adornment in the flat section', () => {
|
|
78
|
+
render(
|
|
79
|
+
<FormSection
|
|
80
|
+
title="Files"
|
|
81
|
+
titleAdornment={<span data-testid="sprinkle">i</span>}
|
|
82
|
+
>
|
|
83
|
+
<div>field</div>
|
|
84
|
+
</FormSection>
|
|
85
|
+
);
|
|
86
|
+
expect(screen.getByRole('heading', { name: 'Files' })).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByTestId('sprinkle')).toBeInTheDocument();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { CollapsibleFormSection } from './CollapsibleFormSection';
|
|
2
|
+
import { FlatFormSection } from './FlatFormSection';
|
|
3
|
+
import { FormSectionProps } from './FormSection.types';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* A titled group of form fields with one consistent rhythm — the shared
|
|
7
|
+
* replacement for the studio panels' ad-hoc `GroupHeader`/`ModeSection` +
|
|
8
|
+
* per-tab `VStack` gaps (8/6/12) and the workflow inspector's per-node section
|
|
9
|
+
* headers. Renders flat by default; set `collapsible` (with a `title`) to render
|
|
10
|
+
* the logician `CollapsibleSection` accordion. `gap` standardizes the field
|
|
11
|
+
* spacing so sections read the same everywhere.
|
|
12
|
+
*
|
|
13
|
+
* Thin dispatcher over the two single-purpose variants so each has one return.
|
|
14
|
+
*/
|
|
15
|
+
export const FormSection = ({
|
|
16
|
+
collapsible = false,
|
|
17
|
+
defaultOpen = true,
|
|
18
|
+
hasError = false,
|
|
19
|
+
...rest
|
|
20
|
+
}: FormSectionProps) =>
|
|
21
|
+
collapsible && rest.title ? (
|
|
22
|
+
<CollapsibleFormSection
|
|
23
|
+
title={rest.title}
|
|
24
|
+
action={rest.action}
|
|
25
|
+
gap={rest.gap}
|
|
26
|
+
defaultOpen={defaultOpen}
|
|
27
|
+
hasError={hasError}
|
|
28
|
+
>
|
|
29
|
+
{rest.children}
|
|
30
|
+
</CollapsibleFormSection>
|
|
31
|
+
) : (
|
|
32
|
+
<FlatFormSection {...rest} />
|
|
33
|
+
);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { BoxProps } from '@chakra-ui/react';
|
|
3
|
+
|
|
4
|
+
export interface FormSectionProps extends Omit<BoxProps, 'title'> {
|
|
5
|
+
/** Section heading. */
|
|
6
|
+
title?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Inline element rendered immediately after the heading (e.g. an
|
|
9
|
+
* `InfoSprinkle`). Flat variant only — the collapsible variant owns its own
|
|
10
|
+
* header. Distinct from `action`, which sits at the far right.
|
|
11
|
+
*/
|
|
12
|
+
titleAdornment?: ReactNode;
|
|
13
|
+
/** Secondary description under the heading. */
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
/** Right-aligned header slot (e.g. an add button or info-sprinkle trigger). */
|
|
16
|
+
action?: ReactNode;
|
|
17
|
+
/** Draw a top divider to separate this section from the previous one. */
|
|
18
|
+
divider?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Render as a collapsible accordion (delegates to logician `CollapsibleSection`).
|
|
21
|
+
* Requires `title`.
|
|
22
|
+
*/
|
|
23
|
+
collapsible?: boolean;
|
|
24
|
+
/** Initial expanded state when `collapsible`. Defaults to open. */
|
|
25
|
+
defaultOpen?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Flag a section that contains errors. When `collapsible` and collapsed, the
|
|
28
|
+
* chevron is replaced by a danger glyph so hidden errors aren't missed.
|
|
29
|
+
*/
|
|
30
|
+
hasError?: boolean;
|
|
31
|
+
/** Vertical gap between child fields. Defaults to the standard field rhythm. */
|
|
32
|
+
gap?: BoxProps['gap'];
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Box } from '@chakra-ui/react';
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
|
|
4
|
+
import { Button } from '@/components/Button';
|
|
5
|
+
import { InfoSprinkle } from '@/components/InfoSprinkle';
|
|
6
|
+
|
|
7
|
+
import { FormSectionHeader } from './FormSectionHeader';
|
|
8
|
+
|
|
9
|
+
const meta: Meta<typeof FormSectionHeader> = {
|
|
10
|
+
title: 'Components/Form/FormSectionHeader',
|
|
11
|
+
component: FormSectionHeader,
|
|
12
|
+
decorators: [
|
|
13
|
+
(Story) => (
|
|
14
|
+
<Box maxW="480px">
|
|
15
|
+
<Story />
|
|
16
|
+
</Box>
|
|
17
|
+
),
|
|
18
|
+
],
|
|
19
|
+
args: {
|
|
20
|
+
title: 'Collaboration',
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default meta;
|
|
25
|
+
type Story = StoryObj<typeof FormSectionHeader>;
|
|
26
|
+
|
|
27
|
+
export const TitleOnly: Story = {};
|
|
28
|
+
|
|
29
|
+
export const WithDescription: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
description: 'Grant analysts and editors access to work on this assistant.',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const WithTitleAdornment: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
title: 'Attached files',
|
|
38
|
+
description: 'Files this assistant can reference when answering.',
|
|
39
|
+
titleAdornment: <InfoSprinkle>Up to 20 files, 100MB each.</InfoSprinkle>,
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const WithAction: Story = {
|
|
44
|
+
args: {
|
|
45
|
+
title: 'Connectors',
|
|
46
|
+
description: 'Live data sources this assistant can query.',
|
|
47
|
+
action: (
|
|
48
|
+
<Button size="sm" variant="ghost">
|
|
49
|
+
Add
|
|
50
|
+
</Button>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { FormSectionHeader } from './FormSectionHeader';
|
|
6
|
+
|
|
7
|
+
// Stub the layout chrome and typography so the test asserts FormSectionHeader's
|
|
8
|
+
// own conditional slots (title / adornment / description / action), not
|
|
9
|
+
// Chakra/logician internals.
|
|
10
|
+
vi.mock('@chakra-ui/react', () => ({
|
|
11
|
+
Box: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
|
12
|
+
Flex: ({ children }: { children?: ReactNode }) => <div>{children}</div>,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock('@/components/Typography', () => ({
|
|
16
|
+
H5: ({ children }: { children?: ReactNode }) => <h5>{children}</h5>,
|
|
17
|
+
Subtext: ({ children }: { children?: ReactNode }) => (
|
|
18
|
+
<span data-testid="description">{children}</span>
|
|
19
|
+
),
|
|
20
|
+
}));
|
|
21
|
+
|
|
22
|
+
describe('FormSectionHeader', () => {
|
|
23
|
+
it('renders the title as a heading with a description below it', () => {
|
|
24
|
+
render(
|
|
25
|
+
<FormSectionHeader
|
|
26
|
+
title="Collaboration"
|
|
27
|
+
description="Grant access to work on this assistant."
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
expect(
|
|
31
|
+
screen.getByRole('heading', { name: 'Collaboration' })
|
|
32
|
+
).toBeInTheDocument();
|
|
33
|
+
expect(screen.getByTestId('description')).toHaveTextContent(
|
|
34
|
+
'Grant access to work on this assistant.'
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('renders the inline title adornment next to the heading', () => {
|
|
39
|
+
render(
|
|
40
|
+
<FormSectionHeader
|
|
41
|
+
title="Files"
|
|
42
|
+
titleAdornment={<span data-testid="sprinkle">i</span>}
|
|
43
|
+
/>
|
|
44
|
+
);
|
|
45
|
+
expect(screen.getByRole('heading', { name: 'Files' })).toBeInTheDocument();
|
|
46
|
+
expect(screen.getByTestId('sprinkle')).toBeInTheDocument();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('renders the right-aligned action', () => {
|
|
50
|
+
render(
|
|
51
|
+
<FormSectionHeader
|
|
52
|
+
title="Connectors"
|
|
53
|
+
action={<button type="button">Add</button>}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
expect(screen.getByRole('button', { name: 'Add' })).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('omits the heading row entirely when there is no title or adornment', () => {
|
|
60
|
+
render(<FormSectionHeader description="Standalone description." />);
|
|
61
|
+
expect(screen.queryByRole('heading')).toBeNull();
|
|
62
|
+
expect(screen.getByTestId('description')).toHaveTextContent(
|
|
63
|
+
'Standalone description.'
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders a bare heading with no description when none is given', () => {
|
|
68
|
+
render(<FormSectionHeader title="Visibility" />);
|
|
69
|
+
expect(
|
|
70
|
+
screen.getByRole('heading', { name: 'Visibility' })
|
|
71
|
+
).toBeInTheDocument();
|
|
72
|
+
expect(screen.queryByTestId('description')).toBeNull();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Box, Flex } from '@chakra-ui/react';
|
|
2
|
+
|
|
3
|
+
import { H5, Subtext } from '@/components/Typography';
|
|
4
|
+
|
|
5
|
+
import { FormSectionHeaderProps } from './FormSectionHeader.types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The standardized section (fieldset) header — the section-level counterpart to
|
|
9
|
+
* {@link FieldLabel}. A heading, an optional inline adornment, a description,
|
|
10
|
+
* and a right-aligned action, laid out with one consistent rhythm (`H5` over a
|
|
11
|
+
* muted `Subtext`). Shared by `FormSection` and the studio `GroupHeader` so
|
|
12
|
+
* every titled field group reads the same everywhere.
|
|
13
|
+
*
|
|
14
|
+
* The description sits inside the left column so a tall `action` centers against
|
|
15
|
+
* the whole heading+description block, matching the prior studio `GroupHeader`.
|
|
16
|
+
*/
|
|
17
|
+
export const FormSectionHeader = ({
|
|
18
|
+
title,
|
|
19
|
+
titleAdornment,
|
|
20
|
+
description,
|
|
21
|
+
action,
|
|
22
|
+
mb = 3,
|
|
23
|
+
...flexProps
|
|
24
|
+
}: FormSectionHeaderProps) => (
|
|
25
|
+
<Flex
|
|
26
|
+
align="center"
|
|
27
|
+
justify="space-between"
|
|
28
|
+
gap={2}
|
|
29
|
+
mb={mb}
|
|
30
|
+
w="100%"
|
|
31
|
+
{...flexProps}
|
|
32
|
+
>
|
|
33
|
+
<Box>
|
|
34
|
+
{(title || titleAdornment) && (
|
|
35
|
+
<Flex align="center" gap={1} mb={description ? 1 : 0}>
|
|
36
|
+
{title && <H5>{title}</H5>}
|
|
37
|
+
{titleAdornment}
|
|
38
|
+
</Flex>
|
|
39
|
+
)}
|
|
40
|
+
{description && (
|
|
41
|
+
<Subtext color="fg.muted" fontWeight="normal">
|
|
42
|
+
{description}
|
|
43
|
+
</Subtext>
|
|
44
|
+
)}
|
|
45
|
+
</Box>
|
|
46
|
+
{action}
|
|
47
|
+
</Flex>
|
|
48
|
+
);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { FlexProps } from '@chakra-ui/react';
|
|
3
|
+
|
|
4
|
+
export interface FormSectionHeaderProps extends Omit<FlexProps, 'title'> {
|
|
5
|
+
/** Section heading — the fieldset-level label. */
|
|
6
|
+
title?: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Inline element rendered immediately after the title text (e.g. an
|
|
9
|
+
* `InfoSprinkle`). Distinct from `action`, which sits at the far right of
|
|
10
|
+
* the row.
|
|
11
|
+
*/
|
|
12
|
+
titleAdornment?: ReactNode;
|
|
13
|
+
/** Secondary description under the title. */
|
|
14
|
+
description?: ReactNode;
|
|
15
|
+
/** Right-aligned slot on the title row (e.g. an add button). */
|
|
16
|
+
action?: ReactNode;
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type { FieldErrorProps } from './FieldError';
|
|
2
|
+
export { FieldError } from './FieldError';
|
|
3
|
+
export type { FieldHelpProps } from './FieldHelp';
|
|
4
|
+
export { FieldHelp } from './FieldHelp';
|
|
5
|
+
export type { FieldLabelProps } from './FieldLabel';
|
|
6
|
+
export { FieldLabel } from './FieldLabel';
|
|
7
|
+
export type { FieldRowProps } from './FieldRow';
|
|
8
|
+
export { FieldRow } from './FieldRow';
|
|
9
|
+
export type { FormFooterProps } from './FormFooter';
|
|
10
|
+
export { FormFooter } from './FormFooter';
|
|
11
|
+
export type { FormModalFooterProps } from './FormModalFooter';
|
|
12
|
+
export { FormModalFooter } from './FormModalFooter';
|
|
13
|
+
export { FormScrollArea } from './FormScrollArea';
|
|
14
|
+
export type { FormSectionProps } from './FormSection';
|
|
15
|
+
export { FormSection } from './FormSection';
|
|
16
|
+
export type { FormSectionHeaderProps } from './FormSectionHeader';
|
|
17
|
+
export { FormSectionHeader } from './FormSectionHeader';
|
|
@@ -16,7 +16,7 @@ export const tagColorPaletteStyles: Record<
|
|
|
16
16
|
primary: {
|
|
17
17
|
soft: {
|
|
18
18
|
bgColor: 'primary.extralight',
|
|
19
|
-
borderColor: 'primary.
|
|
19
|
+
borderColor: 'primary.lighter',
|
|
20
20
|
color: 'primary.darker',
|
|
21
21
|
},
|
|
22
22
|
solid: {
|
|
@@ -33,7 +33,7 @@ export const tagColorPaletteStyles: Record<
|
|
|
33
33
|
secondary: {
|
|
34
34
|
soft: {
|
|
35
35
|
bgColor: 'secondary.extralight',
|
|
36
|
-
borderColor: 'secondary.
|
|
36
|
+
borderColor: 'secondary.lighter',
|
|
37
37
|
color: 'secondary.darker',
|
|
38
38
|
},
|
|
39
39
|
solid: {
|
|
@@ -50,7 +50,7 @@ export const tagColorPaletteStyles: Record<
|
|
|
50
50
|
danger: {
|
|
51
51
|
soft: {
|
|
52
52
|
bgColor: 'danger.extralight',
|
|
53
|
-
borderColor: 'danger.
|
|
53
|
+
borderColor: 'danger.lighter',
|
|
54
54
|
color: 'danger.darker',
|
|
55
55
|
},
|
|
56
56
|
solid: {
|
|
@@ -67,7 +67,7 @@ export const tagColorPaletteStyles: Record<
|
|
|
67
67
|
success: {
|
|
68
68
|
soft: {
|
|
69
69
|
bgColor: 'success.extralight',
|
|
70
|
-
borderColor: 'success.
|
|
70
|
+
borderColor: 'success.lighter',
|
|
71
71
|
color: 'success.darker',
|
|
72
72
|
},
|
|
73
73
|
solid: {
|
|
@@ -84,7 +84,7 @@ export const tagColorPaletteStyles: Record<
|
|
|
84
84
|
warning: {
|
|
85
85
|
soft: {
|
|
86
86
|
bgColor: 'warning.extralight',
|
|
87
|
-
borderColor: 'warning.
|
|
87
|
+
borderColor: 'warning.lighter',
|
|
88
88
|
color: 'warning.darker',
|
|
89
89
|
},
|
|
90
90
|
solid: {
|
package/src/index.ts
CHANGED