@altertable/data-app 0.64.0 → 0.66.0
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/AGENTS.md +40 -0
- package/CONTRIBUTING.md +37 -3
- package/README.md +2 -2
- package/dist/chunks/{contract-mwe7gnmh.js → contract-13j5zb3c.js} +110 -216
- package/dist/chunks/contract-13j5zb3c.js.map +13 -0
- package/dist/chunks/contract-19ckn3n7.js +78 -0
- package/dist/chunks/contract-19ckn3n7.js.map +10 -0
- package/dist/chunks/contract-awa2d5b1.js +272 -0
- package/dist/chunks/contract-awa2d5b1.js.map +12 -0
- package/dist/chunks/{contract-xtxza1fy.js → contract-e11m4y7f.js} +35 -71
- package/dist/chunks/contract-e11m4y7f.js.map +12 -0
- package/dist/chunks/{contract-zr3jd7mr.js → contract-h8a6f559.js} +100 -75
- package/dist/chunks/contract-h8a6f559.js.map +12 -0
- package/dist/chunks/contract-havnjmdr.js +13011 -0
- package/dist/chunks/contract-havnjmdr.js.map +99 -0
- package/dist/chunks/{contract-txjy0en2.js → contract-m6n8ctfc.js} +64 -72
- package/dist/chunks/contract-m6n8ctfc.js.map +10 -0
- package/dist/chunks/contract-pk603qj7.js +146 -0
- package/dist/chunks/contract-pk603qj7.js.map +10 -0
- package/dist/chunks/{contract-yxbjea23.js → contract-rfbakka4.js} +179 -2
- package/dist/chunks/contract-rfbakka4.js.map +13 -0
- package/dist/client/index.js +11 -6
- package/dist/client/index.js.map +1 -1
- package/dist/core/appearance.js +1 -1
- package/dist/core/contract.js +13 -3
- package/dist/core/contract.js.map +1 -1
- package/dist/embed/index.js +25 -13
- package/dist/embed/index.js.map +3 -3
- package/dist/local.js +1 -1
- package/dist/local.js.map +2 -2
- package/dist/react/embed/index.js +5 -2
- package/dist/react/embed/index.js.map +3 -3
- package/dist/react/index.js +2532 -11273
- package/dist/react/index.js.map +16 -88
- package/dist/react/ui/index.js +1614 -0
- package/dist/react/ui/index.js.map +22 -0
- package/dist/server.js +1 -1
- package/dist/server.js.map +2 -2
- package/dist/types/client/annotations.d.ts +23 -0
- package/dist/types/client/iframe.d.ts +2 -0
- package/dist/types/client/index.d.ts +2 -0
- package/dist/types/client/logger.d.ts +6 -0
- package/dist/types/core/annotations.d.ts +92 -0
- package/dist/types/core/appearance.d.ts +2 -2
- package/dist/types/core/bridge-endpoint.d.ts +21 -0
- package/dist/types/core/bridge.d.ts +137 -16
- package/dist/types/core/contract.d.ts +2 -0
- package/dist/types/core/logger.d.ts +12 -0
- package/dist/types/core/presentation.d.ts +2 -0
- package/dist/types/core/variables.d.ts +2 -5
- package/dist/types/embed/host.d.ts +6 -2
- package/dist/types/embed/index.d.ts +1 -0
- package/dist/types/embed/source.d.ts +1 -1
- package/dist/types/react/annotations/AnnotationBar.d.ts +24 -0
- package/dist/types/react/annotations/AnnotationControls.d.ts +10 -0
- package/dist/types/react/annotations/AnnotationEditor.d.ts +15 -0
- package/dist/types/react/annotations/AnnotationMarkers.d.ts +19 -0
- package/dist/types/react/annotations/AnnotationSelectionLayer.d.ts +12 -0
- package/dist/types/react/annotations/AnnotationTarget.d.ts +9 -0
- package/dist/types/react/annotations/AnnotationTooltip.d.ts +9 -0
- package/dist/types/react/annotations/AnnotationTrigger.d.ts +10 -0
- package/dist/types/react/annotations/annotation-editor-state.d.ts +56 -0
- package/dist/types/react/annotations/annotation-screenshot.d.ts +4 -0
- package/dist/types/react/annotations/annotation-targets.d.ts +33 -0
- package/dist/types/react/annotations/getAnnotationProps.d.ts +15 -0
- package/dist/types/react/annotations/styles.d.ts +3 -0
- package/dist/types/react/annotations/useAnnotationGeometry.d.ts +22 -0
- package/dist/types/react/annotations/useDataAppAnnotations.d.ts +19 -0
- package/dist/types/react/bindings.d.ts +110 -0
- package/dist/types/react/content.d.ts +24 -11
- package/dist/types/react/embed/bridge.d.ts +1 -1
- package/dist/types/react/hooks.d.ts +29 -787
- package/dist/types/react/index.d.ts +30 -104
- package/dist/types/react/source-owner.d.ts +2 -0
- package/dist/types/react/style-contract.d.ts +60 -0
- package/dist/types/react/style-validation.d.ts +2 -0
- package/dist/types/react/ui/AboutData.d.ts +9 -1
- package/dist/types/react/ui/AppHeader.d.ts +1 -2
- package/dist/types/react/ui/AppLayout.d.ts +3 -9
- package/dist/types/react/ui/AppToolbar.d.ts +6 -7
- package/dist/types/react/ui/AreaChart.d.ts +7 -0
- package/dist/types/react/ui/BarChart.d.ts +6 -0
- package/dist/types/react/ui/{ComparisonVisual.d.ts → Comparison.d.ts} +3 -3
- package/dist/types/react/ui/ContentSkeleton.d.ts +2 -0
- package/dist/types/react/ui/DataApp.d.ts +17 -53
- package/dist/types/react/ui/DataAppFrame.d.ts +42 -0
- package/dist/types/react/ui/DataBoundary.d.ts +4 -5
- package/dist/types/react/ui/DataSection.d.ts +6 -18
- package/dist/types/react/ui/DataSectionBoundary.d.ts +30 -0
- package/dist/types/react/ui/DataValue.d.ts +9 -0
- package/dist/types/react/ui/DataWidget.d.ts +2 -1
- package/dist/types/react/ui/DateTimeTooltip.d.ts +1 -1
- package/dist/types/react/ui/ExportControl.d.ts +4 -0
- package/dist/types/react/ui/HelpPopover.d.ts +3 -4
- package/dist/types/react/ui/InspectionContext.d.ts +2 -0
- package/dist/types/react/ui/InspectionProvider.d.ts +29 -0
- package/dist/types/react/ui/LineChart.d.ts +7 -0
- package/dist/types/react/ui/MetricWidget.d.ts +4 -3
- package/dist/types/react/ui/PieChart.d.ts +8 -0
- package/dist/types/react/ui/PresentStory.d.ts +2 -5
- package/dist/types/react/ui/RefreshControl.d.ts +1 -2
- package/dist/types/react/ui/ScatterChart.d.ts +17 -0
- package/dist/types/react/ui/SearchField.d.ts +1 -2
- package/dist/types/react/ui/SearchInput.d.ts +3 -1
- package/dist/types/react/ui/Sheet.d.ts +2 -1
- package/dist/types/react/ui/Skeleton.d.ts +5 -2
- package/dist/types/react/ui/StaticDataApp.d.ts +4 -0
- package/dist/types/react/ui/TableWidget.d.ts +19 -16
- package/dist/types/react/ui/Tabs.d.ts +6 -2
- package/dist/types/react/ui/TextWidget.d.ts +2 -1
- package/dist/types/react/ui/Toast.d.ts +8 -0
- package/dist/types/react/ui/Tooltip.d.ts +4 -3
- package/dist/types/react/ui/TrendChart.d.ts +5 -0
- package/dist/types/react/ui/UpdatedAt.d.ts +1 -1
- package/dist/types/react/ui/VisualizationWidget.d.ts +12 -13
- package/dist/types/react/ui/WidgetViewTabs.d.ts +1 -1
- package/dist/types/react/ui/chart-data.d.ts +25 -0
- package/dist/types/react/ui/csv-export.d.ts +19 -0
- package/dist/types/react/ui/data-context.d.ts +18 -7
- package/dist/types/react/ui/icons.d.ts +2 -0
- package/dist/types/react/ui/index.d.ts +74 -0
- package/dist/types/react/ui/metric.d.ts +1 -1
- package/dist/types/react/ui/presentation.d.ts +5 -1
- package/dist/types/react/ui/shortcuts.d.ts +7 -1
- package/dist/types/react/view-runtime.d.ts +29 -0
- package/dist/types/react/view.d.ts +21 -4
- package/dist/types/react/widgets.d.ts +79 -0
- package/dist/worker.js +381 -79
- package/docs/app-authoring.md +64 -30
- package/docs/data-context.md +89 -0
- package/docs/embed.md +1 -1
- package/docs/formatting-and-appearance.md +52 -0
- package/docs/hosted-apps.md +2 -15
- package/docs/layout.md +33 -0
- package/docs/local-data-apps.md +1 -1
- package/docs/react-embed.md +2 -2
- package/docs/react.md +20 -254
- package/docs/stories-and-export.md +52 -0
- package/docs/styling.md +55 -0
- package/docs/ui-quality.md +20 -0
- package/docs/ui.md +41 -0
- package/docs/variables.md +98 -0
- package/docs/views.md +127 -0
- package/docs/widgets.md +142 -0
- package/examples/starter-data-app/index.tsx +89 -44
- package/package.json +13 -4
- package/dist/chunks/contract-cxr9t12b.js +0 -18
- package/dist/chunks/contract-cxr9t12b.js.map +0 -10
- package/dist/chunks/contract-mwe7gnmh.js.map +0 -13
- package/dist/chunks/contract-txjy0en2.js.map +0 -10
- package/dist/chunks/contract-xtxza1fy.js.map +0 -12
- package/dist/chunks/contract-yxbjea23.js.map +0 -12
- package/dist/chunks/contract-zr3jd7mr.js.map +0 -12
- package/dist/types/react/ui/RefreshRegion.d.ts +0 -9
- package/dist/types/react/ui/SelectableBarChart.d.ts +0 -15
- package/docs/releasing.md +0 -79
- /package/dist/types/react/ui/{comparison.d.ts → metric-comparison.d.ts} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Data context and evidence
|
|
2
|
+
|
|
3
|
+
Set `dataContext: context` in each view. The app and its sections inherit the
|
|
4
|
+
view's description, glossary, and permitted query evidence. Render registered identifiers where `<DataIdentifier>` is used.
|
|
5
|
+
|
|
6
|
+
Physical source identifiers name inspected tables and columns. Glossary entries
|
|
7
|
+
explain business meaning. Query names link those definitions and displayed claims
|
|
8
|
+
to their execution evidence. Keep all three explicit.
|
|
9
|
+
|
|
10
|
+
## Register source identifiers
|
|
11
|
+
|
|
12
|
+
Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
|
|
13
|
+
table, and field names. Use `<DataIdentifier>` in descriptions and glossary
|
|
14
|
+
entries to render those source references consistently.
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { defineDataIdentifiers } from '@altertable/data-app/react';
|
|
18
|
+
|
|
19
|
+
const identifiers = defineDataIdentifiers({
|
|
20
|
+
tables: {
|
|
21
|
+
events: {
|
|
22
|
+
catalog: 'product_analytics',
|
|
23
|
+
schema: 'analytics',
|
|
24
|
+
name: 'events',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
columns: { identity: { table: 'events', name: 'identity_uuid' } },
|
|
28
|
+
});
|
|
29
|
+
const { DataIdentifier } = identifiers;
|
|
30
|
+
|
|
31
|
+
<DataIdentifier id="tables.events" />;
|
|
32
|
+
<DataIdentifier id="columns.events.identity" />;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Pass `identifiers.definitions` to `createDataContext()` to reuse the source
|
|
36
|
+
registry.
|
|
37
|
+
|
|
38
|
+
## Bind evidence
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
const queries = defineQueryNames({ activity: 'feature-activity' });
|
|
42
|
+
// In the server operation: queryNames: queries
|
|
43
|
+
const context = createDataContext(queries)({
|
|
44
|
+
identifiers: identifiers.definitions,
|
|
45
|
+
description: (
|
|
46
|
+
<>
|
|
47
|
+
Explore activity in <DataIdentifier id="tables.events" />.
|
|
48
|
+
</>
|
|
49
|
+
),
|
|
50
|
+
glossary: {
|
|
51
|
+
identities: {
|
|
52
|
+
term: 'Tracked identities',
|
|
53
|
+
definition: (
|
|
54
|
+
<>
|
|
55
|
+
Distinct <DataIdentifier id="columns.events.identity" /> values.
|
|
56
|
+
</>
|
|
57
|
+
),
|
|
58
|
+
queryNames: [queries.activity],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const evidence = context.evidence({
|
|
63
|
+
id: 'identities',
|
|
64
|
+
glossaryIds: ['identities'],
|
|
65
|
+
queryNames: [queries.activity],
|
|
66
|
+
});
|
|
67
|
+
// activityView is declared with dataContext: context.
|
|
68
|
+
const trackedIdentities = activityView.metric(
|
|
69
|
+
{
|
|
70
|
+
id: 'identities',
|
|
71
|
+
glossaryId: 'identities',
|
|
72
|
+
format: { kind: 'count' },
|
|
73
|
+
},
|
|
74
|
+
data => ({ current: data.count })
|
|
75
|
+
);
|
|
76
|
+
const finding = context.finding({
|
|
77
|
+
id: 'activity',
|
|
78
|
+
headline: 'What people do',
|
|
79
|
+
visual: <ActivityChart />,
|
|
80
|
+
evidence: { id: 'activity-evidence', queryNames: [queries.activity] },
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Import `defineQueryNames()` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`.
|
|
85
|
+
|
|
86
|
+
Use `view.metric(definition, select)` to register and bind a metric in one call.
|
|
87
|
+
Declare dataset evidence references inside `view.dataset()`; the view registers
|
|
88
|
+
and validates them too.
|
|
89
|
+
Widgets and stories share its values and evidence; see [datasets and metrics](widgets.md#declare-datasets-and-metrics).
|
package/docs/embed.md
CHANGED
|
@@ -73,7 +73,7 @@ app bundle; non-React apps use `createDataAppNavigation()` from `/client`.
|
|
|
73
73
|
|
|
74
74
|
`attachDataAppBridge()` also supports connection mode for a host-owned iframe. Supply
|
|
75
75
|
`connection: { type: 'origin', origin }` or `{ type: 'opaque', token }`, and an
|
|
76
|
-
`onMessage` dispatcher. Both modes use the same transport. It returns `dispose()` and `
|
|
76
|
+
`onMessage` dispatcher. Both modes use the same transport. It returns `dispose()`, `setPresentation()`, and `setLogger()` methods and owns source/origin checks, request
|
|
77
77
|
correlation, cancellation, bounded pending requests, and reconnection. Use source mode for bundle loading and token rotation. The opaque destination requires
|
|
78
78
|
wildcard delivery, but incoming messages still require the exact iframe window,
|
|
79
79
|
null origin, token, document, and session to match.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Formatting and appearance
|
|
2
|
+
|
|
3
|
+
Use package helpers so a value has the same meaning in prose, tables, charts,
|
|
4
|
+
and stories. TypeScript and JSDoc describe options and supported values.
|
|
5
|
+
|
|
6
|
+
## Format values
|
|
7
|
+
|
|
8
|
+
Import formatting helpers from `@altertable/data-app/format`.
|
|
9
|
+
|
|
10
|
+
| Helper | Use |
|
|
11
|
+
| ------------------- | ------------------------------------------------------ |
|
|
12
|
+
| `formatNumber()` | General numerical values |
|
|
13
|
+
| `formatCount()` | Nonnegative integer counts, optionally compact |
|
|
14
|
+
| `formatPercent()` | Ratios represented as a fraction, such as 0.12 for 12% |
|
|
15
|
+
| `formatMetric()` | A metric definition's count, ratio, or currency format |
|
|
16
|
+
| `formatDateRange()` | Inclusive calendar ranges |
|
|
17
|
+
| `pluralize()` | Count-dependent labels |
|
|
18
|
+
|
|
19
|
+
Missing values render distinctly from measured zero. Define metric formatting
|
|
20
|
+
once with `view.metric()`; comparisons derive from its displayed source. Previous
|
|
21
|
+
values, range labels, and favorable direction belong to that metric and displayed
|
|
22
|
+
input; see [data context](data-context.md) and [views](views.md).
|
|
23
|
+
|
|
24
|
+
Import `chartColor()` from `/react` to select colors from the configured palette.
|
|
25
|
+
`<PeriodSummary>` describes reporting periods; `<UpdatedAt>` and
|
|
26
|
+
`<DateTimeTooltip>` expose readable timestamps with exact-date details.
|
|
27
|
+
`<DataTableTimestamp>` and `<DataTableShare>` format custom table cells.
|
|
28
|
+
|
|
29
|
+
## Configure identity and appearance
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import type { DataAppConfig } from '@altertable/data-app/config';
|
|
33
|
+
|
|
34
|
+
const config = {
|
|
35
|
+
title: 'Product activity',
|
|
36
|
+
scope: { organization: 'Acme', environment: 'Production' },
|
|
37
|
+
appearance: {
|
|
38
|
+
theme: 'system',
|
|
39
|
+
accentColor: '#405d47',
|
|
40
|
+
density: 'comfortable',
|
|
41
|
+
},
|
|
42
|
+
} satisfies DataAppConfig;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Scope labels describe the configured connection; they do not grant access.
|
|
46
|
+
`dataAppTitle()` produces the scoped document title used by `mountDataApp()`.
|
|
47
|
+
|
|
48
|
+
Appearance controls the brand palette, typography, density, corner radius, and
|
|
49
|
+
elevation. Those settings apply consistently to the app instead of tuning each
|
|
50
|
+
widget. Theme preference is reader-owned in standalone apps. A trusted parent's
|
|
51
|
+
resolved theme takes precedence and hides local theme controls; see
|
|
52
|
+
[parent presentation](embed.md#parent-presentation).
|
package/docs/hosted-apps.md
CHANGED
|
@@ -4,7 +4,7 @@ Follow the [shared authoring flow](app-authoring.md) using [index.tsx](../exampl
|
|
|
4
4
|
one file with public package imports; omit server files, HTML, credentials, and
|
|
5
5
|
relative or app-alias imports.
|
|
6
6
|
|
|
7
|
-
Replace the sample SQL, parsers, filters, data context, story, and configuration with
|
|
7
|
+
Replace the sample SQL, parsers, filters, data context, CSV export, story, and configuration with
|
|
8
8
|
an exploration of the source data you inspected. The starter uses two SQL
|
|
9
9
|
`VALUES` rows, so it needs no production table.
|
|
10
10
|
|
|
@@ -13,7 +13,7 @@ For execution details, see [browser-owned operations](client.md#browser-owned-op
|
|
|
13
13
|
## Convert a local data app
|
|
14
14
|
|
|
15
15
|
1. Combine the app's operations and parsers, data context, views, story,
|
|
16
|
-
configuration, and browser entry into one `index.tsx`, following the
|
|
16
|
+
CSV export, configuration, and browser entry into one `index.tsx`, following the
|
|
17
17
|
[single-file starter](../examples/starter-data-app/index.tsx).
|
|
18
18
|
2. Replace the HTTP client with `createDataClient({ operations })`, using the
|
|
19
19
|
operation registry as a value. See [browser-owned operations](client.md#browser-owned-operations-for-bundle-apps)
|
|
@@ -24,16 +24,3 @@ For execution details, see [browser-owned operations](client.md#browser-owned-op
|
|
|
24
24
|
4. Confirm the host can query the same catalogs, tables, and fields.
|
|
25
25
|
[Verify the app](app-authoring.md#verify-the-app) in the hosted runtime against
|
|
26
26
|
the local version's filters and findings.
|
|
27
|
-
|
|
28
|
-
## Preview in this repository
|
|
29
|
-
|
|
30
|
-
```fish
|
|
31
|
-
bun install --frozen-lockfile
|
|
32
|
-
bun run build
|
|
33
|
-
bun browser-tests/server.ts
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
Open [the starter preview](http://127.0.0.1:27418/starter-data-app).
|
|
37
|
-
Its test host executes the sample SQL through the iframe bridge using SQLite.
|
|
38
|
-
|
|
39
|
-
For checks, see [Contributing](../CONTRIBUTING.md).
|
package/docs/layout.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Layout contract
|
|
2
|
+
|
|
3
|
+
The shell owns page width, outer gutters, and header/body spacing. Use `<Stack>`
|
|
4
|
+
for sections, `<Grid>` for peer widgets, and `<GridItem>` for spans. Use
|
|
5
|
+
`<TextContent>` for prose. Widgets own their internal padding; their parents own
|
|
6
|
+
external spacing. Keep widget customization inside the widget so its parent can maintain consistent
|
|
7
|
+
spacing between sections and cards.
|
|
8
|
+
|
|
9
|
+
By default, section and widget gaps use `--atbl-layout-gap`, with density configured once
|
|
10
|
+
in `DataAppConfig.appearance`. The package owns wrapping and span collapse based
|
|
11
|
+
on the available container width and configured gap. Custom length values for
|
|
12
|
+
`--atbl-layout-gap`, `--atbl-space-sm`, and `--atbl-space-md` also drive span
|
|
13
|
+
collapse; a two-column span activates only when two minimum-width tracks fit.
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
<Stack>
|
|
17
|
+
<TextContent>
|
|
18
|
+
<h2>Activity</h2>
|
|
19
|
+
<p>Explore the sources behind this finding.</p>
|
|
20
|
+
</TextContent>
|
|
21
|
+
<Grid columns={3}>
|
|
22
|
+
<GridItem span={2}>{activityChart}</GridItem>
|
|
23
|
+
<GridItem>{activityMetric}</GridItem>
|
|
24
|
+
</Grid>
|
|
25
|
+
</Stack>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Verify rendered layouts
|
|
29
|
+
|
|
30
|
+
Check computed section/widget gaps, sibling alignment, outer gutters, and
|
|
31
|
+
horizontal overflow at phone and desktop iframe widths. Check loading, empty,
|
|
32
|
+
error, ready, and stale states. Loading placeholders should use the same grid as
|
|
33
|
+
the ready content.
|
package/docs/local-data-apps.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Start from the CLI scaffold or the [local starter](https://github.com/altertable-ai/data-app/tree/main/examples/starter-local-data-app).
|
|
4
4
|
Use the starter's README for setup and its AGENTS.md to find app-owned files.
|
|
5
|
-
Replace the connectivity screen with the exploration and story from the
|
|
5
|
+
Replace the connectivity screen with the exploration, CSV export, and story from the
|
|
6
6
|
[shared authoring flow](app-authoring.md).
|
|
7
7
|
|
|
8
8
|
| Task | Documentation |
|
package/docs/react-embed.md
CHANGED
|
@@ -93,8 +93,8 @@ are mutually exclusive.
|
|
|
93
93
|
|
|
94
94
|
## Loading an embedded app
|
|
95
95
|
|
|
96
|
-
Use `<DataAppSkeleton>` from `/react` while the host builds or starts an app.
|
|
97
|
-
Call `injectDataAppShellStyles()` from `/react` before rendering the placeholder.
|
|
96
|
+
Use `<DataAppSkeleton>` from `/react/ui` while the host builds or starts an app.
|
|
97
|
+
Call `injectDataAppShellStyles()` from `/react/ui` before rendering the placeholder.
|
|
98
98
|
The host owns when to show it and supplies any surrounding header or footer.
|
|
99
99
|
`/react/embed` itself remains independent of UI components and styles.
|
|
100
100
|
|
package/docs/react.md
CHANGED
|
@@ -5,10 +5,8 @@ React 19.2 or newer and React DOM 19.2 or newer are peer dependencies.
|
|
|
5
5
|
|
|
6
6
|
## Mount the app
|
|
7
7
|
|
|
8
|
-
`mountDataApp({ config, component })`
|
|
9
|
-
|
|
10
|
-
installs `<DataAppProvider>`. When mounting through another
|
|
11
|
-
framework, wrap the app in `<DataAppProvider>` yourself.
|
|
8
|
+
Use `mountDataApp({ config, component })` to mount into `#root`. When mounting
|
|
9
|
+
through another framework, import `<DataAppProvider>` from `/react/ui`.
|
|
12
10
|
|
|
13
11
|
```tsx
|
|
14
12
|
import { injectDataAppStyles, mountDataApp } from '@altertable/data-app/react';
|
|
@@ -17,257 +15,25 @@ injectDataAppStyles();
|
|
|
17
15
|
mountDataApp({ config, component: App });
|
|
18
16
|
```
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
or separate stylesheet asset is needed.
|
|
18
|
+
Call `injectDataAppStyles()` before mounting; no separate stylesheet is needed.
|
|
19
|
+
For a restrictive CSP, pass the permitted nonce on the first call.
|
|
20
|
+
See [Styling](styling.md) for appearance, CSS tokens, and overrides.
|
|
24
21
|
|
|
25
|
-
|
|
22
|
+
Use `/react` for declared views, bound widgets, and layouts. Use
|
|
23
|
+
[`/react/ui`](ui.md) for setup/static screens and direct UI composition.
|
|
26
24
|
|
|
27
|
-
|
|
28
|
-
import { createDataClient } from '@altertable/data-app/client';
|
|
29
|
-
import {
|
|
30
|
-
createDataHooks,
|
|
31
|
-
dateRangeVariable,
|
|
32
|
-
DataApp,
|
|
33
|
-
Grid,
|
|
34
|
-
MetricWidget,
|
|
35
|
-
VisualizationWidget,
|
|
36
|
-
Ranking,
|
|
37
|
-
} from '@altertable/data-app/react';
|
|
38
|
-
import type { operations } from '#app/operations.ts';
|
|
39
|
-
import { calendar } from '#app/contracts.ts';
|
|
40
|
-
import { dataContext, trackedIdentities } from '#app/data-context.tsx';
|
|
41
|
-
import config from '#config';
|
|
42
|
-
|
|
43
|
-
const period = dateRangeVariable({
|
|
44
|
-
key: 'period',
|
|
45
|
-
contract: calendar,
|
|
46
|
-
comparison: true,
|
|
47
|
-
defaultValue: { kind: 'preset', id: 'last-30' },
|
|
48
|
-
});
|
|
49
|
-
const { defineDataView, useView } =
|
|
50
|
-
createDataHooks(createDataClient<typeof operations>());
|
|
51
|
-
const activityView = defineDataView({
|
|
52
|
-
operation: 'activity',
|
|
53
|
-
variables: { period },
|
|
54
|
-
input: ({ period }) => period,
|
|
55
|
-
date: { variable: 'period', input: input => input },
|
|
56
|
-
isEmpty: data => data.features.length === 0,
|
|
57
|
-
empty: { title: 'No activity in this range' },
|
|
58
|
-
});
|
|
59
|
-
const featureEvidence = dataContext.evidence({
|
|
60
|
-
id: 'feature-use',
|
|
61
|
-
queryNames: [dataContext.queryNames.activity],
|
|
62
|
-
});
|
|
63
|
-
const content = activityView.content(result => (
|
|
64
|
-
<Grid columns={2}>
|
|
65
|
-
<MetricWidget
|
|
66
|
-
metric={trackedIdentities}
|
|
67
|
-
reading={result.metric(data => ({
|
|
68
|
-
current: data.count,
|
|
69
|
-
previous: data.previousCount,
|
|
70
|
-
}))}
|
|
71
|
-
/>
|
|
72
|
-
<VisualizationWidget
|
|
73
|
-
title="Feature use"
|
|
74
|
-
evidence={featureEvidence}
|
|
75
|
-
reading={result.select(data => data.features)}
|
|
76
|
-
isEmpty={features => features.length === 0}
|
|
77
|
-
empty={{ title: 'No features' }}
|
|
78
|
-
skeleton={{ variant: 'ranking', rows: 6 }}
|
|
79
|
-
>
|
|
80
|
-
{features => <Ranking items={features} />}
|
|
81
|
-
</VisualizationWidget>
|
|
82
|
-
</Grid>
|
|
83
|
-
));
|
|
84
|
-
function App() {
|
|
85
|
-
const activity = useView(activityView);
|
|
86
|
-
return (
|
|
87
|
-
<DataApp
|
|
88
|
-
config={config}
|
|
89
|
-
dataContext={dataContext}
|
|
90
|
-
request={activity}
|
|
91
|
-
{...content}
|
|
92
|
-
/>
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
The `date` binding identifies the controlling variable and extracts its range from the operation input. Nested inputs use, for example, `input: (input) => input.period`. The runtime rejects mappings that silently change the selected range or comparison. Non-date views supply `describeInput`; date views can override it when other inputs also need describing.
|
|
98
|
-
|
|
99
|
-
Share the [date range contract](contract.md#shared-date-ranges) between the
|
|
100
|
-
operation parser and the view.
|
|
101
|
-
|
|
102
|
-
`useView()` generates controls for date, text and fixed-option select variables; custom controls use `result.variables.bind(name)`. `input` chooses which variables reach the operation, so local search can stay local. Hooks belong in the enclosing component.
|
|
103
|
-
|
|
104
|
-
For large tables, use query-backed pagination with a stable sort and total
|
|
105
|
-
count. Client pagination and search cover only the rows already returned.
|
|
106
|
-
|
|
107
|
-
Bound `<VisualizationWidget>` and `<TableWidget>` components require `evidence` from `context.evidence(...)`. A bound `<MetricWidget>` gets evidence from its metric definition. Evidence must name at least one glossary entry or query. Static widgets may omit it.
|
|
108
|
-
|
|
109
|
-
`<MetricWidget>` and `<ComparisonVisual>` both accept the same `metric` and `reading`. The comparison is enabled by the displayed result's range. The definition supplies formatting and evidence; a reading cannot override those or provide a second value. `favorableDirection` is optional; changes are neutral until the author defines whether up or down is favorable.
|
|
110
|
-
|
|
111
|
-
Use the [app helpers](#reuse-app-helpers) for metric formats and values in tables,
|
|
112
|
-
charts, and custom views.
|
|
113
|
-
|
|
114
|
-
`defineDataContent()` remains available for manually managed requests. Its optional `{ date: (input) => rangeRequest }` binds comparison readings. `<DataSection>` handles independent requests. Low-level widgets, tabs and layout components remain available for custom interfaces.
|
|
115
|
-
|
|
116
|
-
## Narrative text
|
|
117
|
-
|
|
118
|
-
Use `<TextContent>` for prose within a page or custom layout, and `<TextWidget>`
|
|
119
|
-
when the explanation belongs in a titled panel alongside other widgets.
|
|
120
|
-
|
|
121
|
-
Give text a purpose: frame the question, explain how to interpret a comparison,
|
|
122
|
-
qualify a finding, or suggest what to explore next. Choose the content for the
|
|
123
|
-
reader's question and the decisions the exploration supports.
|
|
124
|
-
|
|
125
|
-
For data-dependent text, use `reading={result.select((data, input) => ...)}` and
|
|
126
|
-
provide `evidence`. Derive both the explanation and its scope from those displayed
|
|
127
|
-
values so it stays consistent with the visualizations while filters change.
|
|
128
|
-
Static instructions can use ordinary children; local filters should feed the same
|
|
129
|
-
filtered data to the text and its related visualization.
|
|
130
|
-
|
|
131
|
-
## Reuse app helpers
|
|
132
|
-
|
|
133
|
-
Use the shared helpers for common app tasks. Follow the entry points below to
|
|
134
|
-
find their exports, types, and usage constraints.
|
|
135
|
-
|
|
136
|
-
| Task | Entry point |
|
|
137
|
-
| ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
138
|
-
| Format numbers, counts, percentages, currency, date ranges, and plural labels (`pluralize`) | [Format helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/format.ts) |
|
|
139
|
-
| Choose chart colors | [Chart colors](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/chartColor.ts) |
|
|
140
|
-
| Search items and highlight matches | [Search helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/searchItems.ts) |
|
|
141
|
-
| Read and synchronize URL query state | [URL state helpers](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/search.ts) |
|
|
142
|
-
| Render timestamps, freshness, table shares, periods, and metric comparisons | [React exports](https://github.com/altertable-ai/data-app/blob/main/src/react/index.ts) |
|
|
143
|
-
| Configure appearance and theme preferences | [Appearance helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/appearance.ts) |
|
|
144
|
-
| Build the app's scoped document title | [Configuration helpers](https://github.com/altertable-ai/data-app/blob/main/src/core/config.ts) |
|
|
145
|
-
|
|
146
|
-
## Register source identifiers
|
|
147
|
-
|
|
148
|
-
Use `defineDataIdentifiers()` from `/react` to register exact catalog, schema,
|
|
149
|
-
table, and field names. Use `<DataIdentifier>` in descriptions and glossary
|
|
150
|
-
entries to render those source references consistently.
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
import { defineDataIdentifiers } from '@altertable/data-app/react';
|
|
154
|
-
|
|
155
|
-
const identifiers = defineDataIdentifiers({
|
|
156
|
-
tables: {
|
|
157
|
-
events: {
|
|
158
|
-
catalog: 'product_analytics',
|
|
159
|
-
schema: 'analytics',
|
|
160
|
-
name: 'events',
|
|
161
|
-
},
|
|
162
|
-
},
|
|
163
|
-
columns: { identity: { table: 'events', name: 'identity_uuid' } },
|
|
164
|
-
});
|
|
165
|
-
const { DataIdentifier } = identifiers;
|
|
166
|
-
|
|
167
|
-
<DataIdentifier id="tables.events" />;
|
|
168
|
-
<DataIdentifier id="columns.events.identity" />;
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Pass `identifiers.definitions` to `createDataContext()` so the context carries the
|
|
172
|
-
same source registry. Source identifiers name physical data; glossary entries
|
|
173
|
-
explain its business meaning, and query evidence records how it was queried.
|
|
174
|
-
|
|
175
|
-
## Bind evidence
|
|
176
|
-
|
|
177
|
-
```tsx
|
|
178
|
-
const queries = defineQueryNames({ activity: 'feature-activity' });
|
|
179
|
-
// In the server operation: queryNames: queries
|
|
180
|
-
const context = createDataContext(queries)({
|
|
181
|
-
identifiers: identifiers.definitions,
|
|
182
|
-
description: (
|
|
183
|
-
<>
|
|
184
|
-
Explore activity in <DataIdentifier id="tables.events" />.
|
|
185
|
-
</>
|
|
186
|
-
),
|
|
187
|
-
glossary: {
|
|
188
|
-
identities: {
|
|
189
|
-
term: 'Tracked identities',
|
|
190
|
-
definition: (
|
|
191
|
-
<>
|
|
192
|
-
Distinct <DataIdentifier id="columns.events.identity" /> values.
|
|
193
|
-
</>
|
|
194
|
-
),
|
|
195
|
-
queryNames: [queries.activity],
|
|
196
|
-
},
|
|
197
|
-
},
|
|
198
|
-
});
|
|
199
|
-
const evidence = context.evidence({
|
|
200
|
-
id: 'identities',
|
|
201
|
-
glossaryIds: ['identities'],
|
|
202
|
-
queryNames: [queries.activity],
|
|
203
|
-
});
|
|
204
|
-
const trackedIdentities = context.metric({
|
|
205
|
-
id: 'actions',
|
|
206
|
-
glossaryId: 'identities',
|
|
207
|
-
label: 'Tracked identities',
|
|
208
|
-
format: { kind: 'count' },
|
|
209
|
-
});
|
|
210
|
-
const finding = context.finding({
|
|
211
|
-
id: 'activity',
|
|
212
|
-
headline: 'What people do',
|
|
213
|
-
visual: <ActivityChart />,
|
|
214
|
-
evidence: { id: 'activity-evidence', queryNames: [queries.activity] },
|
|
215
|
-
});
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
Import `defineQueryNames()` from `/contract` and the context/identifier factories from `/react`. Use the same registry in `defineOperation({ queryNames: queries, ... })`. Unknown glossary/query references fail type checks and registry validation; the server also validates returned query names.
|
|
219
|
-
|
|
220
|
-
## Time views and field filters
|
|
221
|
-
|
|
222
|
-
`createDataHooks(client).defineTimeView()` owns the `period` variable, calendar
|
|
223
|
-
controls, and displayed-period label. Declare `time: { contract, defaultValue }`,
|
|
224
|
-
an operation, `isEmpty`, and `empty`. With no additional variables, its default
|
|
225
|
-
input is the calendar request. With additional variables, it is `{ period, ...variables }`.
|
|
226
|
-
Supply an `input` mapper for a different operation shape and `bindings` to extract
|
|
227
|
-
nested period or field-filter inputs. Mappings must preserve the selected values.
|
|
228
|
-
|
|
229
|
-
`dimensionFilter()` from `/contract` requires exactly one option source: fixed `options` or a `facet`.
|
|
230
|
-
Use `defineFacetFilter()` to bind a facet operation and its typed input. The generated
|
|
231
|
-
`<DimensionPicker>` preserves cached options during refresh and failure, offers
|
|
232
|
-
missing values separately, and retains selected values absent from a result with
|
|
233
|
-
zero counts. `<SelectableBarChart>` can share controlled selection with the picker.
|
|
234
|
-
|
|
235
|
-
## Preserve displayed results
|
|
236
|
-
|
|
237
|
-
The callback in `view.content()` receives the displayed result and its original
|
|
238
|
-
input during refreshes and failures. Use that input when labeling the data.
|
|
239
|
-
|
|
240
|
-
Previous data and evidence are retained only when the input changes within the
|
|
241
|
-
same operation. Switching to another operation shows its own cached response or
|
|
242
|
-
an initial loading/error state; it never inherits another operation's result.
|
|
243
|
-
|
|
244
|
-
Operation and facet caches are scoped to the `DataClient` instance. Hooks created
|
|
245
|
-
from the same client share queries; separate clients do not share results, stale
|
|
246
|
-
data, or cancellation even under one provider. Keep client instances stable
|
|
247
|
-
across renders to preserve their cache.
|
|
248
|
-
|
|
249
|
-
## Findings and inspection
|
|
250
|
-
|
|
251
|
-
`<DataWidget>` composes a body, toolbar feedback, and footer. Widgets and their
|
|
252
|
-
inspection sheets render the same visual and controls. Keep interactive state
|
|
253
|
-
above both mounts when authoring custom children.
|
|
254
|
-
|
|
255
|
-
When a trusted parent supplies [parent presentation](embed.md#parent-presentation),
|
|
256
|
-
`<DataApp>` follows its resolved theme and suppresses local theme controls,
|
|
257
|
-
including in presentations. On an embedded surface (`surface: 'embedded'`),
|
|
258
|
-
only toolbar actions remain above the app body; the title, scope, description, and
|
|
259
|
-
footer are omitted. Variables and request states remain available.
|
|
260
|
-
|
|
261
|
-
## Present data with stories
|
|
262
|
-
|
|
263
|
-
Stories turn the exploration's findings into a presentation. Set the `story` prop on `<DataApp>`
|
|
264
|
-
to enable **Present story** in the toolbar; use `<PresentStory>` directly for a
|
|
265
|
-
custom shell. Start from the [data app starter](../examples/starter-data-app/index.tsx).
|
|
25
|
+
## Choose a task
|
|
266
26
|
|
|
267
|
-
|
|
268
|
-
|
|
27
|
+
| Task | Guide |
|
|
28
|
+
| -------------------------------------------------------- | --------------------------------------------------------- |
|
|
29
|
+
| Declare a view and handle loading, refresh, and failures | [Views](views.md) |
|
|
30
|
+
| Configure date, text, select, and dimension controls | [Variables](variables.md) |
|
|
31
|
+
| Render metrics, charts, tables, and narrative | [Widgets](widgets.md) |
|
|
32
|
+
| Compose sections and responsive grids | [Layout](layout.md) |
|
|
33
|
+
| Register sources, definitions, metrics, and evidence | [Data context](data-context.md) |
|
|
34
|
+
| Present findings and export displayed data | [Stories and export](stories-and-export.md) |
|
|
35
|
+
| Format values and configure appearance | [Formatting and appearance](formatting-and-appearance.md) |
|
|
36
|
+
| Host an iframe from React | [React embed](react-embed.md) |
|
|
269
37
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
the visible exploration. Initial loading, empty results, and initial errors have
|
|
273
|
-
no story to present.
|
|
38
|
+
Start with [app authoring](app-authoring.md) and the
|
|
39
|
+
[single-file starter](../examples/starter-data-app/index.tsx).
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Stories and CSV export
|
|
2
|
+
|
|
3
|
+
Every `<DataApp>` from `/react` supplies both `story` and `datasets`, derived from
|
|
4
|
+
the displayed snapshot. Story and export selectors do not run before a result is available.
|
|
5
|
+
Setup and static screens use `<DataApp>` from `/react/ui`; they may omit both
|
|
6
|
+
or pass a direct `CsvExport` object.
|
|
7
|
+
|
|
8
|
+
## Present data with stories
|
|
9
|
+
|
|
10
|
+
Set `story` on `<DataApp>` to enable **Present story** in the toolbar.
|
|
11
|
+
For custom shells, `/react/ui` provides `<PresentStory>`; see [direct UI composition](ui.md). Start from the [data app starter](../examples/starter-data-app/index.tsx).
|
|
12
|
+
|
|
13
|
+
Return one to four findings with a headline, a visual, and registered source
|
|
14
|
+
evidence. Set `evidence` to the registered dataset or metric binding. The app
|
|
15
|
+
validates that every finding belongs to its view.
|
|
16
|
+
|
|
17
|
+
The callback receives the displayed source and its original input, including
|
|
18
|
+
during refresh or failure. Derive the story from that snapshot so it agrees with
|
|
19
|
+
the visible exploration. Initial loading, empty results, and initial errors have
|
|
20
|
+
no story to present; the toolbar keeps its story action visible and disabled.
|
|
21
|
+
|
|
22
|
+
## Export displayed data as CSV
|
|
23
|
+
|
|
24
|
+
Select all distinct named datasets from the displayed snapshot. One dataset downloads directly as CSV. Multiple
|
|
25
|
+
datasets offer individual CSV downloads and **Export all** as a ZIP archive.
|
|
26
|
+
While no displayed snapshot is available, the export action stays visible and disabled.
|
|
27
|
+
|
|
28
|
+
Declare [datasets](widgets.md#declare-datasets-and-metrics) once and pass
|
|
29
|
+
`datasets={[countries, otherDataset]}` to `<DataApp>`. The toolbar
|
|
30
|
+
exports those datasets with raw values and a safe filename derived from the
|
|
31
|
+
displayed scope. List every distinct dataset the app shows.
|
|
32
|
+
|
|
33
|
+
Reuse bound metrics in story visuals:
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
story={snapshot => {
|
|
37
|
+
const totalReading = total.read(snapshot);
|
|
38
|
+
return [{
|
|
39
|
+
id: 'total',
|
|
40
|
+
headline: `Tracked identities: ${formatMetric(totalReading.value.current, total.definition.format)}`,
|
|
41
|
+
context: activityView.scope(snapshot),
|
|
42
|
+
visual: <MetricWidget metric={total} source={snapshot} />,
|
|
43
|
+
evidence: total,
|
|
44
|
+
}];
|
|
45
|
+
}}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Use the [format helpers](formatting-and-appearance.md) for story prose.
|
|
49
|
+
|
|
50
|
+
Dataset accessors return raw values; do not preformat numbers as display
|
|
51
|
+
labels. Null and undefined become empty cells, while measured zero stays zero.
|
|
52
|
+
The helpers handle quoting and spreadsheet formula protection.
|
package/docs/styling.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Styling
|
|
2
|
+
|
|
3
|
+
Use components and typed props first: `<Stack>` for sections, `<Grid>` for peers,
|
|
4
|
+
and `<TextContent>` for prose. Standard widgets use [views and bindings](widgets.md);
|
|
5
|
+
custom controls and direct widget shells use [`/react/ui`](ui.md).
|
|
6
|
+
|
|
7
|
+
Configure theme, palette, accent, typography, density, radius, and elevation through
|
|
8
|
+
`config.appearance`; see [appearance](formatting-and-appearance.md). Call
|
|
9
|
+
`injectDataAppStyles()` before mounting. Use one `<DataApp>` per document.
|
|
10
|
+
|
|
11
|
+
## Customize
|
|
12
|
+
|
|
13
|
+
Add app-owned classes through `className`. Render package components rather than copying their classes onto markup;
|
|
14
|
+
private descendants are not customization hooks. Public names and stable roots
|
|
15
|
+
are listed in the [typed contract](https://github.com/altertable-ai/data-app/blob/main/src/react/style-contract.ts).
|
|
16
|
+
CSS comments document [inherited defaults](https://github.com/altertable-ai/data-app/blob/main/src/react/tokens.css),
|
|
17
|
+
[interaction overrides](https://github.com/altertable-ai/data-app/blob/main/src/react/interaction.css),
|
|
18
|
+
and [focus overrides](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Focus.css).
|
|
19
|
+
Widget-specific overrides live beside their uses in
|
|
20
|
+
[metrics](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/MetricWidget.css),
|
|
21
|
+
[bar charts](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/BarChart.css),
|
|
22
|
+
[code](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/QueryList.css),
|
|
23
|
+
and [layout](https://github.com/altertable-ai/data-app/blob/main/src/react/ui/Grid.css).
|
|
24
|
+
The `--atbl-` prefix means Altertable.
|
|
25
|
+
|
|
26
|
+
```css
|
|
27
|
+
.app-action {
|
|
28
|
+
--atbl-control-height: 40px;
|
|
29
|
+
--atbl-focus-color: var(--atbl-accent);
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Set tokens on `:root` for the document or on a component for local overrides.
|
|
34
|
+
Inherited tokens are usable in app CSS. Optional overrides fall back where they
|
|
35
|
+
are consumed, so local colors, fonts, and spacing compose. Portals inherit from
|
|
36
|
+
their actual DOM ancestors. Normal unlayered app CSS overrides package rules.
|
|
37
|
+
`DataAppStyle` checks public inline custom-property names.
|
|
38
|
+
|
|
39
|
+
When overriding `--atbl-accent` directly, also choose `--atbl-on-accent` with
|
|
40
|
+
sufficient contrast. Prefer appearance configuration for brand and chart colors.
|
|
41
|
+
|
|
42
|
+
## Native controls
|
|
43
|
+
|
|
44
|
+
Use package controls when possible. For a custom native control:
|
|
45
|
+
|
|
46
|
+
```tsx
|
|
47
|
+
<button type="button" data-atbl-control="action" data-atbl-focus="ring">
|
|
48
|
+
Run
|
|
49
|
+
</button>
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
These hooks provide cursor and focus styling; keep native semantics, accessible
|
|
53
|
+
names, disabled state, and keyboard behavior. `DataAppStyleHooks` checks hook
|
|
54
|
+
values. Use `inset` focus inside clipped surfaces and `group` when a wrapper owns
|
|
55
|
+
an input's outline. See [UI quality](ui-quality.md) for rendered verification.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# UI quality
|
|
2
|
+
|
|
3
|
+
Lead with a supported finding and visible scope. Explain each chart's purpose,
|
|
4
|
+
measures, units, and source evidence. Use package typography and formatters;
|
|
5
|
+
keep primary values prominent and preserve exact values in records and exports.
|
|
6
|
+
|
|
7
|
+
Compose with `<Stack>`, `<Grid>`, and `<TextContent>`; parents own spacing and
|
|
8
|
+
widgets own padding. Let labels and values wrap, and keep tables and menus
|
|
9
|
+
scrolling inside their frames. Use the [layout](layout.md) and [styling](styling.md)
|
|
10
|
+
guides for customization.
|
|
11
|
+
|
|
12
|
+
Keep static context visible during loading. Skeletonize only dynamic content;
|
|
13
|
+
refresh, errors, and retry retain the displayed result and scope. Loading
|
|
14
|
+
geometry should match ready content.
|
|
15
|
+
|
|
16
|
+
Verify a real app at phone and desktop widths, including 320px, in both themes
|
|
17
|
+
and with enlarged text. Exercise long labels, localized values, many filters,
|
|
18
|
+
and loading, empty, refresh, and error states. Check containment, contrast,
|
|
19
|
+
keyboard focus, date/menu navigation, modal dismissal, and reduced motion.
|
|
20
|
+
Inspect rendered screenshots as well as computed styles.
|