@djangocfg/ui-core 2.1.504 → 2.1.507
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/README.md
CHANGED
|
@@ -54,7 +54,7 @@ import { UiProviders, Button, Card } from '@djangocfg/ui-core';
|
|
|
54
54
|
| `components/forms/` | Button · Input · Textarea · Select · Switch · Checkbox · Slider · Form |
|
|
55
55
|
| `components/feedback/` | Alert · Toast · Banner · Progress · Spinner |
|
|
56
56
|
| `components/overlay/` | Dialog · Drawer · Popover · Tooltip · HoverCard · Sheet · ContextMenu · DropdownMenu |
|
|
57
|
-
| `components/navigation/` | Sidebar · Tabs · Breadcrumb · Pagination · NavigationMenu · Command |
|
|
57
|
+
| `components/navigation/` | Sidebar · Tabs · Breadcrumb · Pagination · NavigationMenu · Command · Disclosure |
|
|
58
58
|
| `components/layout/` | Container · Grid · Stack · Separator · ScrollArea · Sticky |
|
|
59
59
|
| `components/select/` | Combobox · MultiSelect |
|
|
60
60
|
| `components/effects/` | Glass · Marquee · Backdrop |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.507",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
129
129
|
},
|
|
130
130
|
"peerDependencies": {
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
131
|
+
"@djangocfg/i18n": "^2.1.507",
|
|
132
132
|
"consola": "^3.4.2",
|
|
133
133
|
"lucide-react": "^0.545.0",
|
|
134
134
|
"moment": "^2.30.1",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"@chenglou/pretext": "^0.0.8"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/i18n": "^2.1.
|
|
210
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/i18n": "^2.1.507",
|
|
210
|
+
"@djangocfg/typescript-config": "^2.1.507",
|
|
211
211
|
"@types/node": "^24.13.3",
|
|
212
212
|
"@types/react": "19.2.15",
|
|
213
213
|
"@types/react-dom": "19.2.3",
|
package/src/components/index.ts
CHANGED
|
@@ -92,6 +92,8 @@ export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './
|
|
|
92
92
|
export { useAccordionSingleState, useAccordionMultipleState } from './navigation/accordion/useAccordionState';
|
|
93
93
|
export type { UseAccordionSingleResult, UseAccordionMultipleResult } from './navigation/accordion/useAccordionState';
|
|
94
94
|
export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './navigation/collapsible';
|
|
95
|
+
export { Disclosure } from './navigation/disclosure';
|
|
96
|
+
export type { DisclosureProps, DisclosureTone, DisclosureSize } from './navigation/disclosure';
|
|
95
97
|
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from './navigation/command';
|
|
96
98
|
export { Link, LinkProvider, LinkComponentContext, useLinkComponent } from './navigation/link';
|
|
97
99
|
export type { LinkProps, LinkComponent, LinkComponentProps, LinkProviderProps } from './navigation/link';
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Disclosure — a light "line in the flow" that expands.
|
|
5
|
+
*
|
|
6
|
+
* [icon] label ·································· [fact] [chevron]
|
|
7
|
+
* └────────────── whole row is the expand target (one <button>) ──────────────┘
|
|
8
|
+
* [ body — rendered only when open, indented, NO card frame ]
|
|
9
|
+
*
|
|
10
|
+
* The gap `SettingsBlock` leaves open. `SettingsBlock` renders a section HEADING
|
|
11
|
+
* (`uppercase font-semibold` title) plus its summary/body as a **sibling `<div>`
|
|
12
|
+
* under the header** — so a collapsed block is always ≥2 full-width rows and
|
|
13
|
+
* reads with the weight of a section. That is correct for a settings surface and
|
|
14
|
+
* wrong for a feed of short items (chat tool-call cards): there, a collapsed item
|
|
15
|
+
* should be ONE line that reads as a list row, not a heading.
|
|
16
|
+
*
|
|
17
|
+
* `Disclosure` is that one-line disclosure:
|
|
18
|
+
* - collapsed = exactly one row (`label` + right-aligned `fact` on the SAME line);
|
|
19
|
+
* - `tone` names the weight ('row' = list line, default; 'section' = heading) —
|
|
20
|
+
* weight is derived from intent, never a density preference/toggle;
|
|
21
|
+
* - the leading `icon` is a fixed `size-4` slot, so a spinner→dot swap
|
|
22
|
+
* (running→done) causes no reflow, and `open` only mounts the body below —
|
|
23
|
+
* the header height is stable across running → done → open;
|
|
24
|
+
* - `open` is controllable, so a host can pin a failed item open forever while
|
|
25
|
+
* it still reads as a light, frameless line.
|
|
26
|
+
*
|
|
27
|
+
* Additive: `SettingsBlock` / `SettingRow` are unchanged. See
|
|
28
|
+
* `packages/@dev/active/disclosure-row-primitive/`.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { ChevronDown } from 'lucide-react';
|
|
32
|
+
import * as React from 'react';
|
|
33
|
+
|
|
34
|
+
import { cn } from '../../../lib/utils';
|
|
35
|
+
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '../collapsible';
|
|
36
|
+
|
|
37
|
+
export type DisclosureTone = 'row' | 'section';
|
|
38
|
+
export type DisclosureSize = 'sm' | 'default';
|
|
39
|
+
|
|
40
|
+
export interface DisclosureProps {
|
|
41
|
+
/** Fixed-size leading slot (spinner / status dot / glyph). Rendered in a size-4 box. */
|
|
42
|
+
icon?: React.ReactNode;
|
|
43
|
+
/** Primary label on the header line (e.g. a tool name). */
|
|
44
|
+
label: React.ReactNode;
|
|
45
|
+
/** Right-aligned summary on the SAME header line. Absent → the row stays one line. */
|
|
46
|
+
fact?: React.ReactNode;
|
|
47
|
+
/** Weight. 'row' = light list line (default). 'section' = heading weight. */
|
|
48
|
+
tone?: DisclosureTone;
|
|
49
|
+
/** Density. 'sm' (default) matches a transcript; 'default' is roomier. */
|
|
50
|
+
size?: DisclosureSize;
|
|
51
|
+
/** Controlled open. Host pins failures open (never auto-collapse a failure). */
|
|
52
|
+
open?: boolean;
|
|
53
|
+
/** Uncontrolled initial open. Ignored when `open` is provided. */
|
|
54
|
+
defaultOpen?: boolean;
|
|
55
|
+
onOpenChange?: (open: boolean) => void;
|
|
56
|
+
/** Disable the trigger — e.g. a body-less running row that shouldn't invite a click. */
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
/** Body, mounted only while open. */
|
|
59
|
+
children?: React.ReactNode;
|
|
60
|
+
/** Extra classes on the root. */
|
|
61
|
+
className?: string;
|
|
62
|
+
/** Extra classes on the header button. */
|
|
63
|
+
headerClassName?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const HEADER_BASE =
|
|
67
|
+
'group flex w-full items-center gap-2 text-left transition-colors ' +
|
|
68
|
+
'hover:bg-muted/60 disabled:cursor-default disabled:opacity-60 ' +
|
|
69
|
+
'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring';
|
|
70
|
+
|
|
71
|
+
const SIZE_HEADER: Record<DisclosureSize, string> = {
|
|
72
|
+
sm: 'px-2.5 py-1.5 text-xs',
|
|
73
|
+
default: 'px-3 py-2 text-sm',
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const SIZE_BODY: Record<DisclosureSize, string> = {
|
|
77
|
+
sm: 'px-2.5 pb-1.5 text-[11px]',
|
|
78
|
+
default: 'px-3 pb-2 text-xs',
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
// 'row' reads as a line in the flow; 'section' reproduces SettingsBlock's
|
|
82
|
+
// BLOCK_TITLE weight so a caller wanting a heading needn't reach for it.
|
|
83
|
+
const TONE_LABEL: Record<DisclosureTone, string> = {
|
|
84
|
+
row: 'min-w-0 truncate text-foreground/90',
|
|
85
|
+
section: 'min-w-0 truncate font-semibold uppercase tracking-wide text-muted-foreground',
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* A body that keeps the collapsed row to a single line stays light; the reveal
|
|
90
|
+
* animation classes come from `tailwindcss-animate` and are cosmetic — if a
|
|
91
|
+
* consumer omits that plugin the body still shows/hides, it just snaps. Height
|
|
92
|
+
* stability comes from the fixed header, not the animation.
|
|
93
|
+
*/
|
|
94
|
+
const BODY_ANIM =
|
|
95
|
+
'overflow-hidden data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down';
|
|
96
|
+
|
|
97
|
+
export const Disclosure = React.forwardRef<HTMLDivElement, DisclosureProps>(
|
|
98
|
+
(
|
|
99
|
+
{
|
|
100
|
+
icon,
|
|
101
|
+
label,
|
|
102
|
+
fact,
|
|
103
|
+
tone = 'row',
|
|
104
|
+
size = 'sm',
|
|
105
|
+
open,
|
|
106
|
+
defaultOpen,
|
|
107
|
+
onOpenChange,
|
|
108
|
+
disabled = false,
|
|
109
|
+
children,
|
|
110
|
+
className,
|
|
111
|
+
headerClassName,
|
|
112
|
+
},
|
|
113
|
+
ref,
|
|
114
|
+
) => {
|
|
115
|
+
const hasBody = children != null && children !== false;
|
|
116
|
+
|
|
117
|
+
return (
|
|
118
|
+
<Collapsible
|
|
119
|
+
ref={ref}
|
|
120
|
+
open={open}
|
|
121
|
+
defaultOpen={defaultOpen}
|
|
122
|
+
onOpenChange={onOpenChange}
|
|
123
|
+
className={cn('min-w-0', className)}
|
|
124
|
+
>
|
|
125
|
+
<CollapsibleTrigger
|
|
126
|
+
type="button"
|
|
127
|
+
disabled={disabled || !hasBody}
|
|
128
|
+
className={cn(HEADER_BASE, SIZE_HEADER[size], 'rounded-control', headerClassName)}
|
|
129
|
+
>
|
|
130
|
+
{/* Fixed-size leading slot: spinner↔dot swap here never reflows the row. */}
|
|
131
|
+
{icon != null ? (
|
|
132
|
+
<span className="flex size-4 shrink-0 items-center justify-center">{icon}</span>
|
|
133
|
+
) : null}
|
|
134
|
+
|
|
135
|
+
<span className={TONE_LABEL[tone]}>{label}</span>
|
|
136
|
+
|
|
137
|
+
{/* Right-aligned fact on the SAME line; absent → collapses, row stays 1 line. */}
|
|
138
|
+
{fact != null ? (
|
|
139
|
+
<span className="ml-auto shrink-0 text-muted-foreground">{fact}</span>
|
|
140
|
+
) : null}
|
|
141
|
+
|
|
142
|
+
{/* Chevron only when there is a body to reveal. Pushed to the end;
|
|
143
|
+
when `fact` is absent `ml-auto` here keeps it right-aligned. */}
|
|
144
|
+
{hasBody ? (
|
|
145
|
+
<ChevronDown
|
|
146
|
+
aria-hidden
|
|
147
|
+
className={cn(
|
|
148
|
+
'size-3.5 shrink-0 opacity-60 transition-transform duration-200',
|
|
149
|
+
'group-data-[state=open]:rotate-180',
|
|
150
|
+
fact == null && 'ml-auto',
|
|
151
|
+
)}
|
|
152
|
+
/>
|
|
153
|
+
) : null}
|
|
154
|
+
</CollapsibleTrigger>
|
|
155
|
+
|
|
156
|
+
{hasBody ? (
|
|
157
|
+
<CollapsibleContent className={BODY_ANIM}>
|
|
158
|
+
{/* Indented, frameless body — no card, no border box → stays light
|
|
159
|
+
even when a failed item is pinned open. */}
|
|
160
|
+
<div className={cn('pt-1', SIZE_BODY[size])}>{children}</div>
|
|
161
|
+
</CollapsibleContent>
|
|
162
|
+
) : null}
|
|
163
|
+
</Collapsible>
|
|
164
|
+
);
|
|
165
|
+
},
|
|
166
|
+
);
|
|
167
|
+
Disclosure.displayName = 'Disclosure';
|