@fluid-app/fluid-cli-widget 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +39 -4
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -3
- package/templates/default/.gitignore.template +7 -0
- package/templates/default/AGENTS.md +180 -5
- package/templates/default/README.md.template +271 -0
- package/templates/default/package.json.template +1 -0
- package/templates/default/vite.config.ts +59 -53
- package/templates/default/README.md +0 -38
- /package/templates/default/{index.html → index.html.template} +0 -0
- /package/templates/default/{manifest.ts → manifest.ts.template} +0 -0
|
@@ -2,9 +2,184 @@
|
|
|
2
2
|
|
|
3
3
|
Guidance for AI coding tools working in this standalone Fluid widget project.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
This repository is a generated Fluid widget package. Treat these instructions as the source of truth for code generated here. Do not assume the parent Fluid monorepo is present.
|
|
6
|
+
|
|
7
|
+
## Project boundary
|
|
8
|
+
|
|
9
|
+
- This project contains a widget package only. Do not scaffold droplets, portal apps, Next.js apps, Rails code, API servers, or monorepo packages here.
|
|
10
|
+
- Use React, TypeScript, Vite, and the Fluid widget CLI scripts already in `package.json`.
|
|
7
11
|
- Keep widget source under `src/widgets/` and package metadata in `manifest.ts`.
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
12
|
+
- Keep runtime registration in `src/index.ts`. Do not rewrite the global registration protocol unless the Fluid widget CLI changes.
|
|
13
|
+
- Keep package ownership in `fluid.widget.config.ts`. For droplet-owned widgets, link or set the droplet with `pnpm run widget:link` or `fluid widget link`; do not hard-code a droplet into component source.
|
|
14
|
+
- Runtime CSS belongs in `styles.css` or files imported by modules loaded by the widget builder. Import CSS from `manifest.ts` or the widget component so build artifacts are discovered.
|
|
15
|
+
|
|
16
|
+
## Manifest authoring
|
|
17
|
+
|
|
18
|
+
Use `defineWidget()` and `defineWidgetPackage()` from `@fluid-app/portal-sdk`.
|
|
19
|
+
|
|
20
|
+
### `defineWidget()` checklist
|
|
21
|
+
|
|
22
|
+
Each widget should define:
|
|
23
|
+
|
|
24
|
+
- `name`: stable, URL-safe widget name. Use letters, numbers, underscore, hyphen, or tilde. Changing it changes the generated widget type.
|
|
25
|
+
- `component`: React component that renders the widget.
|
|
26
|
+
- `displayName`, `description`, `icon`, and `category`: palette metadata for the builder.
|
|
27
|
+
- `defaultProps`: JSON-serializable default props. Do not use functions, Dates, undefined values, class instances, NaN, or Infinity.
|
|
28
|
+
- `propertySchema`: JSON-serializable editable fields for the builder property panel.
|
|
29
|
+
- `container`: usually `block` or `card`; use `inline` only for inline content and `fullscreen` only for true full-screen experiences.
|
|
30
|
+
- `resizable`: omit or set false for fixed widgets; otherwise use `true`, `horizontal`, `vertical`, `both`, or an object with horizontal/vertical booleans and optional min sizes.
|
|
31
|
+
|
|
32
|
+
### `defineWidgetPackage()` checklist
|
|
33
|
+
|
|
34
|
+
The generated package is droplet-owned:
|
|
35
|
+
|
|
36
|
+
- `scope` is the namespace passed at project creation.
|
|
37
|
+
- `packageType` must stay `droplet`.
|
|
38
|
+
- `version` must be SemVer without build metadata, such as `1.2.3` or `1.2.3-beta.1`.
|
|
39
|
+
- `remoteEntryUrl` should stay `widget.js` for the standalone publish flow.
|
|
40
|
+
- `widgets` must contain at least one `defineWidget()` result.
|
|
41
|
+
- Do not manually set `packageStableId` for the default droplet template. The CLI injects the linked droplet as the stable package key during validation, build, and publish.
|
|
42
|
+
|
|
43
|
+
## Property schema reference
|
|
44
|
+
|
|
45
|
+
A property schema describes the builder editing UI. Use this shape:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
propertySchema: {
|
|
49
|
+
tabsConfig: [{ id: "content", label: "Content" }],
|
|
50
|
+
dataSourceTargetProps: ["title", "items"],
|
|
51
|
+
fields: [
|
|
52
|
+
{
|
|
53
|
+
key: "title",
|
|
54
|
+
label: "Title",
|
|
55
|
+
type: "text",
|
|
56
|
+
defaultValue: "Featured items",
|
|
57
|
+
tab: "content",
|
|
58
|
+
group: "Copy",
|
|
59
|
+
},
|
|
60
|
+
],
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Base field keys:
|
|
65
|
+
|
|
66
|
+
- `key`: prop key written into widget props. For visual-only fields such as section headers, use a unique non-prop key.
|
|
67
|
+
- `label`: human-readable property label.
|
|
68
|
+
- `type`: one of the supported field types below.
|
|
69
|
+
- `description`: optional helper text.
|
|
70
|
+
- `defaultValue`: optional JSON-serializable value matching the prop.
|
|
71
|
+
- `tab`: optional tab id from `tabsConfig`.
|
|
72
|
+
- `group`: optional group label inside a tab.
|
|
73
|
+
- `advanced`: true for theme override controls; advanced fields render in the Custom styling group.
|
|
74
|
+
- `requiresKeyValue`: conditionally show the field when another prop has a value. Arrays are AND logic.
|
|
75
|
+
|
|
76
|
+
Supported field types:
|
|
77
|
+
|
|
78
|
+
- `text`: single-line string. Optional `placeholder`, `maxLength`, `tokenSuggestions`.
|
|
79
|
+
- `textarea`: multi-line string. Optional `placeholder`, `rows`, `maxLength`.
|
|
80
|
+
- `number`: numeric input. Optional `min`, `max`, `step`.
|
|
81
|
+
- `boolean`: toggle.
|
|
82
|
+
- `select`: dropdown. Requires `options` with `label` and `value` entries.
|
|
83
|
+
- `color`: basic color value.
|
|
84
|
+
- `range`: slider. Requires `min` and `max`; optional `step`.
|
|
85
|
+
- `dataSource`: data-source selector/configuration entry point.
|
|
86
|
+
- `resource`: single shareable/resource selector. Optional `allowedTypes`.
|
|
87
|
+
- `image`: media picker. Optional `accept` as `image`, `video`, or `any`.
|
|
88
|
+
- `alignment`: alignment picker. Requires `options.verticalEnabled` and `options.horizontalEnabled`.
|
|
89
|
+
- `slider`: numeric slider with optional `unit` suffix plus `min`, `max`, and `step`.
|
|
90
|
+
- `colorPicker`: color picker with optional `swatches`.
|
|
91
|
+
- `sectionHeader`: visual grouping header with optional `subtitle`.
|
|
92
|
+
- `separator`: visual separator.
|
|
93
|
+
- `buttonGroup`: segmented control. Requires `options`; each option has `value` plus optional `label`, `ariaLabel`, and icon.
|
|
94
|
+
- `colorSelect`: semantic theme color selector. Optional `excludeColors`.
|
|
95
|
+
- `sectionLayoutSelect`: visual layout selector.
|
|
96
|
+
- `background`: combined resource/color background control.
|
|
97
|
+
- `contentPosition`: 3-by-3 content position picker.
|
|
98
|
+
- `textSizeSelect`: theme text size selector.
|
|
99
|
+
- `cssUnit`: number plus unit. Optional `allowedUnits`, `defaultUnit`, and min/max/step maps per unit.
|
|
100
|
+
- `fontPicker`: Google font picker with optional `placeholder`.
|
|
101
|
+
- `stringArray`: editable list of strings with optional `placeholder`.
|
|
102
|
+
- `borderRadius`: composite radius editor. Requires `keys.topLeft`, `keys.topRight`, `keys.bottomLeft`, and `keys.bottomRight` mapping to real prop keys.
|
|
103
|
+
- `screenPicker`: portal screen picker. Optional `includeSystemItems`.
|
|
104
|
+
|
|
105
|
+
Data-source-ready props:
|
|
106
|
+
|
|
107
|
+
- Add bindable prop keys to `dataSourceTargetProps`.
|
|
108
|
+
- Keep those prop types serializable and tolerant of missing, empty, or partially populated data.
|
|
109
|
+
- For arrays, render empty states and validate each item before reading nested properties.
|
|
110
|
+
- Keep manual defaults in `defaultProps`; data sources should override props without requiring component rewrites.
|
|
111
|
+
- If selected data needs per-item settings, add `itemConfigSchema` with its own `fields` array.
|
|
112
|
+
|
|
113
|
+
## Component quality bar
|
|
114
|
+
|
|
115
|
+
- Components must be deterministic, portable, and host-safe. Avoid direct assumptions about the embedding portal, route, global CSS reset, or parent DOM.
|
|
116
|
+
- Props are untrusted. Provide defaults, guard array access, and handle nullish values.
|
|
117
|
+
- Use stable keys for lists. Do not use array indexes when items have stable ids.
|
|
118
|
+
- Avoid side effects during render. Use effects only for host-safe subscriptions and clean them up.
|
|
119
|
+
- Do not fetch data directly unless the widget explicitly owns that integration. Prefer props and data-source-ready schemas.
|
|
120
|
+
- Keep bundle weight small. Avoid large UI kits or date/chart libraries unless the widget truly needs them.
|
|
121
|
+
- Do not store secrets, API tokens, or tenant-specific credentials in source, props, or defaultProps.
|
|
122
|
+
|
|
123
|
+
## Accessibility requirements
|
|
124
|
+
|
|
125
|
+
- Use semantic HTML first: sections, headings, buttons, lists, forms, and labels.
|
|
126
|
+
- Every interactive control must be keyboard reachable and have a visible focus state.
|
|
127
|
+
- Icon-only buttons need `aria-label`.
|
|
128
|
+
- Images need meaningful alt text, or empty alt text when decorative.
|
|
129
|
+
- Preserve heading order inside the widget; do not choose headings based only on size.
|
|
130
|
+
- Announce dynamic changes when necessary with appropriate ARIA live regions.
|
|
131
|
+
- Meet contrast expectations by using theme foreground/background token pairs.
|
|
132
|
+
- Respect reduced motion for animation-heavy widgets.
|
|
133
|
+
|
|
134
|
+
## Theme and styling guidance
|
|
135
|
+
|
|
136
|
+
Fluid hosts provide semantic CSS variables. Prefer those over hard-coded colors:
|
|
137
|
+
|
|
138
|
+
- Surfaces: `--background`, `--card`, `--popover`.
|
|
139
|
+
- Text on surfaces: `--foreground`, `--card-foreground`, `--popover-foreground`.
|
|
140
|
+
- Brand/action: `--primary`, `--primary-foreground`.
|
|
141
|
+
- Supporting UI: `--secondary`, `--secondary-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`.
|
|
142
|
+
- Status and chrome: `--destructive`, `--destructive-foreground`, `--border`, `--input`, `--ring`.
|
|
143
|
+
- Charts: `--chart-1` through `--chart-5`.
|
|
144
|
+
- Radius: `--radius`, `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`.
|
|
145
|
+
- Theme engine aliases may also provide `--font-header`, `--font-body`, `--font-size-extra-small`, `--font-size-small`, `--font-size-regular`, `--font-size-large`, `--font-size-extra-large`, and `--font-size-giant`.
|
|
146
|
+
|
|
147
|
+
Tailwind equivalents, when Tailwind is available in a host or preview, are the semantic utilities: `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-primary`, `text-primary-foreground`, `border-border`, `ring-ring`, `rounded-lg`, and text sizes such as `text-sm` or `text-xl`. In this standalone template, plain CSS is the safest runtime default.
|
|
148
|
+
|
|
149
|
+
Light/dark behavior:
|
|
150
|
+
|
|
151
|
+
- The portal switches theme values with `data-theme-mode="dark"` and may also honor system dark mode when configured.
|
|
152
|
+
- Do not write separate hard-coded dark palettes unless absolutely necessary. Use semantic variables so the host theme controls both modes.
|
|
153
|
+
- If you need mode-specific refinements, scope them to `[data-theme-mode="dark"]` and keep them token-based.
|
|
154
|
+
- Avoid styling that only works on white backgrounds. Test in the local builder preview dark toggle and with high-contrast colors.
|
|
155
|
+
|
|
156
|
+
Runtime CSS rules:
|
|
157
|
+
|
|
158
|
+
- Put runtime selectors in `styles.css` or imported CSS modules.
|
|
159
|
+
- Keep selectors prefixed with the widget name to avoid leaking styles into the host.
|
|
160
|
+
- Do not rely on global body styles for runtime appearance; body styles are only for local preview.
|
|
161
|
+
- Ensure CSS is imported by `manifest.ts` or another module included in the widget build, otherwise CSS artifacts may not be published.
|
|
162
|
+
|
|
163
|
+
## Validation workflow
|
|
164
|
+
|
|
165
|
+
Before considering changes complete, run:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pnpm typecheck
|
|
169
|
+
pnpm validate
|
|
170
|
+
pnpm build
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Use `pnpm dev` for local preview and `pnpm run widget:publish -- --dry-run` when checking publish readiness without uploading.
|
|
174
|
+
|
|
175
|
+
Common failures and fixes:
|
|
176
|
+
|
|
177
|
+
- Missing droplet UUID: run `pnpm run widget:link` or pass `--droplet` to validate/build/publish commands.
|
|
178
|
+
- No source package found: ensure `fluid.widget.config.ts` exports `widgetPackage` or `widgetPackages` from `manifest.ts`.
|
|
179
|
+
- Invalid package type: keep `packageType: "droplet"` in `defineWidgetPackage()`.
|
|
180
|
+
- Invalid widget name or package key: use URL-safe names only.
|
|
181
|
+
- Invalid version: use SemVer without build metadata.
|
|
182
|
+
- Non-serializable metadata: remove functions, undefined values, Dates, NaN, Infinity, Maps, Sets, and class instances from `propertySchema` and `defaultProps`.
|
|
183
|
+
- CSS URL mismatch: import runtime CSS into the bundle or remove manual `cssUrls` entries.
|
|
184
|
+
- Build succeeds but styles are missing: confirm the CSS import is reachable from `manifest.ts` or the widget component.
|
|
185
|
+
- Preview works but published widget fails: check that browser-only APIs are guarded and no local-only URLs or environment variables are required at runtime.
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
Standalone Fluid widget project generated by `@fluid-app/fluid-cli-widget`.
|
|
4
|
+
|
|
5
|
+
This project builds and publishes a widget package only. It does **not** scaffold droplets, a full portal application, or backend services.
|
|
6
|
+
|
|
7
|
+
## Scripts
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm dev # Run the local Vite/builder preview
|
|
11
|
+
pnpm typecheck # Run TypeScript checking
|
|
12
|
+
pnpm validate # Validate package metadata with the Fluid widget CLI
|
|
13
|
+
pnpm build # Validate and build runtime artifacts
|
|
14
|
+
pnpm run widget:link # Link this project to an existing Fluid droplet
|
|
15
|
+
pnpm run widget:publish # Publish to the linked droplet
|
|
16
|
+
pnpm run widget:publish -- --dry-run # Build upload payload without uploading
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Project structure
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
.
|
|
23
|
+
├── AGENTS.md # Portable AI authoring guidance
|
|
24
|
+
├── CLAUDE.md # Claude-compatible AI authoring guidance
|
|
25
|
+
├── .agents/skills/ # Agent skill docs for this project
|
|
26
|
+
├── .claude/skills/ # Claude skill docs for this project
|
|
27
|
+
├── fluid.widget.config.ts # Widget CLI config and manifest exports
|
|
28
|
+
├── manifest.ts # defineWidget/defineWidgetPackage metadata + runtime CSS import
|
|
29
|
+
├── styles.css # Runtime + preview styles
|
|
30
|
+
├── src/index.ts # Runtime registration entry
|
|
31
|
+
├── src/widget-preview.tsx # Local preview entry
|
|
32
|
+
└── src/widgets/review-carousel/ReviewCarousel.tsx
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Package boundary
|
|
36
|
+
|
|
37
|
+
Stay inside the standalone widget package:
|
|
38
|
+
|
|
39
|
+
- Put reusable widget components under `src/widgets/`.
|
|
40
|
+
- Put package and palette metadata in `manifest.ts`.
|
|
41
|
+
- Keep runtime registration in `src/index.ts`.
|
|
42
|
+
- Keep runtime styles in `styles.css` or CSS files imported by widget modules.
|
|
43
|
+
- Do not add Next.js app folders, Rails code, API servers, portal app shells, or droplet scaffolding.
|
|
44
|
+
|
|
45
|
+
Droplet ownership is managed by the Fluid widget CLI through `fluid.widget.config.ts`. Link the project with `pnpm run widget:link` or pass a droplet option to CLI commands when needed.
|
|
46
|
+
|
|
47
|
+
## Authoring widgets
|
|
48
|
+
|
|
49
|
+
Widgets are authored with `defineWidget()` and grouped with `defineWidgetPackage()` from `@fluid-app/portal-sdk`.
|
|
50
|
+
|
|
51
|
+
`defineWidget()` describes one widget:
|
|
52
|
+
|
|
53
|
+
- `name`: stable URL-safe widget name. Changing it changes the generated widget type.
|
|
54
|
+
- `component`: React component rendered by the host.
|
|
55
|
+
- `displayName`, `description`, `icon`, `category`: builder palette metadata.
|
|
56
|
+
- `defaultProps`: JSON-serializable defaults for new widget instances.
|
|
57
|
+
- `propertySchema`: builder property panel fields.
|
|
58
|
+
- `container`: `block`, `card`, `inline`, or `fullscreen`.
|
|
59
|
+
- `resizable`: false/omitted, true, `horizontal`, `vertical`, `both`, or an object with horizontal/vertical flags and optional minimum sizes.
|
|
60
|
+
|
|
61
|
+
`defineWidgetPackage()` describes the package:
|
|
62
|
+
|
|
63
|
+
- Keep `packageType` set to `droplet` for this template.
|
|
64
|
+
- Keep `remoteEntryUrl` as `widget.js` for the default publish flow.
|
|
65
|
+
- Keep `widgets` as the array of widgets exported by this package.
|
|
66
|
+
- Use a SemVer `version` without build metadata.
|
|
67
|
+
- Do not manually set `packageStableId` in this generated droplet template; the CLI injects the linked droplet key during validation, build, and publish.
|
|
68
|
+
|
|
69
|
+
## Manifest structure
|
|
70
|
+
|
|
71
|
+
`manifest.ts` should remain the single source of package metadata:
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { defineWidget, defineWidgetPackage } from "@fluid-app/portal-sdk";
|
|
75
|
+
import "./styles.css";
|
|
76
|
+
import { ExampleWidget } from "./src/widgets/example/ExampleWidget";
|
|
77
|
+
|
|
78
|
+
export const exampleWidget = defineWidget({
|
|
79
|
+
name: "ExampleWidget",
|
|
80
|
+
component: ExampleWidget,
|
|
81
|
+
displayName: "Example Widget",
|
|
82
|
+
description: "A focused reusable Fluid widget.",
|
|
83
|
+
icon: "box",
|
|
84
|
+
category: "components",
|
|
85
|
+
container: "card",
|
|
86
|
+
defaultProps: {
|
|
87
|
+
title: "Featured content",
|
|
88
|
+
},
|
|
89
|
+
propertySchema: {
|
|
90
|
+
tabsConfig: [{ id: "content", label: "Content" }],
|
|
91
|
+
dataSourceTargetProps: ["title"],
|
|
92
|
+
fields: [
|
|
93
|
+
{
|
|
94
|
+
key: "title",
|
|
95
|
+
label: "Title",
|
|
96
|
+
type: "text",
|
|
97
|
+
defaultValue: "Featured content",
|
|
98
|
+
tab: "content",
|
|
99
|
+
group: "Copy",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export const widgetPackage = defineWidgetPackage({
|
|
106
|
+
scope: "droplet",
|
|
107
|
+
version: "0.1.0",
|
|
108
|
+
packageType: "droplet",
|
|
109
|
+
remoteEntryUrl: "widget.js",
|
|
110
|
+
widgets: [exampleWidget],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
export const widgetPackages = [widgetPackage] as const;
|
|
114
|
+
export default widgetPackage;
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Property schema reference
|
|
118
|
+
|
|
119
|
+
Property schemas define editable builder controls. They must be JSON-serializable.
|
|
120
|
+
|
|
121
|
+
Top-level schema keys:
|
|
122
|
+
|
|
123
|
+
- `tabsConfig`: optional array of tab objects with `id` and `label`.
|
|
124
|
+
- `fields`: array of controls shown in the builder property panel.
|
|
125
|
+
- `dataSourceTargetProps`: prop keys that can be populated from configured data sources.
|
|
126
|
+
- `itemConfigSchema`: optional per-item configuration schema for custom data-source selections.
|
|
127
|
+
- `validate`: optional custom validator in in-app manifests; avoid it for published standalone metadata because package metadata must be serializable.
|
|
128
|
+
|
|
129
|
+
Base field keys:
|
|
130
|
+
|
|
131
|
+
| Key | Purpose |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| `key` | Widget prop key written by the field. Use a unique non-prop key for visual-only controls. |
|
|
134
|
+
| `label` | Builder label. |
|
|
135
|
+
| `type` | Supported field type listed below. |
|
|
136
|
+
| `description` | Optional helper text. |
|
|
137
|
+
| `defaultValue` | Optional JSON-serializable default. |
|
|
138
|
+
| `tab` | Optional tab id matching `tabsConfig`. |
|
|
139
|
+
| `group` | Optional group label inside a tab. |
|
|
140
|
+
| `advanced` | Marks theme overrides or low-frequency settings; advanced fields render in Custom styling. |
|
|
141
|
+
| `requiresKeyValue` | Optional conditional display rule. An array means all conditions must match. |
|
|
142
|
+
|
|
143
|
+
Supported field types:
|
|
144
|
+
|
|
145
|
+
| Type | Use | Extra keys |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `text` | Single-line string | `placeholder`, `maxLength`, `tokenSuggestions` |
|
|
148
|
+
| `textarea` | Multi-line string | `placeholder`, `rows`, `maxLength` |
|
|
149
|
+
| `number` | Number input | `min`, `max`, `step` |
|
|
150
|
+
| `boolean` | Toggle | None |
|
|
151
|
+
| `select` | Dropdown | `options` array with `label` and `value` |
|
|
152
|
+
| `color` | Basic color value | None |
|
|
153
|
+
| `range` | Slider | Required `min`, `max`; optional `step` |
|
|
154
|
+
| `dataSource` | Data-source selector/configuration | None |
|
|
155
|
+
| `resource` | Single shareable/resource picker | `allowedTypes` |
|
|
156
|
+
| `image` | Media picker | `accept` as `image`, `video`, or `any` |
|
|
157
|
+
| `alignment` | Alignment picker | `options.verticalEnabled`, `options.horizontalEnabled` |
|
|
158
|
+
| `slider` | Numeric slider | `min`, `max`, `step`, `unit` |
|
|
159
|
+
| `colorPicker` | Color picker | `swatches` |
|
|
160
|
+
| `sectionHeader` | Visual section heading | `subtitle` |
|
|
161
|
+
| `separator` | Visual divider | None |
|
|
162
|
+
| `buttonGroup` | Segmented choice | `options` with `value`, optional `label`, `ariaLabel`, `icon` |
|
|
163
|
+
| `colorSelect` | Semantic theme color picker | `excludeColors` |
|
|
164
|
+
| `sectionLayoutSelect` | Visual section layout picker | None |
|
|
165
|
+
| `background` | Combined background resource/color control | Resource/color field keys as needed |
|
|
166
|
+
| `contentPosition` | 3-by-3 content position picker | None |
|
|
167
|
+
| `textSizeSelect` | Theme text-size picker | None |
|
|
168
|
+
| `cssUnit` | Number with unit selector | `allowedUnits`, `defaultUnit`, `minByUnit`, `maxByUnit`, `stepByUnit` |
|
|
169
|
+
| `fontPicker` | Google font picker | `placeholder` |
|
|
170
|
+
| `stringArray` | Editable list of strings | `placeholder` |
|
|
171
|
+
| `borderRadius` | Composite four-corner radius editor | `keys.topLeft`, `keys.topRight`, `keys.bottomLeft`, `keys.bottomRight` |
|
|
172
|
+
| `screenPicker` | Portal screen picker | `includeSystemItems` |
|
|
173
|
+
|
|
174
|
+
### Data-source-ready props
|
|
175
|
+
|
|
176
|
+
For props that may later be connected to data sources:
|
|
177
|
+
|
|
178
|
+
- Add the prop key to `dataSourceTargetProps`.
|
|
179
|
+
- Keep the component tolerant of missing, empty, or partially populated values.
|
|
180
|
+
- Render useful empty states for empty arrays and failed mapping.
|
|
181
|
+
- Keep prop values JSON-serializable.
|
|
182
|
+
- Avoid fetching inside the component when a prop or data source can provide the data.
|
|
183
|
+
- Use `itemConfigSchema` when each selected item needs widget-specific settings.
|
|
184
|
+
|
|
185
|
+
## Component quality bar
|
|
186
|
+
|
|
187
|
+
- Use TypeScript interfaces for public props.
|
|
188
|
+
- Provide defaults for optional props.
|
|
189
|
+
- Treat incoming props as untrusted and validate before reading nested values.
|
|
190
|
+
- Keep render logic deterministic and side-effect free.
|
|
191
|
+
- Clean up effects and subscriptions.
|
|
192
|
+
- Keep bundles small; avoid heavy libraries unless the widget requires them.
|
|
193
|
+
- Avoid secrets, tenant credentials, local URLs, and environment-only assumptions.
|
|
194
|
+
- Prefer semantic HTML over div-only structures.
|
|
195
|
+
|
|
196
|
+
## Accessibility
|
|
197
|
+
|
|
198
|
+
- Use headings, sections, lists, buttons, labels, and form elements semantically.
|
|
199
|
+
- Make every interactive element keyboard reachable.
|
|
200
|
+
- Provide visible focus states.
|
|
201
|
+
- Add `aria-label` to icon-only controls.
|
|
202
|
+
- Add alt text to meaningful images and empty alt text to decorative images.
|
|
203
|
+
- Preserve logical heading order inside the widget.
|
|
204
|
+
- Use ARIA live regions for important dynamic updates.
|
|
205
|
+
- Respect reduced-motion preferences for animation-heavy widgets.
|
|
206
|
+
- Use semantic foreground/background token pairs for contrast.
|
|
207
|
+
|
|
208
|
+
## Runtime CSS and theme tokens
|
|
209
|
+
|
|
210
|
+
Fluid hosts provide semantic CSS variables. Prefer token-based CSS instead of hard-coded palettes.
|
|
211
|
+
|
|
212
|
+
Common CSS variables:
|
|
213
|
+
|
|
214
|
+
- Surfaces: `--background`, `--card`, `--popover`.
|
|
215
|
+
- Text: `--foreground`, `--card-foreground`, `--popover-foreground`.
|
|
216
|
+
- Brand/actions: `--primary`, `--primary-foreground`.
|
|
217
|
+
- Supporting UI: `--secondary`, `--secondary-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground`.
|
|
218
|
+
- Status/chrome: `--destructive`, `--destructive-foreground`, `--border`, `--input`, `--ring`.
|
|
219
|
+
- Charts: `--chart-1`, `--chart-2`, `--chart-3`, `--chart-4`, `--chart-5`.
|
|
220
|
+
- Radius: `--radius`, `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl`.
|
|
221
|
+
- Theme-engine aliases may include `--font-header`, `--font-body`, `--font-size-extra-small`, `--font-size-small`, `--font-size-regular`, `--font-size-large`, `--font-size-extra-large`, and `--font-size-giant`.
|
|
222
|
+
|
|
223
|
+
Tailwind equivalents, when Tailwind is available, are semantic utilities such as `bg-background`, `text-foreground`, `bg-card`, `text-card-foreground`, `bg-primary`, `text-primary-foreground`, `border-border`, `ring-ring`, `rounded-lg`, `text-sm`, and `text-xl`. This standalone template uses plain CSS for runtime portability.
|
|
224
|
+
|
|
225
|
+
Light/dark behavior:
|
|
226
|
+
|
|
227
|
+
- Fluid themes update variables for light and dark mode.
|
|
228
|
+
- Explicit dark mode is represented by `data-theme-mode="dark"` on a host element.
|
|
229
|
+
- Some hosts also use system dark mode when no explicit mode is selected.
|
|
230
|
+
- Prefer semantic variables so both modes work automatically.
|
|
231
|
+
- If mode-specific styling is unavoidable, scope it with `[data-theme-mode="dark"]` and keep values token-based.
|
|
232
|
+
|
|
233
|
+
Runtime CSS rules:
|
|
234
|
+
|
|
235
|
+
- Import runtime CSS from `manifest.ts` or a widget module included in the build.
|
|
236
|
+
- Prefix selectors with the widget name to prevent host leakage.
|
|
237
|
+
- Do not rely on `body` styles for published runtime rendering; body styles are preview-only.
|
|
238
|
+
- If published styles are missing, check that the CSS import is reachable from the built entry.
|
|
239
|
+
|
|
240
|
+
## Validation, build, and publish preflight
|
|
241
|
+
|
|
242
|
+
Before shipping changes, run:
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pnpm typecheck
|
|
246
|
+
pnpm validate
|
|
247
|
+
pnpm build
|
|
248
|
+
pnpm run widget:publish -- --dry-run
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Publish only after the dry run succeeds:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
pnpm run widget:publish
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Build output is written under `.fluid/widget-dist/`. The runtime entry registers the package with `window.FluidWidgets` when loaded by a Fluid portal host.
|
|
258
|
+
|
|
259
|
+
## Common failures
|
|
260
|
+
|
|
261
|
+
| Failure | Fix |
|
|
262
|
+
| --- | --- |
|
|
263
|
+
| Missing droplet UUID | Run `pnpm run widget:link` or pass a droplet option to validate/build/publish. |
|
|
264
|
+
| No source package found | Ensure `fluid.widget.config.ts` exports `widgetPackage` or `widgetPackages` from `manifest.ts`. |
|
|
265
|
+
| Invalid package type | Keep `packageType: "droplet"`. |
|
|
266
|
+
| Invalid package key or widget name | Use URL-safe letters, numbers, dots for package keys, and underscore/hyphen/tilde where allowed. |
|
|
267
|
+
| Invalid version | Use SemVer without build metadata. |
|
|
268
|
+
| Non-serializable metadata | Remove functions, undefined values, Dates, NaN, Infinity, Maps, Sets, and class instances from `propertySchema` and `defaultProps`. |
|
|
269
|
+
| Relative CSS URL rejected | Import CSS into the bundle or remove manual `cssUrls` entries. |
|
|
270
|
+
| Styles present in preview but absent after publish | Confirm `styles.css` is imported by `manifest.ts` or a widget module loaded by the builder. |
|
|
271
|
+
| Published widget fails but preview works | Guard browser-only APIs, remove local-only URLs, and avoid environment variables required at runtime. |
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { defineConfig } from "vite";
|
|
2
2
|
import type { Plugin, ViteDevServer } from "vite";
|
|
3
3
|
import react from "@vitejs/plugin-react";
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
4
5
|
|
|
5
6
|
export default defineConfig({
|
|
6
7
|
plugins: [react(), fluidWidgetDevRoutes()],
|
|
8
|
+
resolve: {
|
|
9
|
+
conditions: ["fluid-widget-authoring"],
|
|
10
|
+
},
|
|
11
|
+
ssr: {
|
|
12
|
+
noExternal: ["@fluid-app/portal-sdk"],
|
|
13
|
+
resolve: {
|
|
14
|
+
conditions: ["fluid-widget-authoring"],
|
|
15
|
+
},
|
|
16
|
+
},
|
|
7
17
|
});
|
|
8
18
|
|
|
9
19
|
function fluidWidgetDevRoutes(): Plugin {
|
|
@@ -11,49 +21,53 @@ function fluidWidgetDevRoutes(): Plugin {
|
|
|
11
21
|
name: "fluid-widget-dev-routes",
|
|
12
22
|
apply: "serve",
|
|
13
23
|
configureServer(server: ViteDevServer) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
24
|
+
const devRoutesHandler = async (
|
|
25
|
+
req: DevRequest,
|
|
26
|
+
res: DevResponse,
|
|
27
|
+
next: (err?: unknown) => void,
|
|
28
|
+
): Promise<void> => {
|
|
29
|
+
const pathname = (req.url ?? "").split("?")[0] ?? "";
|
|
30
|
+
|
|
31
|
+
if (
|
|
32
|
+
pathname === "/builder-preview" ||
|
|
33
|
+
pathname === "/builder-preview/"
|
|
34
|
+
) {
|
|
35
|
+
await sendTransformedHtml(
|
|
36
|
+
server,
|
|
37
|
+
res,
|
|
38
|
+
"/builder-preview",
|
|
39
|
+
getBuilderPreviewHtml(),
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (pathname === "/__preview__" || pathname === "/__preview__/") {
|
|
45
|
+
await sendTransformedHtml(
|
|
46
|
+
server,
|
|
47
|
+
res,
|
|
48
|
+
"/__preview__",
|
|
49
|
+
getPreviewHtml(),
|
|
50
|
+
);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
pathname === "/__runtime-entry__" ||
|
|
56
|
+
pathname === "/__runtime-entry__/"
|
|
57
|
+
) {
|
|
58
|
+
sendJavaScript(req, res, 'void import("/src/index.ts");\n');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (pathname === "/__manifests__" || pathname === "/__manifests__/") {
|
|
63
|
+
await sendManifests(server, req, res);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
next();
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
server.middlewares.use(devRoutesHandler);
|
|
57
71
|
},
|
|
58
72
|
};
|
|
59
73
|
}
|
|
@@ -164,16 +178,8 @@ function getPreviewHtml(): string {
|
|
|
164
178
|
</html>`;
|
|
165
179
|
}
|
|
166
180
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
readonly url?: string;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
interface DevResponse {
|
|
173
|
-
statusCode: number;
|
|
174
|
-
setHeader(name: string, value: string): void;
|
|
175
|
-
end(body?: string): void;
|
|
176
|
-
}
|
|
181
|
+
type DevRequest = IncomingMessage;
|
|
182
|
+
type DevResponse = ServerResponse;
|
|
177
183
|
|
|
178
184
|
interface SourceWidgetPackage {
|
|
179
185
|
readonly packageId: string;
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# {{projectName}}
|
|
2
|
-
|
|
3
|
-
Standalone Fluid widget project generated by `@fluid-app/fluid-cli-widget`.
|
|
4
|
-
|
|
5
|
-
This project builds a widget package only. It does **not** scaffold droplets or a full portal application.
|
|
6
|
-
|
|
7
|
-
## Scripts
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
pnpm dev # Run the local Vite preview
|
|
11
|
-
pnpm validate # Validate the widget package with the Fluid widget CLI
|
|
12
|
-
pnpm build # Build the widget runtime bundle with the Fluid widget CLI
|
|
13
|
-
pnpm run widget:publish # Publish with the Fluid widget CLI
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
## Project structure
|
|
17
|
-
|
|
18
|
-
```text
|
|
19
|
-
.
|
|
20
|
-
├── fluid.widget.config.ts # Widget package config export
|
|
21
|
-
├── manifest.ts # defineWidget/defineWidgetPackage metadata + runtime CSS import
|
|
22
|
-
├── styles.css # Runtime + preview styles
|
|
23
|
-
├── src/index.ts # Runtime registration entry
|
|
24
|
-
├── src/widget-preview.tsx # Local preview entry
|
|
25
|
-
└── src/widgets/review-carousel/ReviewCarousel.tsx
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Authoring widgets
|
|
29
|
-
|
|
30
|
-
Widgets are authored with `defineWidget()` and grouped with `defineWidgetPackage()` from `@fluid-app/portal-sdk`.
|
|
31
|
-
|
|
32
|
-
Edit `manifest.ts` to update package metadata such as `version` and to add widgets. Droplet package identity is managed by the Fluid widget CLI from `fluid.widget.config.ts` and the linked droplet.
|
|
33
|
-
|
|
34
|
-
Add new widgets under `src/widgets/`, import them in `manifest.ts`, and add them to the `widgets` array. Keep runtime CSS imported by `manifest.ts` or another module loaded by the widget builder; the build discovers emitted CSS artifacts automatically.
|
|
35
|
-
|
|
36
|
-
## Build output
|
|
37
|
-
|
|
38
|
-
`pnpm build` validates the package and writes runtime artifacts under `.fluid/widget-dist/`. The runtime entry registers the package with `window.FluidWidgets` when loaded by a Fluid portal host.
|
|
File without changes
|
|
File without changes
|