fluentui-webcomponents 0.0.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/AGENTS.md +212 -0
- package/README.md +99 -0
- package/components/avatar/fluent-avatar.css +481 -0
- package/components/avatar/fluent-avatar.js +80 -0
- package/components/badge/fluent-badge.css +289 -0
- package/components/badge/fluent-badge.js +20 -0
- package/components/breadcrumb/fluent-breadcrumb.css +29 -0
- package/components/breadcrumb/fluent-breadcrumb.js +33 -0
- package/components/breadcrumb-item/fluent-breadcrumb-item.css +70 -0
- package/components/breadcrumb-item/fluent-breadcrumb-item.js +77 -0
- package/components/button/fluent-button.css +265 -0
- package/components/button/fluent-button.js +326 -0
- package/components/card/fluent-card.css +85 -0
- package/components/card/fluent-card.js +21 -0
- package/components/checkbox/fluent-checkbox.css +171 -0
- package/components/checkbox/fluent-checkbox.js +294 -0
- package/components/dialog/fluent-dialog.css +82 -0
- package/components/dialog/fluent-dialog.js +137 -0
- package/components/divider/fluent-divider.css +124 -0
- package/components/divider/fluent-divider.js +14 -0
- package/components/image/fluent-image.css +73 -0
- package/components/image/fluent-image.js +36 -0
- package/components/label/fluent-label.css +49 -0
- package/components/label/fluent-label.js +61 -0
- package/components/link/fluent-link.css +72 -0
- package/components/link/fluent-link.js +109 -0
- package/components/menu/fluent-menu.css +57 -0
- package/components/menu/fluent-menu.js +202 -0
- package/components/menu-item/fluent-menu-item.css +152 -0
- package/components/menu-item/fluent-menu-item.js +177 -0
- package/components/popover/fluent-popover.css +95 -0
- package/components/popover/fluent-popover.js +93 -0
- package/components/radio/fluent-radio.css +123 -0
- package/components/radio/fluent-radio.js +257 -0
- package/components/select/fluent-select.css +194 -0
- package/components/select/fluent-select.js +245 -0
- package/components/slider/fluent-slider.css +199 -0
- package/components/slider/fluent-slider.js +438 -0
- package/components/spinner/fluent-spinner.css +160 -0
- package/components/spinner/fluent-spinner.js +30 -0
- package/components/switch/fluent-switch.css +154 -0
- package/components/switch/fluent-switch.js +260 -0
- package/components/text/fluent-text.css +128 -0
- package/components/text/fluent-text.js +21 -0
- package/components/text-input/fluent-text-input.css +227 -0
- package/components/text-input/fluent-text-input.js +298 -0
- package/components/textarea/fluent-textarea.css +227 -0
- package/components/textarea/fluent-textarea.js +400 -0
- package/components/tooltip/fluent-tooltip.css +65 -0
- package/components/tooltip/fluent-tooltip.js +102 -0
- package/components/tree/fluent-tree.css +16 -0
- package/components/tree/fluent-tree.js +167 -0
- package/components/tree-item/fluent-tree-item.css +147 -0
- package/components/tree-item/fluent-tree-item.js +163 -0
- package/core/fluent-element.js +34 -0
- package/gallery.html +492 -0
- package/package.json +19 -0
- package/theme/theme-picker.js +38 -0
- package/tokens.css +724 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# Fluent UI Web Components — Agent Instructions
|
|
2
|
+
|
|
3
|
+
**These instructions are the source of truth.** All contributions must follow these principles.
|
|
4
|
+
|
|
5
|
+
## Critical Rules (never violate)
|
|
6
|
+
|
|
7
|
+
1. **Zero external dependencies.** No `@microsoft/fast-element`, no `@fluentui/tokens`, no Lit, no frameworks. Vanilla `HTMLElement` only.
|
|
8
|
+
2. **No build tools.** Plain `.js` + `.css` files. Native ES modules (`import`/`export`). No bundler, no TypeScript compiler, no CSS preprocessor.
|
|
9
|
+
3. **Design tokens are CSS custom properties.** All colors, spacing, typography, radii, shadows, durations, and curves are defined in `tokens.css` and referenced via `var(--tokenName)`. Never hardcode a value.
|
|
10
|
+
4. **`color-mix(in srgb, ...)` for palette derivation.** The 16-stop brand/neutral palettes derive from a single `--accent-base` / `--neutral-base` CSS variable using the `@microsoft/fast-colors` ColorPalette algorithm (trim-and-rescale with RGB interpolation). No JS color math, no lookup tables.
|
|
11
|
+
5. **Component CSS imports tokens.** Every `fluent-xxx.css` starts with `@import url('../../tokens.css');`. Browsers deduplicate by URL.
|
|
12
|
+
|
|
13
|
+
## Component Pattern
|
|
14
|
+
|
|
15
|
+
### JS Template
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import { FluentElement } from '../../core/fluent-element.js';
|
|
19
|
+
|
|
20
|
+
const stylesUrl = new URL('./fluent-xxx.css', import.meta.url).href;
|
|
21
|
+
|
|
22
|
+
class FluentXxx extends FluentElement {
|
|
23
|
+
static stylesUrl = stylesUrl;
|
|
24
|
+
static template = `
|
|
25
|
+
<div class="root">
|
|
26
|
+
<!-- shadow DOM content here -->
|
|
27
|
+
<slot></slot>
|
|
28
|
+
</div>
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
static get observedAttributes() {
|
|
32
|
+
return ['disabled', 'appearance', 'size', 'shape'];
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
customElements.define('fluent-xxx', FluentXxx);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### CSS Template
|
|
40
|
+
|
|
41
|
+
```css
|
|
42
|
+
@import url('../../tokens.css');
|
|
43
|
+
|
|
44
|
+
:host {
|
|
45
|
+
display: inline-flex; /* host stays in layout tree for pointer events */
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.root {
|
|
49
|
+
/* ALL visual styles go here — colors, spacing, borders, shadows, fonts */
|
|
50
|
+
background: var(--colorBrandBackground);
|
|
51
|
+
padding: 0 var(--spacingHorizontalM);
|
|
52
|
+
border-radius: var(--borderRadiusMedium);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* State via host attributes targeting .root */
|
|
56
|
+
:host([disabled]) .root { opacity: 0.5; cursor: not-allowed; }
|
|
57
|
+
:host([appearance='primary']) .root { background: var(--colorBrandBackground); }
|
|
58
|
+
|
|
59
|
+
/* Pseudo-classes on .root */
|
|
60
|
+
.root:hover { background: var(--colorBrandBackgroundHover); }
|
|
61
|
+
.root:focus-visible { outline: 2px solid var(--colorStrokeFocus2); }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Key Patterns
|
|
65
|
+
|
|
66
|
+
| Concern | Where | Why |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| Visual styles | `.root` div | Isolated from page CSS — page's `* { padding: 0 }` can't reach it |
|
|
69
|
+
| Layout / display | `:host` | Host must stay in layout tree for pointer events, `getBoundingClientRect()`, and CSS anchor positioning |
|
|
70
|
+
| State selectors | `:host([attr]) .root` | Host carries the attribute, `.root` receives the visual change |
|
|
71
|
+
| Slotted content | `::slotted(...)` | Stays as-is, no `.root` prefix needed |
|
|
72
|
+
|
|
73
|
+
### `FluentElement` Base Class
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
// core/fluent-element.js — ~30 lines
|
|
77
|
+
class FluentElement extends HTMLElement {
|
|
78
|
+
static stylesUrl = '';
|
|
79
|
+
static template = '';
|
|
80
|
+
|
|
81
|
+
constructor() {
|
|
82
|
+
super();
|
|
83
|
+
this._root = this.attachShadow({ mode: 'open' });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
connectedCallback() {
|
|
87
|
+
if (this.constructor.stylesUrl) {
|
|
88
|
+
const link = document.createElement('link');
|
|
89
|
+
link.rel = 'stylesheet';
|
|
90
|
+
link.href = this.constructor.stylesUrl;
|
|
91
|
+
this._root.appendChild(link);
|
|
92
|
+
}
|
|
93
|
+
const tmpl = document.createElement('template');
|
|
94
|
+
tmpl.innerHTML = this.constructor.template;
|
|
95
|
+
this._root.appendChild(tmpl.content.cloneNode(true));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static get observedAttributes() { return []; }
|
|
99
|
+
attributeChangedCallback(name, oldVal, newVal) {
|
|
100
|
+
if (oldVal !== newVal) this.changed(name, oldVal, newVal);
|
|
101
|
+
}
|
|
102
|
+
changed() {}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Design Tokens
|
|
107
|
+
|
|
108
|
+
### Palette Derivation
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
--accent-base (#0f6cbd)
|
|
112
|
+
→ trim endpoints (ColorPalette: clipLight=0.185, clipDark=0.16, RGB interp)
|
|
113
|
+
→ 16-stop ramp from trimDark through baseColor to trimLight
|
|
114
|
+
→ --brand-10 … --brand-160
|
|
115
|
+
|
|
116
|
+
--neutral-base (#808080)
|
|
117
|
+
→ same algorithm, trim endpoints
|
|
118
|
+
→ --neutral-10 … --neutral-160
|
|
119
|
+
|
|
120
|
+
Dark mode: clipLight=0 → trimLight = pure white (maximum contrast text)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Semantic tokens alias these stops:
|
|
124
|
+
```
|
|
125
|
+
--colorBrandBackground → --brand-80
|
|
126
|
+
--colorBrandBackgroundHover → --brand-70
|
|
127
|
+
--colorBrandBackgroundPressed → --brand-40
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Runtime Accent Change
|
|
131
|
+
|
|
132
|
+
```js
|
|
133
|
+
document.documentElement.style.setProperty('--accent-base', '#e3008c');
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
One line. Entire palette and all dependent tokens update instantly. Zero JS math.
|
|
137
|
+
|
|
138
|
+
### Theme Switching
|
|
139
|
+
|
|
140
|
+
```css
|
|
141
|
+
:root.dark { --accent-base: #479ef5; --neutral-base: #b0b0b0; }
|
|
142
|
+
body.dark { /* overrides for dark theme tokens */ }
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Form Association
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
class FluentCheckbox extends FluentElement {
|
|
149
|
+
static formAssociated = true;
|
|
150
|
+
|
|
151
|
+
constructor() {
|
|
152
|
+
super();
|
|
153
|
+
this._internals = this.attachInternals();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Never pass empty second arg to setValidity when flags are truthy
|
|
157
|
+
_setValidity() {
|
|
158
|
+
const msg = this.validationMessage;
|
|
159
|
+
this._internals.setValidity(
|
|
160
|
+
{ valueMissing: this.required && !this._checked },
|
|
161
|
+
msg || undefined
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Component-Specific Exception Rules
|
|
168
|
+
|
|
169
|
+
### Dialog
|
|
170
|
+
- Host uses `:host { display: none; }` / `:host([open]) { display: block; }` for show/hide
|
|
171
|
+
- `trigger` and `close-on` attributes for declarative open/close wiring
|
|
172
|
+
- Uses native `<dialog>` element inside shadow DOM
|
|
173
|
+
|
|
174
|
+
### Tooltip / Popover
|
|
175
|
+
- Uses Popover API: `popover="manual"`, `showPopover()` / `hidePopover()`
|
|
176
|
+
- `inset-area` / `position-area` MUST stay on `:host`, not on `.root`
|
|
177
|
+
- JS sets `anchor-name` on the anchor element and `position-anchor` on the tooltip host
|
|
178
|
+
|
|
179
|
+
### Slider
|
|
180
|
+
- CSS custom properties (`--slider-thumb`, `--slider-progress`, `--step-rate`) set on `.root` via inline style to override CSS defaults
|
|
181
|
+
- Pointer event handlers bound once in constructor (`this._boundMove = this._handlePointerMove.bind(this)`) — never use `.bind()` in addEventListener arguments
|
|
182
|
+
|
|
183
|
+
## File Structure
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
fluentui-webcomponents/
|
|
187
|
+
├── tokens.css # All design tokens
|
|
188
|
+
├── gallery.html # Single-page component showcase
|
|
189
|
+
├── core/
|
|
190
|
+
│ └── fluent-element.js # HTMLElement base class
|
|
191
|
+
├── components/
|
|
192
|
+
│ ├── button/ fluent-button.js + .css
|
|
193
|
+
│ ├── badge/ fluent-badge.js + .css
|
|
194
|
+
│ ├── checkbox/ fluent-checkbox.js + .css
|
|
195
|
+
│ ├── ... (26 components total)
|
|
196
|
+
│ └── tree/ fluent-tree.js + .css
|
|
197
|
+
└── theme/
|
|
198
|
+
└── theme-picker.js # Accent color + theme switcher
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Quality Checklist
|
|
202
|
+
|
|
203
|
+
- [ ] Zero console errors in gallery
|
|
204
|
+
- [ ] Component has both `.js` and `.css` files
|
|
205
|
+
- [ ] CSS starts with `@import url('../../tokens.css');`
|
|
206
|
+
- [ ] Template wrapped in `<div class="root">...</div>`
|
|
207
|
+
- [ ] Visual styles on `.root`, not on `:host`
|
|
208
|
+
- [ ] No hardcoded colors/spacing/typography — all `var(--tokenName)`
|
|
209
|
+
- [ ] `:host` has a display mode (not `display: contents`)
|
|
210
|
+
- [ ] Form components use `static formAssociated = true` + `attachInternals()`
|
|
211
|
+
- [ ] `setValidity()` never passes empty second arg with truthy flags
|
|
212
|
+
- [ ] Event handlers (if any) bound once in constructor, not each call
|
package/README.md
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# fluentui-webcomponents
|
|
2
|
+
|
|
3
|
+
Vanilla Custom Elements rewrite of `@fluentui/web-components` — zero dependencies, zero build tools.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```html
|
|
8
|
+
<script type="module" src="core/fluent-element.js"></script>
|
|
9
|
+
<script type="module" src="components/button/fluent-button.js"></script>
|
|
10
|
+
<script type="module" src="components/badge/fluent-badge.js"></script>
|
|
11
|
+
|
|
12
|
+
<fluent-button appearance="primary">Click me</fluent-button>
|
|
13
|
+
<fluent-badge color="danger">New</fluent-badge>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
CSS design tokens are defined in `tokens.css`. Each component's CSS imports it via `@import` — no `<link>` needed in your page.
|
|
17
|
+
|
|
18
|
+
## Gallery
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx serve .
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Open `http://localhost:3000/gallery.html` — a single-page showcase of all 26 components with a theme picker.
|
|
25
|
+
|
|
26
|
+
## Customization
|
|
27
|
+
|
|
28
|
+
**Accent color** (instant, no JS math):
|
|
29
|
+
```css
|
|
30
|
+
:root { --accent-base: #e3008c; }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The entire 16-stop brand palette derives from this single CSS variable via `color-mix(in oklch, ...)`. All component colors update automatically.
|
|
34
|
+
|
|
35
|
+
**Dark theme**:
|
|
36
|
+
```html
|
|
37
|
+
<body class="dark">
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Theme picker** (optional):
|
|
41
|
+
```html
|
|
42
|
+
<script type="module" src="theme/theme-picker.js"></script>
|
|
43
|
+
<fluent-theme-picker></fluent-theme-picker>
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Components
|
|
47
|
+
|
|
48
|
+
| Component | Tag |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Button | `<fluent-button>` |
|
|
51
|
+
| Badge | `<fluent-badge>` |
|
|
52
|
+
| Link | `<fluent-link>` |
|
|
53
|
+
| Divider | `<fluent-divider>` |
|
|
54
|
+
| Spinner | `<fluent-spinner>` |
|
|
55
|
+
| Avatar | `<fluent-avatar>` |
|
|
56
|
+
| Checkbox | `<fluent-checkbox>` |
|
|
57
|
+
| Radio | `<fluent-radio>` |
|
|
58
|
+
| Switch | `<fluent-switch>` |
|
|
59
|
+
| Slider | `<fluent-slider>` |
|
|
60
|
+
| TextInput | `<fluent-text-input>` |
|
|
61
|
+
| Textarea | `<fluent-textarea>` |
|
|
62
|
+
| Select | `<fluent-select>` |
|
|
63
|
+
| Card | `<fluent-card>` |
|
|
64
|
+
| Label | `<fluent-label>` |
|
|
65
|
+
| Text | `<fluent-text>` |
|
|
66
|
+
| Image | `<fluent-image>` |
|
|
67
|
+
| Dialog | `<fluent-dialog trigger="#btn" close-on="#cancel">` |
|
|
68
|
+
| Tooltip | `<fluent-tooltip anchor="btn-id">` |
|
|
69
|
+
| Menu | `<fluent-menu>` |
|
|
70
|
+
| Breadcrumb | `<fluent-breadcrumb>` |
|
|
71
|
+
| Tree | `<fluent-tree>` |
|
|
72
|
+
|
|
73
|
+
## File Structure
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
fluentui-webcomponents/
|
|
77
|
+
├── tokens.css # All design tokens (colors, spacing, type, shadows...)
|
|
78
|
+
├── gallery.html # Component showcase
|
|
79
|
+
├── core/
|
|
80
|
+
│ └── fluent-element.js # Base class (~30 lines)
|
|
81
|
+
├── components/
|
|
82
|
+
│ ├── button/
|
|
83
|
+
│ │ ├── fluent-button.js
|
|
84
|
+
│ │ └── fluent-button.css # @import url('../../tokens.css');
|
|
85
|
+
│ ├── badge/
|
|
86
|
+
│ ├── ...
|
|
87
|
+
│ └── tree/
|
|
88
|
+
└── theme/
|
|
89
|
+
└── theme-picker.js # Optional accent/theme switcher
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Each component is self-contained: one `.js` file (class + `customElements.define`) and one `.css` file. No bundler, no framework, no external dependencies.
|
|
93
|
+
|
|
94
|
+
## Architecture
|
|
95
|
+
|
|
96
|
+
- **`FluentElement`** — base class extends `HTMLElement`, attaches shadow DOM, injects `<link rel="stylesheet">` for the component's CSS
|
|
97
|
+
- **CSS-only state** — `:host([disabled])`, `:host([appearance="primary"])`, etc. Zero JS for visual updates
|
|
98
|
+
- **Form association** — native `this.attachInternals()` + `setFormValue` + `setValidity`
|
|
99
|
+
- **Design tokens** — `color-mix(in oklch, var(--accent-base), ...)` palette, CSS `var()` throughout
|