@capillaryjs/capillary-ui 1.0.0-alpha.1
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/CHANGELOG.md +459 -0
- package/LICENSE +55 -0
- package/NOTICE +2 -0
- package/README.md +853 -0
- package/colors/README.md +63 -0
- package/colors/gray/colors.css +16 -0
- package/colors/green/colors.css +16 -0
- package/colors/iceblue/colors.css +19 -0
- package/colors/ocean/colors.css +16 -0
- package/colors/orange/colors.css +16 -0
- package/colors/purple/colors.css +16 -0
- package/colors/red/colors.css +16 -0
- package/colors/yellow/colors.css +16 -0
- package/dist/Components/Placeholder.d.ts +15 -0
- package/dist/Components/Placeholder.d.ts.map +1 -0
- package/dist/Components/app/app.d.ts +41 -0
- package/dist/Components/app/app.d.ts.map +1 -0
- package/dist/Components/component.d.ts +256 -0
- package/dist/Components/component.d.ts.map +1 -0
- package/dist/Components/controlUtils.d.ts +23 -0
- package/dist/Components/controlUtils.d.ts.map +1 -0
- package/dist/Components/data/descriptionList.d.ts +23 -0
- package/dist/Components/data/descriptionList.d.ts.map +1 -0
- package/dist/Components/data/filterState.d.ts +38 -0
- package/dist/Components/data/filterState.d.ts.map +1 -0
- package/dist/Components/data/infoPanel.d.ts +29 -0
- package/dist/Components/data/infoPanel.d.ts.map +1 -0
- package/dist/Components/data/listview/listview.d.ts +51 -0
- package/dist/Components/data/listview/listview.d.ts.map +1 -0
- package/dist/Components/data/selectionhandler.d.ts +87 -0
- package/dist/Components/data/selectionhandler.d.ts.map +1 -0
- package/dist/Components/data/table/DataTable.d.ts +79 -0
- package/dist/Components/data/table/DataTable.d.ts.map +1 -0
- package/dist/Components/data/table/FilterPanel.d.ts +41 -0
- package/dist/Components/data/table/FilterPanel.d.ts.map +1 -0
- package/dist/Components/data/table/TableHeader.d.ts +23 -0
- package/dist/Components/data/table/TableHeader.d.ts.map +1 -0
- package/dist/Components/data/table/TableHeaderCell.d.ts +41 -0
- package/dist/Components/data/table/TableHeaderCell.d.ts.map +1 -0
- package/dist/Components/data/table/tableDataSource.d.ts +49 -0
- package/dist/Components/data/table/tableDataSource.d.ts.map +1 -0
- package/dist/Components/data/table/tableQuery.d.ts +29 -0
- package/dist/Components/data/table/tableQuery.d.ts.map +1 -0
- package/dist/Components/data/treeview/treeModel.d.ts +23 -0
- package/dist/Components/data/treeview/treeModel.d.ts.map +1 -0
- package/dist/Components/data/treeview/treeitem.d.ts +21 -0
- package/dist/Components/data/treeview/treeitem.d.ts.map +1 -0
- package/dist/Components/data/treeview/treeview.d.ts +49 -0
- package/dist/Components/data/treeview/treeview.d.ts.map +1 -0
- package/dist/Components/dialog/dialog.d.ts +46 -0
- package/dist/Components/dialog/dialog.d.ts.map +1 -0
- package/dist/Components/layout/declarativeRegion.d.ts +30 -0
- package/dist/Components/layout/declarativeRegion.d.ts.map +1 -0
- package/dist/Components/layout/groupBox.d.ts +21 -0
- package/dist/Components/layout/groupBox.d.ts.map +1 -0
- package/dist/Components/layout/header.d.ts +20 -0
- package/dist/Components/layout/header.d.ts.map +1 -0
- package/dist/Components/layout/layout.d.ts +28 -0
- package/dist/Components/layout/layout.d.ts.map +1 -0
- package/dist/Components/layout/layoutTraits.d.ts +39 -0
- package/dist/Components/layout/layoutTraits.d.ts.map +1 -0
- package/dist/Components/layout/optionGroup.d.ts +35 -0
- package/dist/Components/layout/optionGroup.d.ts.map +1 -0
- package/dist/Components/layout/optionsBox.d.ts +12 -0
- package/dist/Components/layout/optionsBox.d.ts.map +1 -0
- package/dist/Components/layout/panel.d.ts +34 -0
- package/dist/Components/layout/panel.d.ts.map +1 -0
- package/dist/Components/layout/routedSelection.d.ts +10 -0
- package/dist/Components/layout/routedSelection.d.ts.map +1 -0
- package/dist/Components/layout/sidebar.d.ts +27 -0
- package/dist/Components/layout/sidebar.d.ts.map +1 -0
- package/dist/Components/layout/splitView.d.ts +82 -0
- package/dist/Components/layout/splitView.d.ts.map +1 -0
- package/dist/Components/layout/tabpanel/tab.d.ts +15 -0
- package/dist/Components/layout/tabpanel/tab.d.ts.map +1 -0
- package/dist/Components/layout/tabpanel/tabline.d.ts +31 -0
- package/dist/Components/layout/tabpanel/tabline.d.ts.map +1 -0
- package/dist/Components/layout/tabpanel/tabpanel.d.ts +44 -0
- package/dist/Components/layout/tabpanel/tabpanel.d.ts.map +1 -0
- package/dist/Components/lineinputs/CheckableControl.d.ts +7 -0
- package/dist/Components/lineinputs/CheckableControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/LabeledInputControl.d.ts +7 -0
- package/dist/Components/lineinputs/LabeledInputControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/SelectControl.d.ts +7 -0
- package/dist/Components/lineinputs/SelectControl.d.ts.map +1 -0
- package/dist/Components/lineinputs/checkbox/Checkbox.d.ts +46 -0
- package/dist/Components/lineinputs/checkbox/Checkbox.d.ts.map +1 -0
- package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts +11 -0
- package/dist/Components/lineinputs/checkbox/QuadCheckbox.d.ts.map +1 -0
- package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts +11 -0
- package/dist/Components/lineinputs/checkbox/TriCheckbox.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/Calendar.d.ts +23 -0
- package/dist/Components/lineinputs/datetime/Calendar.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/DatePicker.d.ts +62 -0
- package/dist/Components/lineinputs/datetime/DatePicker.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/DateTimePicker.d.ts +52 -0
- package/dist/Components/lineinputs/datetime/DateTimePicker.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/TimePicker.d.ts +46 -0
- package/dist/Components/lineinputs/datetime/TimePicker.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/civilDate.d.ts +37 -0
- package/dist/Components/lineinputs/datetime/civilDate.d.ts.map +1 -0
- package/dist/Components/lineinputs/datetime/timeString.d.ts +26 -0
- package/dist/Components/lineinputs/datetime/timeString.d.ts.map +1 -0
- package/dist/Components/lineinputs/dropdown.d.ts +49 -0
- package/dist/Components/lineinputs/dropdown.d.ts.map +1 -0
- package/dist/Components/lineinputs/label.d.ts +14 -0
- package/dist/Components/lineinputs/label.d.ts.map +1 -0
- package/dist/Components/lineinputs/radio.d.ts +69 -0
- package/dist/Components/lineinputs/radio.d.ts.map +1 -0
- package/dist/Components/lineinputs/textbox.d.ts +40 -0
- package/dist/Components/lineinputs/textbox.d.ts.map +1 -0
- package/dist/Components/lineinputs/toggle.d.ts +40 -0
- package/dist/Components/lineinputs/toggle.d.ts.map +1 -0
- package/dist/Components/menu/button.d.ts +31 -0
- package/dist/Components/menu/button.d.ts.map +1 -0
- package/dist/Components/menu/toolbar.d.ts +15 -0
- package/dist/Components/menu/toolbar.d.ts.map +1 -0
- package/dist/Components/navigation/breadcrumb.d.ts +31 -0
- package/dist/Components/navigation/breadcrumb.d.ts.map +1 -0
- package/dist/Components/navigation/navigationBar.d.ts +53 -0
- package/dist/Components/navigation/navigationBar.d.ts.map +1 -0
- package/dist/Components/status/progressBar.d.ts +23 -0
- package/dist/Components/status/progressBar.d.ts.map +1 -0
- package/dist/Components/status/statusPresentation.d.ts +21 -0
- package/dist/Components/status/statusPresentation.d.ts.map +1 -0
- package/dist/Components/theme/stylesheetPicker.d.ts +42 -0
- package/dist/Components/theme/stylesheetPicker.d.ts.map +1 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7736 -0
- package/dist/index.js.map +1 -0
- package/dist/jsx-dev-runtime-BAF7C1E1.js +1368 -0
- package/dist/jsx-dev-runtime-BAF7C1E1.js.map +1 -0
- package/dist/jsx-dev-runtime.d.ts +2 -0
- package/dist/jsx-dev-runtime.d.ts.map +1 -0
- package/dist/jsx-dev-runtime.js +6 -0
- package/dist/jsx-dev-runtime.js.map +1 -0
- package/dist/jsx-runtime.d.ts +26 -0
- package/dist/jsx-runtime.d.ts.map +1 -0
- package/dist/jsx-runtime.js +9 -0
- package/dist/jsx-runtime.js.map +1 -0
- package/dist/localization.d.ts +76 -0
- package/dist/localization.d.ts.map +1 -0
- package/dist/routing/RouteLink.d.ts +18 -0
- package/dist/routing/RouteLink.d.ts.map +1 -0
- package/dist/routing/RouteOutlet.d.ts +38 -0
- package/dist/routing/RouteOutlet.d.ts.map +1 -0
- package/dist/routing/RouteQuery.d.ts +19 -0
- package/dist/routing/RouteQuery.d.ts.map +1 -0
- package/dist/routing/RouteScope.d.ts +18 -0
- package/dist/routing/RouteScope.d.ts.map +1 -0
- package/dist/routing/RouteValue.d.ts +19 -0
- package/dist/routing/RouteValue.d.ts.map +1 -0
- package/dist/routing/navigationAdapter.d.ts +33 -0
- package/dist/routing/navigationAdapter.d.ts.map +1 -0
- package/dist/routing/route.d.ts +64 -0
- package/dist/routing/route.d.ts.map +1 -0
- package/dist/routing/router.d.ts +109 -0
- package/dist/routing/router.d.ts.map +1 -0
- package/dist/runtime.d.ts +34 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/services.d.ts +45 -0
- package/dist/services.d.ts.map +1 -0
- package/dist/styling/styleRegistry.d.ts +15 -0
- package/dist/styling/styleRegistry.d.ts.map +1 -0
- package/dist/styling/theme.d.ts +39 -0
- package/dist/styling/theme.d.ts.map +1 -0
- package/dist/util/filterMode.d.ts +15 -0
- package/dist/util/filterMode.d.ts.map +1 -0
- package/docs/application-composition-guide.md +843 -0
- package/docs/application-layout-guide.md +553 -0
- package/package.json +90 -0
- package/styles/structural.css +2388 -0
- package/themes/README.md +99 -0
- package/themes/base.css +344 -0
- package/themes/java/theme.css +21 -0
- package/themes/minimal/theme.css +7 -0
- package/themes/shiny/theme.css +168 -0
|
@@ -0,0 +1,843 @@
|
|
|
1
|
+
# Capillary UI Application Composition Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you turn an application's intended functionality into readable
|
|
4
|
+
screens, component boundaries, and layouts. It assumes familiarity with HTML,
|
|
5
|
+
CSS, and TypeScript, but no particular experience with Capillary UI.
|
|
6
|
+
|
|
7
|
+
Start with the work the user needs to do. Decide which information and controls
|
|
8
|
+
belong together, which regions persist during navigation, and what should
|
|
9
|
+
happen when data changes. Then choose components and layout mechanics that
|
|
10
|
+
express those decisions.
|
|
11
|
+
|
|
12
|
+
These are design recommendations within Capillary UI's existing contracts, rather
|
|
13
|
+
than a mandatory application template. Applications own composition and policy;
|
|
14
|
+
Capillary UI owns presentation and browser lifecycle; Capillary owns reactive propagation.
|
|
15
|
+
The companion [repository layout guide](application-layout-guide.md) explains
|
|
16
|
+
where to put the resulting code.
|
|
17
|
+
|
|
18
|
+
## 1. Start with workflows and relationships
|
|
19
|
+
|
|
20
|
+
For each screen, write down the question it answers and the actions it supports.
|
|
21
|
+
For example, a records application might have these workflows:
|
|
22
|
+
|
|
23
|
+
| Workflow | Information and interaction | Likely composition |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Find records needing attention | Search, status filters, matching records | Controls beside results |
|
|
26
|
+
| Investigate one record | Record selection, summary, history | Navigator beside a workspace with local tabs |
|
|
27
|
+
| Read a report | Headings, prose, supporting tables | A document that grows with its content |
|
|
28
|
+
| Monitor activity | Several changing result streams | Independently scrolling regions |
|
|
29
|
+
|
|
30
|
+
Similar colors, spacing, and headers do not require identical screen structure.
|
|
31
|
+
Give each workflow an appropriate arrangement while sharing presentation traits
|
|
32
|
+
and components where their meaning stays consistent.
|
|
33
|
+
|
|
34
|
+
Identify the authoritative values and how interactions affect them. A search
|
|
35
|
+
control can write a Capillary emitter used as a query argument; a results component
|
|
36
|
+
can observe the query result. Both components participate in one workflow
|
|
37
|
+
without either needing to know the other's DOM structure. Keep business rules
|
|
38
|
+
and reusable calculations in application/domain code, and use a view-owned
|
|
39
|
+
coordinator when several controls and results need orchestration. A simple
|
|
40
|
+
screen does not need a coordinator merely for consistency with larger screens.
|
|
41
|
+
|
|
42
|
+
Keep these boundaries distinct:
|
|
43
|
+
|
|
44
|
+
| Boundary | Decision it expresses |
|
|
45
|
+
| --- | --- |
|
|
46
|
+
| Component | A presentation or interaction responsibility with a useful contract |
|
|
47
|
+
| Route or tab | A navigable choice and its content lifetime |
|
|
48
|
+
| State owner | Who creates, changes, and disposes a value or operation |
|
|
49
|
+
| Layout region | How space is allocated and content arranged |
|
|
50
|
+
| Scroll container | Which content moves when the user scrolls |
|
|
51
|
+
| Island | A meaningful, visually self-contained work surface |
|
|
52
|
+
|
|
53
|
+
A results island can contain several components that share a view's state and
|
|
54
|
+
use one inner scroll container. None of those boundaries requires the others
|
|
55
|
+
to occupy the same place in the tree.
|
|
56
|
+
|
|
57
|
+
### Choose an intentional layout boundary
|
|
58
|
+
|
|
59
|
+
Use the smallest component whose contract explains why the container exists:
|
|
60
|
+
|
|
61
|
+
| Component | Use it when | Do not use it merely for |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| `Layout` | Children need a horizontal or vertical arrangement, allocation, or an explicit scroll owner, but the container has no further user-facing meaning | Surface chrome, a labelled region, or resizing |
|
|
64
|
+
| `Panel` | The region is a deliberate themed surface, optionally with a heading and toolbar; its body is a Layout | A neutral wrapper whose only job is child arrangement |
|
|
65
|
+
| `SplitView` | Exactly two named panes need user-controlled resizing | An ordinary two-column or two-row arrangement |
|
|
66
|
+
|
|
67
|
+
`SplitView` supplies the accessible separator and its interaction. `Layout`
|
|
68
|
+
and `Panel` do not; use a horizontal or vertical `Layout` for a fixed
|
|
69
|
+
arrangement. `Panel` is itself composed over an inner Layout, so its ordinary
|
|
70
|
+
children receive the same allocation and arrangement contract while the Panel
|
|
71
|
+
owns the surrounding chrome.
|
|
72
|
+
|
|
73
|
+
Do not add an authored `<div>` or other anonymous element merely to carry
|
|
74
|
+
layout classes. It has neither a Capillary UI component contract nor component-owned
|
|
75
|
+
structural and theme CSS, and it obscures whether the wrapper is neutral,
|
|
76
|
+
surface-like, or interactive. Use `Layout` for that neutral case. This is a
|
|
77
|
+
preference for intentional boundaries, not a ban on native HTML: use `main`,
|
|
78
|
+
`section`, `aside`, `nav`, `header`, `footer`, `article`, lists, tables, and
|
|
79
|
+
form elements when they express real document or control semantics. Such
|
|
80
|
+
application-owned native elements may still use Capillary UI's public traits when the
|
|
81
|
+
native semantic boundary is the right layout boundary.
|
|
82
|
+
|
|
83
|
+
For the three layout components, use direct `horizontal` / `vertical` and
|
|
84
|
+
`scroll` modifiers. Keep `allocation` named because its meaning is relative to
|
|
85
|
+
the parent's main axis. The public traits remain available for semantic native
|
|
86
|
+
elements and deliberate integration seams; they are not the default way to
|
|
87
|
+
invent generic wrappers.
|
|
88
|
+
|
|
89
|
+
## 2. Separate persistent structure from changing content
|
|
90
|
+
|
|
91
|
+
Put application-wide branding, navigation, and status in the application root.
|
|
92
|
+
Put an outlet where page content changes. A page then declares the arrangement
|
|
93
|
+
needed for its own workflow.
|
|
94
|
+
|
|
95
|
+
The following excerpts use current Capillary UI APIs. Imports and application-specific
|
|
96
|
+
data implementations are omitted. `RecordsView`, `ActivityView`,
|
|
97
|
+
`RecordNavigator`, `RecordSummary`, `RecordHistory`, `RecordFilters`, and
|
|
98
|
+
`RecordsResults` are application components, not Capillary UI exports. See
|
|
99
|
+
[TSX setup](../README.md#set-up-tsx) for imports, presentation assets, and style
|
|
100
|
+
collection. Class components declare their rendered component dependencies so
|
|
101
|
+
`mountCapillaryUiApp()` can collect structural CSS, including components appearing only
|
|
102
|
+
in inactive routes or conditional branches.
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
const recordsRoute = defineRoute('records')
|
|
106
|
+
const activityRoute = defineRoute('activity')
|
|
107
|
+
|
|
108
|
+
class RecordsApp extends CapillaryUiApp {
|
|
109
|
+
protected override renderContent() {
|
|
110
|
+
return <>
|
|
111
|
+
<header className="cap-size-natural">
|
|
112
|
+
<h1>Records</h1>
|
|
113
|
+
</header>
|
|
114
|
+
<NavigationBar
|
|
115
|
+
allocation="natural"
|
|
116
|
+
label="Application sections"
|
|
117
|
+
items={[
|
|
118
|
+
{id: 'records', label: 'Records', to: routeTarget(recordsRoute)},
|
|
119
|
+
{id: 'activity', label: 'Activity', to: routeTarget(activityRoute)},
|
|
120
|
+
]}
|
|
121
|
+
/>
|
|
122
|
+
<main className="cap-size-flexible cap-layout-vertical">
|
|
123
|
+
<RouteOutlet
|
|
124
|
+
mountPolicy="active-only"
|
|
125
|
+
views={[
|
|
126
|
+
{id: 'records', route: recordsRoute, content: <RecordsView />},
|
|
127
|
+
{id: 'activity', route: activityRoute, content: <ActivityView />},
|
|
128
|
+
]}
|
|
129
|
+
/>
|
|
130
|
+
</main>
|
|
131
|
+
<footer className="cap-size-natural">Connected</footer>
|
|
132
|
+
</>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
static dependencies = [NavigationBar, RouteOutlet, RecordsView, ActivityView]
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const router = createBrowserRouter({adapter: createHashNavigation()})
|
|
139
|
+
const runtime = createCapillaryUiRuntime({router})
|
|
140
|
+
const app = mountCapillaryUiApp(runtime, RecordsApp, document.querySelector('#app')!, {
|
|
141
|
+
sizing: 'viewport',
|
|
142
|
+
layout: 'vertical',
|
|
143
|
+
landmark: 'none',
|
|
144
|
+
})
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Here `CapillaryUiApp` supplies the bounded root and its vertical arrangement. The
|
|
148
|
+
explicit `<main>` supplies the primary-content landmark, so the root uses
|
|
149
|
+
`landmark: 'none'` to avoid nesting main landmarks. The example assumes a
|
|
150
|
+
dedicated app document with its default body margin removed. At application
|
|
151
|
+
shutdown, destroy `app`, dispose the caller-owned `router`, and dispose any
|
|
152
|
+
application-owned service scope after its component tree.
|
|
153
|
+
|
|
154
|
+
`NavigationBar` renders native route links and their active state. `RouteOutlet`
|
|
155
|
+
owns the destination content and registers its sibling routes. Keep one owner
|
|
156
|
+
for that route set. Navigation does not require rebuilding the application
|
|
157
|
+
root or duplicating the header in each page.
|
|
158
|
+
|
|
159
|
+
### Adapt organization localization at the root
|
|
160
|
+
|
|
161
|
+
When the application has selected a locale and loaded its ordinary catalogs,
|
|
162
|
+
adapt only Capillary UI-authored messages into the same runtime composition:
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
const capillaryUiMessages: CapillaryUiMessageOverrides = {
|
|
166
|
+
toolbarLabel: i18n.t('capillaryUi.toolbar.label'),
|
|
167
|
+
dataTableEmpty: i18n.t('capillaryUi.table.empty'),
|
|
168
|
+
tableSortColumnLabel: (label) => i18n.t('capillaryUi.table.sort', {label}),
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
document.documentElement.lang = i18n.locale
|
|
172
|
+
|
|
173
|
+
const runtime = createCapillaryUiRuntime({
|
|
174
|
+
router,
|
|
175
|
+
localization: {locale: i18n.locale, messages: capillaryUiMessages},
|
|
176
|
+
})
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Keep screen headings, navigation items, field labels, validation text, and
|
|
180
|
+
domain messages in the application's catalogs and pass their resolved values
|
|
181
|
+
as ordinary props/content. Capillary UI applies English fallback to omitted internal
|
|
182
|
+
keys and uses the configured locale for calendar display names and numerals. It does not load the
|
|
183
|
+
catalog, change `lang`/`dir`, or switch the runtime's locale reactively. If the
|
|
184
|
+
application changes language in place, recreate its Capillary UI runtime tree with a
|
|
185
|
+
new static localization snapshot. The application also owns direction and RTL
|
|
186
|
+
policy.
|
|
187
|
+
|
|
188
|
+
The header remains mounted; it can still update a title or status. Remaining
|
|
189
|
+
mounted also differs from remaining visible: in a document-flow application,
|
|
190
|
+
a persistent header may scroll off screen. Section 4 explains the sizing
|
|
191
|
+
choice. The example's `active-only` policy is a deliberate choice, not the
|
|
192
|
+
default; section 3 explains alternatives.
|
|
193
|
+
|
|
194
|
+
### Apply the same pattern within a screen
|
|
195
|
+
|
|
196
|
+
A record workspace can keep its navigator and selected record while changing
|
|
197
|
+
only the summary/history tab. In this excerpt, `selection` is an
|
|
198
|
+
application-owned writable record-key emitter shared by the three application
|
|
199
|
+
components; its owner outlives both tab contents.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
<Layout horizontal allocation="flexible" ariaLabel="Record workspace">
|
|
203
|
+
<Sidebar allocation="natural" className="record-navigation"
|
|
204
|
+
island header="Records">
|
|
205
|
+
<RecordNavigator selection={selection} />
|
|
206
|
+
</Sidebar>
|
|
207
|
+
<Panel allocation="flexible" island header="Selected record">
|
|
208
|
+
<TabPanel label="Record sections" className="cap-size-flexible"
|
|
209
|
+
mountPolicy="lazy">
|
|
210
|
+
<Tab id="summary" label="Summary">
|
|
211
|
+
<RecordSummary selection={selection} />
|
|
212
|
+
</Tab>
|
|
213
|
+
<Tab id="history" label="History">
|
|
214
|
+
<RecordHistory selection={selection} />
|
|
215
|
+
</Tab>
|
|
216
|
+
</TabPanel>
|
|
217
|
+
</Panel>
|
|
218
|
+
</Layout>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Application CSS sets the navigator's width. `Sidebar` owns scrolling for its
|
|
222
|
+
contents; `TabPanel` provides scrolling tabpanel containers. The two islands
|
|
223
|
+
are siblings. The tab panel does not introduce another island inside the
|
|
224
|
+
selected-record surface.
|
|
225
|
+
|
|
226
|
+
This local arrangement persists while switching its tabs. Its lifetime when
|
|
227
|
+
leaving the whole screen is a separate top-level outlet decision. Add route
|
|
228
|
+
descriptors to the tabs when those destinations should be addressable through
|
|
229
|
+
the router; local tab selection alone does not require URLs.
|
|
230
|
+
|
|
231
|
+
### Share definitions and instances deliberately
|
|
232
|
+
|
|
233
|
+
Two pages can each use the same layout component and receive separate mounted
|
|
234
|
+
instances. This centralizes structure without making their controls, selection,
|
|
235
|
+
or scroll positions global. If the same region should survive navigation, move
|
|
236
|
+
its owning instance above the relevant outlet instead.
|
|
237
|
+
|
|
238
|
+
A shared sidebar belongs in the shell when its purpose and desired lifetime
|
|
239
|
+
are application-wide. Sidebars with different page-specific controls can stay
|
|
240
|
+
inside their pages, even when they share width, styling, and arrangement.
|
|
241
|
+
|
|
242
|
+
When a page changes content in shared chrome, first determine whether that
|
|
243
|
+
content needs to live there. Page-specific actions can often remain in the
|
|
244
|
+
page's toolbar. For a genuinely shared header or status region, let the shell
|
|
245
|
+
compose content from the active selection and application-owned values or
|
|
246
|
+
callbacks. A supplied outlet `valueEmitter` can also be observed by other
|
|
247
|
+
regions; only the outlet registers the routes. Keep presentation markup in
|
|
248
|
+
components and data/operations in services. Avoid having mounted pages locate
|
|
249
|
+
and modify shell DOM or leave global toolbar registrations behind on exit.
|
|
250
|
+
|
|
251
|
+
### Arrange form fields in vertical groups
|
|
252
|
+
|
|
253
|
+
Controls in a form read top to bottom. Choose the grouping component by the
|
|
254
|
+
relationship among its contents: `OptionGroup` is a fieldset of *peer*
|
|
255
|
+
controls answering one narrow concern — its legend names the question —
|
|
256
|
+
while `GroupBox` gathers *distinct* fields under one broader subject, its
|
|
257
|
+
header naming the topic. Stack each group's controls in a vertical `Layout`.
|
|
258
|
+
When a form has several groups, place them side by side in a horizontal
|
|
259
|
+
`Layout` so the columns use the available width and wrap when it runs out;
|
|
260
|
+
each group still owns its vertical field order.
|
|
261
|
+
|
|
262
|
+
```tsx
|
|
263
|
+
<Panel header="Connection" context="form">
|
|
264
|
+
<Layout horizontal className="form-groups">
|
|
265
|
+
<GroupBox header="Server">
|
|
266
|
+
<Layout vertical>
|
|
267
|
+
<Textbox label="Host" valueEmitter={state.host} />
|
|
268
|
+
<Textbox label="Port" valueEmitter={state.port} />
|
|
269
|
+
</Layout>
|
|
270
|
+
</GroupBox>
|
|
271
|
+
<GroupBox header="Credentials">
|
|
272
|
+
<Layout vertical>
|
|
273
|
+
<Textbox label="User" valueEmitter={state.user} />
|
|
274
|
+
<Textbox label="Password" type="password"
|
|
275
|
+
valueEmitter={state.password} />
|
|
276
|
+
</Layout>
|
|
277
|
+
</GroupBox>
|
|
278
|
+
<OptionGroup label="Protocol">
|
|
279
|
+
<Layout vertical>
|
|
280
|
+
<Checkbox label="TLS" valueEmitter={state.tls} />
|
|
281
|
+
<Checkbox label="Compression"
|
|
282
|
+
valueEmitter={state.compression} />
|
|
283
|
+
</Layout>
|
|
284
|
+
</OptionGroup>
|
|
285
|
+
</Layout>
|
|
286
|
+
</Panel>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
```css
|
|
290
|
+
.form-groups { flex-wrap: wrap; align-items: flex-start; gap: 1rem 2rem; }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
`Panel` and `Layout` accept `context="control" | "form"` to declare a
|
|
294
|
+
presentation context for their descendants; the nearest marked ancestor
|
|
295
|
+
wins. In a `form` context `GroupBox` keeps its border but drops the
|
|
296
|
+
sidebar-weighted header chrome — the section header becomes a plain
|
|
297
|
+
text-colored label above the content — while the unmarked default keeps
|
|
298
|
+
the control presentation.
|
|
299
|
+
`OptionsBox` is the `GroupBox` specialization for sidebar panels of
|
|
300
|
+
`OptionGroup`s. Prefer these components over anonymous wrappers: the
|
|
301
|
+
fieldset, legend, and vertical rhythm are the contract a theme styles.
|
|
302
|
+
|
|
303
|
+
## 3. Choose state lifetime and transition boundaries
|
|
304
|
+
|
|
305
|
+
Decide what the user should find when returning to a screen. A draft may need
|
|
306
|
+
to survive navigation; a hover detail may not. Preserving a filter value does
|
|
307
|
+
not necessarily require preserving the entire results DOM.
|
|
308
|
+
|
|
309
|
+
`RouteOutlet` and `TabPanel` offer the same `mountPolicy` choices:
|
|
310
|
+
|
|
311
|
+
| Policy | Content lifetime | Use when |
|
|
312
|
+
| --- | --- | --- |
|
|
313
|
+
| `eager` (default) | Mount every branch and retain it | All branches should initialize immediately |
|
|
314
|
+
| `lazy` | Mount on first selection, then retain visited branches | Reusing visited component/DOM state matters |
|
|
315
|
+
| `active-only` | Destroy inactive content and mount the selected branch | Recreating views is appropriate and inactive content should be released |
|
|
316
|
+
|
|
317
|
+
Retained content remains mounted and can keep subscriptions and queries active.
|
|
318
|
+
It is not automatically paused while hidden. Destroying a view cleans up its
|
|
319
|
+
rendered subtree and renderer-managed subscriptions and runs its cleanup hooks.
|
|
320
|
+
Arrange disposal of application-created queries, emitters, and subscriptions
|
|
321
|
+
through their owner's `onCleanup()` or `onDestroy()`; simply storing an object
|
|
322
|
+
in a component field does not arrange its disposal. A destroyed view does not
|
|
323
|
+
dispose a shared application service or stop work owned elsewhere. Choose query
|
|
324
|
+
activation and disposal with the same care as component lifetime. Capillary UI does not
|
|
325
|
+
fetch merely because a route exists.
|
|
326
|
+
|
|
327
|
+
Keep state that must survive a destroyed view in an owner that outlives it,
|
|
328
|
+
such as its enclosing workspace or an application-owned service. Keep local
|
|
329
|
+
state local when its lifetime should match the view. Create emitters, queries,
|
|
330
|
+
and other owned objects at their lifetime boundary, rather than creating new
|
|
331
|
+
ones on every render. Layout convenience is not a reason to mirror all values
|
|
332
|
+
into a global UI store.
|
|
333
|
+
|
|
334
|
+
Use recreatable TSX/VNodes for `active-only` branches. A destroyed component
|
|
335
|
+
instance cannot be mounted again. Use stable keys to preserve sibling identity;
|
|
336
|
+
changing a key intentionally resets that subtree. Neither retained DOM nor
|
|
337
|
+
long-lived state implies persistence across a browser reload.
|
|
338
|
+
|
|
339
|
+
### Confine loading and errors to the affected work
|
|
340
|
+
|
|
341
|
+
If only results are loading, keep the shell, filters, and useful actions
|
|
342
|
+
available. A results component can observe a query snapshot and choose loading,
|
|
343
|
+
error, empty, or populated output inside its assigned region. Returning a
|
|
344
|
+
spinner before rendering the entire page frame removes that frame and its
|
|
345
|
+
local component state too.
|
|
346
|
+
|
|
347
|
+
Decide whether a refresh keeps the previous result visible with a busy indicator
|
|
348
|
+
or clears it. Disable actions whose prerequisites are unavailable rather than
|
|
349
|
+
disabling unrelated navigation. An empty result still occupies a meaningful
|
|
350
|
+
results region in a fullscreen workspace. Application-wide startup failures
|
|
351
|
+
may justify a broader boundary; the scope should match what is unavailable.
|
|
352
|
+
|
|
353
|
+
Details opened by a selection follow the same principle: the page owns the
|
|
354
|
+
selection and close action, while a details component presents the selected
|
|
355
|
+
information. A page can conditionally include details without making every
|
|
356
|
+
shared layout aware of selection policy.
|
|
357
|
+
|
|
358
|
+
See [component lifecycle](../README.md#components-and-lifecycle),
|
|
359
|
+
[reactive templates](../README.md#reactive-templates),
|
|
360
|
+
[application services](../README.md#application-services), and
|
|
361
|
+
[routing](../README.md#browser-routing) for implementation contracts.
|
|
362
|
+
|
|
363
|
+
## 4. Choose viewport allocation or document flow
|
|
364
|
+
|
|
365
|
+
Make this choice from the intended interaction, before adding scrollbars.
|
|
366
|
+
|
|
367
|
+
| Model | Extent and scrolling | Typical uses |
|
|
368
|
+
| --- | --- | --- |
|
|
369
|
+
| Viewport allocation | A bounded root allocates remaining space; designated inner regions scroll | Workspaces, editors, monitoring screens |
|
|
370
|
+
| Document flow | Content determines height; the browser document scrolls | Articles, long registers, record pages, portals |
|
|
371
|
+
|
|
372
|
+
A horizontal arrangement, a grid, or a set of islands does not decide which
|
|
373
|
+
model applies. A dashboard can use either model.
|
|
374
|
+
|
|
375
|
+
### Keep the bounded chain intact
|
|
376
|
+
|
|
377
|
+
For a viewport application, `CapillaryUiApp.sizing="viewport"` supplies the external
|
|
378
|
+
bound. The bound only holds when the document cooperates: remove the browser's
|
|
379
|
+
default `body` margin (`html, body { margin: 0 }`), which would otherwise push
|
|
380
|
+
the `100vh` root into document scrollbars. `layout="vertical"` arranges its
|
|
381
|
+
direct children. Each intermediate DOM
|
|
382
|
+
container must carry the allocation to the region that needs it:
|
|
383
|
+
|
|
384
|
+
```text
|
|
385
|
+
viewport root, vertical arrangement
|
|
386
|
+
├── header and navigation: natural
|
|
387
|
+
├── main: flexible, vertical arrangement
|
|
388
|
+
│ └── outlet and active page: carry the available space
|
|
389
|
+
│ ├── toolbar: natural
|
|
390
|
+
│ └── results: flexible, scroll owner
|
|
391
|
+
└── footer: natural
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Capillary UI's public traits express the common mechanics when a semantic native
|
|
395
|
+
element is the appropriate layout boundary:
|
|
396
|
+
|
|
397
|
+
- `cap-layout-horizontal` / `cap-layout-vertical`: arrange direct children.
|
|
398
|
+
- `cap-size-natural`: retain the content/application allocation on the parent's
|
|
399
|
+
main axis; this does not itself set a fixed width or height.
|
|
400
|
+
- `cap-size-flexible`: share remaining space and permit shrinking below
|
|
401
|
+
intrinsic content size through zero logical minimums.
|
|
402
|
+
- `cap-scroll`: make an already bounded region an overflow owner.
|
|
403
|
+
|
|
404
|
+
Flexible sizing does not imply scrolling. An auto-height wrapper does not
|
|
405
|
+
inherit a viewport bound just because a distant ancestor has one. Extracting a
|
|
406
|
+
component that adds a DOM wrapper can therefore change layout: preserve the
|
|
407
|
+
DOM shape or deliberately carry allocation through the new host.
|
|
408
|
+
|
|
409
|
+
Here is one possible `RecordsView` for the shell above. `RecordFilters` and
|
|
410
|
+
`RecordsResults` resolve or receive the application's shared view state; the
|
|
411
|
+
former renders controls and the latter renders the result/loading/error
|
|
412
|
+
content. The frame remains present through those states.
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
class RecordsView extends Component {
|
|
416
|
+
render() {
|
|
417
|
+
return <Layout horizontal allocation="flexible"
|
|
418
|
+
className="records-workspace" ariaLabel="Find records">
|
|
419
|
+
<aside className="record-filters island cap-size-natural cap-scroll"
|
|
420
|
+
aria-label="Record filters" tabIndex={0}>
|
|
421
|
+
<RecordFilters />
|
|
422
|
+
</aside>
|
|
423
|
+
<Panel allocation="flexible" header="Matching records" scroll={false}>
|
|
424
|
+
<PanelToolbar>
|
|
425
|
+
<Toolbar allocation="natural" label="Result actions">
|
|
426
|
+
<button type="button" onClick={exportRecords}>Export</button>
|
|
427
|
+
</Toolbar>
|
|
428
|
+
</PanelToolbar>
|
|
429
|
+
<Layout vertical allocation="flexible" scroll
|
|
430
|
+
ariaLabel="Record results" tabIndex={0}>
|
|
431
|
+
<RecordsResults />
|
|
432
|
+
</Layout>
|
|
433
|
+
</Panel>
|
|
434
|
+
</Layout>
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
static dependencies = [Layout, Panel, PanelToolbar, RecordFilters, RecordsResults, Toolbar]
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
`exportRecords` is an application action. Application CSS supplies dimensions
|
|
442
|
+
and spacing, for example:
|
|
443
|
+
|
|
444
|
+
```css
|
|
445
|
+
.records-workspace { gap: 1rem; }
|
|
446
|
+
.record-filters { inline-size: 18rem; }
|
|
447
|
+
.record-navigation { inline-size: 18rem; }
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
The filters and results have separate scroll owners, and the toolbar stays
|
|
451
|
+
outside the results scrollbar. The application must adapt the widths or
|
|
452
|
+
arrangement when the available space cannot accommodate both regions. Generic
|
|
453
|
+
scroll regions need appropriate accessible names and keyboard access; consider
|
|
454
|
+
the focusability already provided by their contents when choosing tab stops.
|
|
455
|
+
|
|
456
|
+
Use component arguments where they target the intended element. `Layout` and
|
|
457
|
+
`Panel` use `horizontal` or `vertical` for their arranged content, while
|
|
458
|
+
supported components use `allocation` for their outer host. `Panel` applies
|
|
459
|
+
its direction to its inner Layout body rather than its generated header or
|
|
460
|
+
toolbar. A generic layout trait on another component host might instead
|
|
461
|
+
arrange generated chrome. Support is explicit; do not assume every component
|
|
462
|
+
accepts the same layout arguments.
|
|
463
|
+
|
|
464
|
+
Components such as `Sidebar`, `Panel`, `TabPanel`, and `RouteOutlet` already
|
|
465
|
+
have layout/overflow behavior. Inspect that contract before adding another
|
|
466
|
+
scroll container. Existing ancestor overflow can be an inactive fallback;
|
|
467
|
+
verify which element actually has scroll range. Avoid concealing allocation
|
|
468
|
+
errors with blanket clipping.
|
|
469
|
+
|
|
470
|
+
### Let a document grow
|
|
471
|
+
|
|
472
|
+
For a traditional register, a content-oriented `RecordsResults` can contribute
|
|
473
|
+
its full height to the page. This is an alternative root, not a child placed
|
|
474
|
+
inside the preceding viewport shell:
|
|
475
|
+
|
|
476
|
+
```tsx
|
|
477
|
+
class RegisterApp extends CapillaryUiApp {
|
|
478
|
+
protected override renderContent() {
|
|
479
|
+
return <main className="register-page">
|
|
480
|
+
<h1>Record register</h1>
|
|
481
|
+
<RecordFilters />
|
|
482
|
+
<RecordsResults />
|
|
483
|
+
</main>
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
static dependencies = [RecordFilters, RecordsResults]
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
mountCapillaryUiApp(createCapillaryUiRuntime(), RegisterApp, document.querySelector('#app')!, {
|
|
490
|
+
sizing: 'embedded',
|
|
491
|
+
landmark: 'none',
|
|
492
|
+
})
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
```css
|
|
496
|
+
.register-page {
|
|
497
|
+
max-inline-size: 70rem;
|
|
498
|
+
margin-inline: auto;
|
|
499
|
+
padding: 1rem;
|
|
500
|
+
}
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
There is no bounded results container here. More rows increase page height.
|
|
504
|
+
The surrounding host page must also allow document flow. Setting an inner
|
|
505
|
+
component to `embedded` inside an already constrained scrolling shell does not
|
|
506
|
+
transfer scrolling to the browser document.
|
|
507
|
+
|
|
508
|
+
Keep reusable result content free of a forced viewport height when callers
|
|
509
|
+
need both uses. A deliberately bounded result widget can instead document that
|
|
510
|
+
requirement and be used only where appropriate. Large data sets still need
|
|
511
|
+
application-owned query limits, pagination, or another suitable strategy;
|
|
512
|
+
document scrolling does not remove the cost of rendering rows.
|
|
513
|
+
|
|
514
|
+
### Adapt the composition without changing the ownership rules
|
|
515
|
+
|
|
516
|
+
- A classic fullscreen shell reserves natural space for chrome and allocates
|
|
517
|
+
the rest to its workspace.
|
|
518
|
+
- A data workspace separates control allocation from a flexible results region.
|
|
519
|
+
- A workbench repeats the bounded chain through nested panes. Make size ratios
|
|
520
|
+
and each pane's scroll owner intentional. `SplitView` supplies a two-pane
|
|
521
|
+
composition and accessible resizing mechanics, but not size persistence or
|
|
522
|
+
responsive policy.
|
|
523
|
+
- A fullscreen monitoring screen can allocate equal shares to similarly
|
|
524
|
+
decorated sibling regions with independent scrolling. Equal flexible growth
|
|
525
|
+
does not guarantee equal outer boxes with different padding or borders.
|
|
526
|
+
- Articles and registers grow in document flow; record/detail and portal pages
|
|
527
|
+
can add application-owned columns or grids while retaining that behavior.
|
|
528
|
+
|
|
529
|
+
Application CSS owns precise widths, ratios, gaps, maximum sizes, and responsive
|
|
530
|
+
rearrangement. Capillary UI does not provide breakpoint variants. Prefer responsive CSS
|
|
531
|
+
when the same component tree can serve the smaller layout, and keep visual,
|
|
532
|
+
reading, and keyboard order coherent. If a changed structure remounts content,
|
|
533
|
+
account for its state and focus lifetime explicitly.
|
|
534
|
+
|
|
535
|
+
See [root sizing](../README.md#root-sizing-and-typography) and
|
|
536
|
+
[layout traits](../README.md#reusable-traits) for the public contract.
|
|
537
|
+
|
|
538
|
+
## 5. Extract components that establish a useful contract
|
|
539
|
+
|
|
540
|
+
A reader should see the screen's major regions, their contents, and the values
|
|
541
|
+
connecting them. The internals of a known component can stay behind its API.
|
|
542
|
+
Readable composition does not require placing every control in one large
|
|
543
|
+
render method.
|
|
544
|
+
|
|
545
|
+
Use this decision table when similar markup appears:
|
|
546
|
+
|
|
547
|
+
| What is actually shared? | A useful starting point |
|
|
548
|
+
| --- | --- |
|
|
549
|
+
| Styling, spacing, or widths, with varying anatomy | Native markup and shared CSS traits/tokens |
|
|
550
|
+
| A fixed arrangement with a few meaningful content regions | A shared layout component accepting parent-specific named region children |
|
|
551
|
+
| An accessible interaction or recognizable widget | A component such as `GroupBox`, `Sidebar`, or a domain-specific presentation |
|
|
552
|
+
| A substantial part of one screen | A component colocated with that screen, even if it has only one caller |
|
|
553
|
+
| Mostly another component's props, passed straight through | Keep the direct use unless the wrapper adds a meaningful contract |
|
|
554
|
+
|
|
555
|
+
There is no fixed number of repeated lines or callers that makes extraction
|
|
556
|
+
correct. Ask whether these structures should change together and whether the
|
|
557
|
+
new API lets readers trust what is hidden. Two visually similar sections may
|
|
558
|
+
need to evolve independently.
|
|
559
|
+
|
|
560
|
+
For example, a `FilterSidebar` that only renders supplied children followed by
|
|
561
|
+
a status-filter panel hides their ordering without owning a sidebar or useful
|
|
562
|
+
behavior. Declaring those siblings in the view can be clearer. A `RecordFilters`
|
|
563
|
+
component that owns a recognizable set of application filters and their reset
|
|
564
|
+
interaction provides a stronger contract, even if its implementation is small.
|
|
565
|
+
|
|
566
|
+
### Pass content as content
|
|
567
|
+
|
|
568
|
+
Use props to configure a component. Use ordinary children for ordered content
|
|
569
|
+
in one region. Use parent-specific named region children when a template has
|
|
570
|
+
several distinct content roles.
|
|
571
|
+
|
|
572
|
+
Configuration props include identifiers, short labels, state bindings,
|
|
573
|
+
callbacks, allocation modes, accessibility names, and other values that remain
|
|
574
|
+
easy to read on the component's opening tag. A compact heading such as
|
|
575
|
+
`header="Display options"` is also reasonable there. Props should rarely carry
|
|
576
|
+
a substantial `CapillaryUiChild` tree: important structure becomes punctuation-heavy
|
|
577
|
+
and disappears from the visible parent/child hierarchy.
|
|
578
|
+
|
|
579
|
+
Choose the content API from what the parent does with it:
|
|
580
|
+
|
|
581
|
+
| Content relationship | Preferred API |
|
|
582
|
+
| --- | --- |
|
|
583
|
+
| One body whose children render in authored sequence | Ordinary children |
|
|
584
|
+
| A homogeneous ordered collection | Ordered declarative item children such as `Tab` |
|
|
585
|
+
| Several regions with different roles | Parent-specific declarative region children |
|
|
586
|
+
| Elements genuinely generated from metadata | A typed data/model prop |
|
|
587
|
+
|
|
588
|
+
If a component simply renders several supplied elements in one panel body,
|
|
589
|
+
ordinary children are already the ordered contract. Do not assign special
|
|
590
|
+
meaning to child indexes unnecessarily:
|
|
591
|
+
|
|
592
|
+
`GroupBox` owns its labeled group structure and presentation. Its caller owns
|
|
593
|
+
the controls. For example, `state.colorBy` and `state.relativeTo` below are
|
|
594
|
+
writable emitters created by the view's state owner:
|
|
595
|
+
|
|
596
|
+
```tsx
|
|
597
|
+
<GroupBox header="Display options">
|
|
598
|
+
<RadioGroup
|
|
599
|
+
label="Colors represent"
|
|
600
|
+
options={[
|
|
601
|
+
['status', 'Status'],
|
|
602
|
+
['owner', 'Owner'],
|
|
603
|
+
]}
|
|
604
|
+
valueEmitter={state.colorBy}
|
|
605
|
+
/>
|
|
606
|
+
<RadioGroup
|
|
607
|
+
label="Compare against"
|
|
608
|
+
options={[
|
|
609
|
+
['selection', 'Selection'],
|
|
610
|
+
['all', 'All records'],
|
|
611
|
+
]}
|
|
612
|
+
valueEmitter={state.relativeTo}
|
|
613
|
+
/>
|
|
614
|
+
</GroupBox>
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
This keeps the controls, their order, and their bindings visible while sharing
|
|
618
|
+
the group chrome. `OptionsBox` provides a more specific arrangement for
|
|
619
|
+
option groups when that is the intended structure.
|
|
620
|
+
|
|
621
|
+
A wrapper that accepts an array of radio-group specifications merely to
|
|
622
|
+
reconstruct these elements adds another authoring format and must forward each
|
|
623
|
+
control capability. Prefer the existing composition when the structure is
|
|
624
|
+
authored directly. Data-driven definitions are appropriate when the controls
|
|
625
|
+
really come from metadata or when the component owns a meaningful model.
|
|
626
|
+
|
|
627
|
+
When regions have different meanings, name them with parent-specific
|
|
628
|
+
components. SplitView's named regions are specialized Layout panes rather than
|
|
629
|
+
non-visual markers:
|
|
630
|
+
|
|
631
|
+
```tsx
|
|
632
|
+
<SplitView horizontal allocation="flexible" primarySize="18rem"
|
|
633
|
+
separatorLabel="Resize record navigation">
|
|
634
|
+
<SplitPrimary vertical scroll label="Record navigation">
|
|
635
|
+
<RecordNavigator selection={selection} />
|
|
636
|
+
</SplitPrimary>
|
|
637
|
+
<SplitSecondary vertical scroll label="Record details">
|
|
638
|
+
<RecordSummary selection={selection} />
|
|
639
|
+
<RecordHistory selection={selection} />
|
|
640
|
+
</SplitSecondary>
|
|
641
|
+
</SplitView>
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
SplitView owns the separator's pointer and keyboard behavior and reports sizes;
|
|
645
|
+
the application owns persistence and responsive policy. `PanelToolbar`,
|
|
646
|
+
`SidebarToolbar`, `DialogActions`, and
|
|
647
|
+
`OptionGroupHeaderEnd` provide the corresponding named insertion points for
|
|
648
|
+
those components. Their contents remain nested in the call site instead of
|
|
649
|
+
being hidden in `toolbar={...}` or `actions={...}` props. Short heading and
|
|
650
|
+
label props remain configuration:
|
|
651
|
+
|
|
652
|
+
```tsx
|
|
653
|
+
<Panel header="Matching records">
|
|
654
|
+
<PanelToolbar>
|
|
655
|
+
<Toolbar label="Result actions">
|
|
656
|
+
<Button label="Export" onClick={exportRecords} />
|
|
657
|
+
</Toolbar>
|
|
658
|
+
</PanelToolbar>
|
|
659
|
+
<RecordsResults />
|
|
660
|
+
</Panel>
|
|
661
|
+
```
|
|
662
|
+
|
|
663
|
+
Use positional region assignment only when every position receives the same
|
|
664
|
+
treatment and ordering is the complete meaning—for example, an equal-panel
|
|
665
|
+
component that wraps each ordinary child in the same panel. If “first” means
|
|
666
|
+
navigation and “second” means workspace, explicit names are more resilient and
|
|
667
|
+
readable.
|
|
668
|
+
|
|
669
|
+
Prefer semantic marker names such as `ShellHeader` and `ShellContent` over a
|
|
670
|
+
universal `<Slot name="header">`. The supported anatomy is then visible in the
|
|
671
|
+
import and TSX types; two parents cannot silently give the same string name
|
|
672
|
+
different contracts. The shared parsing mechanism may be generic, but the
|
|
673
|
+
public composition language should describe the region's role.
|
|
674
|
+
|
|
675
|
+
### Define a component with named regions
|
|
676
|
+
|
|
677
|
+
Application-defined templates can extend `DeclarativeRegion` for each role and
|
|
678
|
+
use `readDeclarativeRegions()` to consume their direct children:
|
|
679
|
+
|
|
680
|
+
```tsx
|
|
681
|
+
class ShellHeader extends DeclarativeRegion {}
|
|
682
|
+
class ShellContent extends DeclarativeRegion {}
|
|
683
|
+
class ShellFooter extends DeclarativeRegion {}
|
|
684
|
+
|
|
685
|
+
class ApplicationShell extends Component {
|
|
686
|
+
render() {
|
|
687
|
+
const {regions} = readDeclarativeRegions(
|
|
688
|
+
'ApplicationShell',
|
|
689
|
+
this.props.children,
|
|
690
|
+
{
|
|
691
|
+
header: ShellHeader,
|
|
692
|
+
content: ShellContent,
|
|
693
|
+
footer: ShellFooter,
|
|
694
|
+
},
|
|
695
|
+
{allowContent: false, required: ['content']},
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
return <Layout vertical className="application-shell">
|
|
699
|
+
{regions.header == null ? null : <header>{regions.header}</header>}
|
|
700
|
+
<main className="cap-size-flexible">{regions.content}</main>
|
|
701
|
+
{regions.footer == null ? null : <footer>{regions.footer}</footer>}
|
|
702
|
+
</Layout>
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
static dependencies = [Layout, ShellHeader, ShellContent, ShellFooter]
|
|
706
|
+
}
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
The resulting use keeps the supplied anatomy visible:
|
|
710
|
+
|
|
711
|
+
```tsx
|
|
712
|
+
<ApplicationShell>
|
|
713
|
+
<ShellHeader>
|
|
714
|
+
<Brand />
|
|
715
|
+
<NavigationBar label="Application sections" items={navigationItems} />
|
|
716
|
+
</ShellHeader>
|
|
717
|
+
<ShellContent>
|
|
718
|
+
<RouteOutlet views={routes} />
|
|
719
|
+
</ShellContent>
|
|
720
|
+
<ShellFooter>
|
|
721
|
+
<ConnectionStatus />
|
|
722
|
+
</ShellFooter>
|
|
723
|
+
</ApplicationShell>
|
|
724
|
+
```
|
|
725
|
+
|
|
726
|
+
Region markers are non-visual instructions, not additional surfaces or DOM
|
|
727
|
+
wrappers. They must be direct children of the parent that documents them.
|
|
728
|
+
`readDeclarativeRegions()` preserves ordinary content order, rejects duplicate
|
|
729
|
+
or foreign region markers, can reject ordinary content, and can require named
|
|
730
|
+
regions. Keep the region set small and stable. Put reactive values inside a
|
|
731
|
+
region rather than making the template anatomy itself a changing stream.
|
|
732
|
+
|
|
733
|
+
A named region exposes where caller-owned content belongs; it does not reveal
|
|
734
|
+
or transfer the parent's other responsibilities. The parent still owns the
|
|
735
|
+
rendered landmarks, allocation, scrolling, accessibility wiring, and region
|
|
736
|
+
order. Source order should normally match rendered and keyboard order.
|
|
737
|
+
|
|
738
|
+
Expose only genuine variability. If every caller receives the same application
|
|
739
|
+
header or footer, render it inside the shell rather than adding a region merely
|
|
740
|
+
because the structure has a name. A single-use shell can remain direct markup
|
|
741
|
+
in the application root; named regions do not make an otherwise unnecessary
|
|
742
|
+
abstraction valuable.
|
|
743
|
+
|
|
744
|
+
Prefer a few meaningful regions over a universal panel whose many options
|
|
745
|
+
change its topology. If callers repeatedly need to inspect internals or target
|
|
746
|
+
private descendants to place content correctly, reconsider the boundary.
|
|
747
|
+
|
|
748
|
+
### Make a shared layout's promises explicit
|
|
749
|
+
|
|
750
|
+
A small layout component can earn its place by preserving a reliable sizing
|
|
751
|
+
chain and scroll boundary. State its contract in ordinary terms:
|
|
752
|
+
|
|
753
|
+
> This workspace fills its parent's allocated space, keeps a toolbar above
|
|
754
|
+
> the results, and gives the results region the scrollbar.
|
|
755
|
+
|
|
756
|
+
Document what space it expects, how its host participates, where content is
|
|
757
|
+
placed, and whether it owns scrolling or delegates it. If it deliberately
|
|
758
|
+
supports both document flow and bounded allocation, make those modes explicit
|
|
759
|
+
and verify both. It need not expose every CSS property as a prop.
|
|
760
|
+
|
|
761
|
+
Sharing CSS centralizes presentation but leaves structural markup repeated.
|
|
762
|
+
Extracting a layout centralizes structural changes but asks readers to learn
|
|
763
|
+
its contract. Choose based on the changes that should remain coordinated.
|
|
764
|
+
Keep arrangement and scrolling visible in the TSX of the component that owns
|
|
765
|
+
them; callers can then rely on its documented contract.
|
|
766
|
+
|
|
767
|
+
### Prefer composition for screens; use inheritance deliberately
|
|
768
|
+
|
|
769
|
+
A view can contain a layout and supply its contents. Making every view inherit
|
|
770
|
+
from a layout spreads the declaration across overridden methods and does not
|
|
771
|
+
make the layout instance persist across navigation. Composition is the usual
|
|
772
|
+
choice for assembling application screens.
|
|
773
|
+
|
|
774
|
+
Inheritance remains useful where Capillary UI provides an intentional specialization
|
|
775
|
+
contract. An application root can extend `CapillaryUiApp` and override
|
|
776
|
+
`renderContent()`, as above. `OptionsBox` extends `GroupBox` to specialize
|
|
777
|
+
shared chrome. Such examples do not require an application-wide hierarchy of
|
|
778
|
+
page base classes.
|
|
779
|
+
|
|
780
|
+
## 6. Share styling without multiplying surfaces
|
|
781
|
+
|
|
782
|
+
Use an island for a meaningful work surface: a navigation area, a results
|
|
783
|
+
workspace, or a substantial analysis region. Do not turn every extracted
|
|
784
|
+
component into a separate island. Several controls, groups, and data views can
|
|
785
|
+
belong to one island; islands must not nest.
|
|
786
|
+
|
|
787
|
+
Let the composition that knows the surrounding surfaces choose island
|
|
788
|
+
placement. A reusable inner component should normally leave that choice to
|
|
789
|
+
its caller. The `island` prop or class supplies surface treatment, not space
|
|
790
|
+
allocation or the intended scroll owner. A `GroupBox` inside an island can
|
|
791
|
+
retain its ordinary group chrome without itself being another island.
|
|
792
|
+
|
|
793
|
+
Use native elements for native semantics. Apply Capillary UI's public traits directly
|
|
794
|
+
to those elements when they participate in Capillary UI layout or presentation. For
|
|
795
|
+
example, an application shell can naturally use
|
|
796
|
+
`<header className="island cap-size-natural">` and
|
|
797
|
+
`<footer className="island cap-size-natural">`; a Capillary UI-specific header or
|
|
798
|
+
footer component is not required merely to obtain the standard surface
|
|
799
|
+
treatment.
|
|
800
|
+
|
|
801
|
+
Conversely, Capillary UI does not infer that treatment from the native element type
|
|
802
|
+
alone. A plain `header`, `footer`, `section`, or `aside` remains ordinary
|
|
803
|
+
application markup until the application explicitly opts it into a Capillary UI trait or
|
|
804
|
+
places it inside a Capillary UI-owned component contract. Share application CSS through
|
|
805
|
+
meaningful traits rather than extracting components solely to attach a class.
|
|
806
|
+
Components own their structural presentation; themes and palette assets supply
|
|
807
|
+
the chosen visual treatment. Follow the
|
|
808
|
+
[styling contract](../README.md#styling-contract) rather than duplicating
|
|
809
|
+
internal component styles in every screen.
|
|
810
|
+
|
|
811
|
+
Development controls are another application-owned boundary. If an app provides
|
|
812
|
+
forced loading, disabled, or validation states for testing, apply them to the
|
|
813
|
+
intended content while leaving the controls that restore normal operation
|
|
814
|
+
usable. A demo harness is not a required layer of every Capillary UI application.
|
|
815
|
+
|
|
816
|
+
## 7. Review the design through real transitions
|
|
817
|
+
|
|
818
|
+
Before treating a composition or shared layout as established, check:
|
|
819
|
+
|
|
820
|
+
- Can a reader identify the screen's task, major regions, and control/result
|
|
821
|
+
relationships without opening a chain of forwarding wrappers?
|
|
822
|
+
- Does navigation replace only the intended content? Test return navigation and
|
|
823
|
+
direct nested URLs when routing is enabled.
|
|
824
|
+
- Do drafts, selections, and filters survive or reset deliberately? Are owned
|
|
825
|
+
subscriptions and queries released at the right lifetime boundary?
|
|
826
|
+
- During loading, refresh, error, and empty results, do useful controls remain
|
|
827
|
+
available and is feedback confined to the affected region?
|
|
828
|
+
- With overflowing content, which elements actually scroll? In a viewport
|
|
829
|
+
layout, verify that content does not accidentally grow the document or
|
|
830
|
+
create competing ancestor scrollbars.
|
|
831
|
+
- With little or no content, does a bounded workspace still fill its allocation
|
|
832
|
+
and keep bottom chrome in place? In a document layout, does more content grow
|
|
833
|
+
the document naturally?
|
|
834
|
+
- At narrow widths and enlarged text, do controls remain reachable? Check
|
|
835
|
+
landmarks, headings, region names, keyboard order, and focus after content
|
|
836
|
+
changes. Do not rely on clipping to make geometry appear correct.
|
|
837
|
+
- Are shared structure and styling centralized where they should change
|
|
838
|
+
together, while page-specific decisions remain easy to find?
|
|
839
|
+
|
|
840
|
+
For reusable layouts, browser geometry checks can verify bounds and actual
|
|
841
|
+
scroll ranges; visual and keyboard review checks how the composition feels to
|
|
842
|
+
use. A screenshot of one populated desktop state does not establish the whole
|
|
843
|
+
contract.
|