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.
Files changed (59) hide show
  1. package/AGENTS.md +212 -0
  2. package/README.md +99 -0
  3. package/components/avatar/fluent-avatar.css +481 -0
  4. package/components/avatar/fluent-avatar.js +80 -0
  5. package/components/badge/fluent-badge.css +289 -0
  6. package/components/badge/fluent-badge.js +20 -0
  7. package/components/breadcrumb/fluent-breadcrumb.css +29 -0
  8. package/components/breadcrumb/fluent-breadcrumb.js +33 -0
  9. package/components/breadcrumb-item/fluent-breadcrumb-item.css +70 -0
  10. package/components/breadcrumb-item/fluent-breadcrumb-item.js +77 -0
  11. package/components/button/fluent-button.css +265 -0
  12. package/components/button/fluent-button.js +326 -0
  13. package/components/card/fluent-card.css +85 -0
  14. package/components/card/fluent-card.js +21 -0
  15. package/components/checkbox/fluent-checkbox.css +171 -0
  16. package/components/checkbox/fluent-checkbox.js +294 -0
  17. package/components/dialog/fluent-dialog.css +82 -0
  18. package/components/dialog/fluent-dialog.js +137 -0
  19. package/components/divider/fluent-divider.css +124 -0
  20. package/components/divider/fluent-divider.js +14 -0
  21. package/components/image/fluent-image.css +73 -0
  22. package/components/image/fluent-image.js +36 -0
  23. package/components/label/fluent-label.css +49 -0
  24. package/components/label/fluent-label.js +61 -0
  25. package/components/link/fluent-link.css +72 -0
  26. package/components/link/fluent-link.js +109 -0
  27. package/components/menu/fluent-menu.css +57 -0
  28. package/components/menu/fluent-menu.js +202 -0
  29. package/components/menu-item/fluent-menu-item.css +152 -0
  30. package/components/menu-item/fluent-menu-item.js +177 -0
  31. package/components/popover/fluent-popover.css +95 -0
  32. package/components/popover/fluent-popover.js +93 -0
  33. package/components/radio/fluent-radio.css +123 -0
  34. package/components/radio/fluent-radio.js +257 -0
  35. package/components/select/fluent-select.css +194 -0
  36. package/components/select/fluent-select.js +245 -0
  37. package/components/slider/fluent-slider.css +199 -0
  38. package/components/slider/fluent-slider.js +438 -0
  39. package/components/spinner/fluent-spinner.css +160 -0
  40. package/components/spinner/fluent-spinner.js +30 -0
  41. package/components/switch/fluent-switch.css +154 -0
  42. package/components/switch/fluent-switch.js +260 -0
  43. package/components/text/fluent-text.css +128 -0
  44. package/components/text/fluent-text.js +21 -0
  45. package/components/text-input/fluent-text-input.css +227 -0
  46. package/components/text-input/fluent-text-input.js +298 -0
  47. package/components/textarea/fluent-textarea.css +227 -0
  48. package/components/textarea/fluent-textarea.js +400 -0
  49. package/components/tooltip/fluent-tooltip.css +65 -0
  50. package/components/tooltip/fluent-tooltip.js +102 -0
  51. package/components/tree/fluent-tree.css +16 -0
  52. package/components/tree/fluent-tree.js +167 -0
  53. package/components/tree-item/fluent-tree-item.css +147 -0
  54. package/components/tree-item/fluent-tree-item.js +163 -0
  55. package/core/fluent-element.js +34 -0
  56. package/gallery.html +492 -0
  57. package/package.json +19 -0
  58. package/theme/theme-picker.js +38 -0
  59. package/tokens.css +724 -0
@@ -0,0 +1,124 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: flex; }
4
+
5
+ :host([hidden]) .root {
6
+ display: none;
7
+ }
8
+
9
+ .root {
10
+ display: flex;
11
+ }
12
+
13
+ .root::after,
14
+ .root::before {
15
+ align-self: center;
16
+ background: var(--colorNeutralStroke2);
17
+ box-sizing: border-box;
18
+ content: '';
19
+ display: flex;
20
+ flex-grow: 1;
21
+ height: var(--strokeWidthThin);
22
+ }
23
+
24
+ :host([inset]) .root {
25
+ padding: 0 12px;
26
+ }
27
+
28
+ .root ::slotted(*) {
29
+ color: var(--colorNeutralForeground2);
30
+ font-family: var(--fontFamilyBase);
31
+ font-size: var(--fontSizeBase200);
32
+ font-weight: var(--fontWeightRegular);
33
+ margin: 0;
34
+ padding: 0 12px;
35
+ }
36
+
37
+ :host([align-content='start']) .root::before,
38
+ :host([align-content='end']) .root::after {
39
+ flex-basis: 12px;
40
+ flex-grow: 0;
41
+ flex-shrink: 0;
42
+ }
43
+
44
+ :host([orientation='vertical']) .root {
45
+ align-items: center;
46
+ flex-direction: column;
47
+ height: 100%;
48
+ min-height: 84px;
49
+ }
50
+
51
+ :host([orientation='vertical']:empty) .root {
52
+ min-height: 20px;
53
+ }
54
+
55
+ :host([orientation='vertical'][inset]) .root::before {
56
+ margin-top: 12px;
57
+ }
58
+ :host([orientation='vertical'][inset]) .root::after {
59
+ margin-bottom: 12px;
60
+ }
61
+
62
+ :host([orientation='vertical']:empty) .root::before,
63
+ :host([orientation='vertical']:empty) .root::after {
64
+ height: 10px;
65
+ min-height: 10px;
66
+ flex-grow: 0;
67
+ }
68
+
69
+ :host([orientation='vertical']) .root::before,
70
+ :host([orientation='vertical']) .root::after {
71
+ width: var(--strokeWidthThin);
72
+ min-height: 20px;
73
+ height: 100%;
74
+ }
75
+
76
+ :host([orientation='vertical']) .root ::slotted(*) {
77
+ display: flex;
78
+ flex-direction: column;
79
+ padding: 12px 0;
80
+ line-height: 20px;
81
+ }
82
+
83
+ :host([orientation='vertical'][align-content='start']) .root::before {
84
+ min-height: 8px;
85
+ }
86
+ :host([orientation='vertical'][align-content='end']) .root::after {
87
+ min-height: 8px;
88
+ }
89
+
90
+ :host([appearance='strong']) .root::before,
91
+ :host([appearance='strong']) .root::after {
92
+ background: var(--colorNeutralStroke1);
93
+ }
94
+ :host([appearance='strong']) .root ::slotted(*) {
95
+ color: var(--colorNeutralForeground1);
96
+ }
97
+ :host([appearance='brand']) .root::before,
98
+ :host([appearance='brand']) .root::after {
99
+ background: var(--colorBrandStroke1);
100
+ }
101
+ :host([appearance='brand']) .root ::slotted(*) {
102
+ color: var(--colorBrandForeground1);
103
+ }
104
+ :host([appearance='subtle']) .root::before,
105
+ :host([appearance='subtle']) .root::after {
106
+ background: var(--colorNeutralStroke3);
107
+ }
108
+ :host([appearance='subtle']) .root ::slotted(*) {
109
+ color: var(--colorNeutralForeground3);
110
+ }
111
+
112
+ @media (forced-colors: active) {
113
+ :host([appearance='strong']) .root::before,
114
+ :host([appearance='strong']) .root::after,
115
+ :host([appearance='brand']) .root::before,
116
+ :host([appearance='brand']) .root::after,
117
+ :host([appearance='subtle']) .root::before,
118
+ :host([appearance='subtle']) .root::after,
119
+ .root::after,
120
+ .root::before {
121
+ background: WindowText;
122
+ color: WindowText;
123
+ }
124
+ }
@@ -0,0 +1,14 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-divider.css', import.meta.url).href;
4
+
5
+ class FluentDivider extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `<div class="root"><slot></slot></div>`;
8
+
9
+ static get observedAttributes() {
10
+ return ['role', 'orientation', 'align-content', 'appearance', 'inset'];
11
+ }
12
+ }
13
+
14
+ customElements.define('fluent-divider', FluentDivider);
@@ -0,0 +1,73 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-block; }
4
+
5
+ .root {
6
+ display: inline-block;
7
+ }
8
+
9
+ :host([block]) .root {
10
+ display: block;
11
+ }
12
+
13
+ :host ::slotted(img) {
14
+ box-sizing: border-box;
15
+ min-height: 8px;
16
+ min-width: 8px;
17
+ display: inline-block;
18
+ }
19
+
20
+ :host([block]) ::slotted(img) {
21
+ width: 100%;
22
+ height: auto;
23
+ }
24
+
25
+ :host([bordered]) ::slotted(img) {
26
+ border: var(--strokeWidthThin) solid var(--colorNeutralStroke2);
27
+ }
28
+
29
+ :host([fit='none']) ::slotted(img) {
30
+ object-fit: none;
31
+ object-position: top left;
32
+ height: 100%;
33
+ width: 100%;
34
+ }
35
+
36
+ :host([fit='center']) ::slotted(img) {
37
+ object-fit: none;
38
+ object-position: center;
39
+ height: 100%;
40
+ width: 100%;
41
+ }
42
+
43
+ :host([fit='contain']) ::slotted(img) {
44
+ object-fit: contain;
45
+ object-position: center;
46
+ height: 100%;
47
+ width: 100%;
48
+ }
49
+
50
+ :host([fit='cover']) ::slotted(img) {
51
+ object-fit: cover;
52
+ object-position: center;
53
+ height: 100%;
54
+ width: 100%;
55
+ }
56
+
57
+ :host([shadow]) ::slotted(img) {
58
+ box-shadow: var(--shadow4);
59
+ }
60
+
61
+ :host([shape='circular']) ::slotted(img) {
62
+ border-radius: var(--borderRadiusCircular);
63
+ }
64
+
65
+ :host([shape='rounded']) ::slotted(img) {
66
+ border-radius: var(--borderRadiusMedium);
67
+ }
68
+
69
+ @media (forced-colors: active) {
70
+ :host([bordered]) ::slotted(img) {
71
+ border-color: ButtonText;
72
+ }
73
+ }
@@ -0,0 +1,36 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-image.css', import.meta.url).href;
4
+
5
+ class FluentImage extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `<div class="root"><slot></slot></div>`;
8
+
9
+ static formAssociated = true;
10
+
11
+ constructor() {
12
+ super();
13
+ this._internals = this.attachInternals();
14
+ }
15
+
16
+ static get observedAttributes() {
17
+ return ['src', 'alt', 'fit', 'shape', 'block', 'bordered', 'shadow'];
18
+ }
19
+
20
+ connectedCallback() {
21
+ super.connectedCallback();
22
+ const img = this.querySelector('img');
23
+ if (!img) {
24
+ const src = this.getAttribute('src');
25
+ const alt = this.getAttribute('alt') || '';
26
+ if (src) {
27
+ const newImg = document.createElement('img');
28
+ newImg.src = src;
29
+ newImg.alt = alt;
30
+ this.appendChild(newImg);
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ customElements.define('fluent-image', FluentImage);
@@ -0,0 +1,49 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-flex; }
4
+
5
+ .root {
6
+ display: inline-flex;
7
+ color: var(--colorNeutralForeground1);
8
+ cursor: pointer;
9
+ font-family: var(--fontFamilyBase);
10
+ font-size: var(--fontSizeBase300);
11
+ font-weight: var(--fontWeightRegular);
12
+ line-height: var(--lineHeightBase300);
13
+ user-select: none;
14
+ }
15
+
16
+ .asterisk {
17
+ color: var(--colorPaletteRedForeground1);
18
+ margin-inline-start: var(--spacingHorizontalXS);
19
+ }
20
+
21
+ :host(:not([required])) .root .asterisk {
22
+ display: none;
23
+ }
24
+
25
+ :host([size='small']) .root {
26
+ font-size: var(--fontSizeBase200);
27
+ line-height: var(--lineHeightBase200);
28
+ }
29
+
30
+ :host([size='large']) .root {
31
+ font-size: var(--fontSizeBase400);
32
+ line-height: var(--lineHeightBase400);
33
+ }
34
+
35
+ :host(:is([size='large'], [weight='semibold'])) .root {
36
+ font-weight: var(--fontWeightSemibold);
37
+ }
38
+
39
+ :host([disabled]) .root,
40
+ :host([disabled]) .root .asterisk {
41
+ color: var(--colorNeutralForegroundDisabled);
42
+ }
43
+
44
+ @media (forced-colors: active) {
45
+ :host([disabled]) .root,
46
+ :host([disabled]) .root .asterisk {
47
+ color: GrayText;
48
+ }
49
+ }
@@ -0,0 +1,61 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-label.css', import.meta.url).href;
4
+
5
+ class FluentLabel extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `
8
+ <label class="root">
9
+ <slot></slot>
10
+ <span class="asterisk" part="asterisk" aria-hidden="true">*</span>
11
+ </label>
12
+ `;
13
+
14
+ static get observedAttributes() {
15
+ return ['disabled', 'required', 'size', 'weight', 'for'];
16
+ }
17
+
18
+ connectedCallback() {
19
+ super.connectedCallback();
20
+ requestAnimationFrame(() => {
21
+ this._updateFor();
22
+ this._updateRequired();
23
+ });
24
+ }
25
+
26
+ changed(name, oldVal, newVal) {
27
+ switch (name) {
28
+ case 'for':
29
+ this._updateFor();
30
+ break;
31
+ case 'required':
32
+ this._updateRequired();
33
+ break;
34
+ case 'disabled':
35
+ case 'size':
36
+ case 'weight':
37
+ break;
38
+ }
39
+ }
40
+
41
+ _updateFor() {
42
+ const label = this._root?.querySelector('.root');
43
+ if (!label) return;
44
+ const forAttr = this.getAttribute('for');
45
+ if (forAttr) {
46
+ label.setAttribute('for', forAttr);
47
+ } else {
48
+ label.removeAttribute('for');
49
+ }
50
+ }
51
+
52
+ _updateRequired() {
53
+ const label = this._root?.querySelector('.root');
54
+ if (!label) return;
55
+ const asterisk = label.querySelector('.asterisk');
56
+ if (!asterisk) return;
57
+ asterisk.style.display = this.hasAttribute('required') ? 'inline' : 'none';
58
+ }
59
+ }
60
+
61
+ customElements.define('fluent-label', FluentLabel);
@@ -0,0 +1,72 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline; }
4
+
5
+ :host([hidden]) .root {
6
+ display: none;
7
+ }
8
+
9
+ .root {
10
+ display: inline;
11
+ box-sizing: border-box;
12
+ background-color: transparent;
13
+ color: var(--colorBrandForegroundLink);
14
+ cursor: pointer;
15
+ font-family: var(--fontFamilyBase);
16
+ font-size: var(--fontSizeBase300);
17
+ font-weight: var(--fontWeightRegular);
18
+ text-align: start;
19
+ text-decoration: none;
20
+ text-decoration-thickness: var(--strokeWidthThin);
21
+ user-select: text;
22
+ }
23
+
24
+ .root:is(:hover, :focus-visible) {
25
+ outline: none;
26
+ text-decoration-line: underline;
27
+ }
28
+
29
+ @media (hover: hover) {
30
+ .root:hover {
31
+ color: var(--colorBrandForegroundLinkHover);
32
+ }
33
+
34
+ .root:active {
35
+ color: var(--colorBrandForegroundLinkPressed);
36
+ }
37
+
38
+ :host([appearance='subtle']) .root:hover {
39
+ color: var(--colorNeutralForeground2LinkHover);
40
+ }
41
+
42
+ :host([appearance='subtle']) .root:active {
43
+ color: var(--colorNeutralForeground2LinkPressed);
44
+ }
45
+ }
46
+
47
+ :host([appearance='subtle']) .root {
48
+ color: var(--colorNeutralForeground2Link);
49
+ }
50
+
51
+ :host-context(:is(h1, h2, h3, h4, h5, h6, p, fluent-text)),
52
+ :host([inline]) .root {
53
+ font: inherit;
54
+ text-decoration: underline;
55
+ }
56
+
57
+ :host(:not([href])) .root {
58
+ color: inherit;
59
+ text-decoration: none;
60
+ pointer-events: none;
61
+ }
62
+
63
+ :host([disabled]) .root {
64
+ color: var(--colorNeutralForegroundDisabled);
65
+ text-decoration: none;
66
+ }
67
+
68
+ @media (forced-colors: active) {
69
+ .root {
70
+ color: LinkText;
71
+ }
72
+ }
@@ -0,0 +1,109 @@
1
+ import { FluentElement } from '../../core/fluent-element.js';
2
+
3
+ const stylesUrl = new URL('./fluent-link.css', import.meta.url).href;
4
+
5
+ class FluentLink extends FluentElement {
6
+ static stylesUrl = stylesUrl;
7
+ static template = `<a class="root"><slot></slot></a>`;
8
+
9
+ static get observedAttributes() {
10
+ return ['appearance', 'inline', 'disabled', 'href', 'target', 'rel', 'hreflang', 'ping', 'referrerpolicy', 'type', 'download'];
11
+ }
12
+
13
+ constructor() {
14
+ super();
15
+ this._boundClick = this._handleClick.bind(this);
16
+ }
17
+
18
+ connectedCallback() {
19
+ super.connectedCallback();
20
+ this._root.addEventListener('click', this._boundClick);
21
+ const anchor = this._root.querySelector('.root');
22
+ if (anchor) {
23
+ this._syncAllAttrs(anchor);
24
+ }
25
+ }
26
+
27
+ disconnectedCallback() {
28
+ super.disconnectedCallback();
29
+ this._root.removeEventListener('click', this._boundClick);
30
+ }
31
+
32
+ changed(name, oldVal, newVal) {
33
+ const anchor = this._root.querySelector('.root');
34
+ if (!anchor) return;
35
+
36
+ switch (name) {
37
+ case 'href':
38
+ anchor.href = newVal || '';
39
+ break;
40
+ case 'target':
41
+ anchor.target = newVal || '';
42
+ break;
43
+ case 'rel':
44
+ anchor.rel = newVal || '';
45
+ break;
46
+ case 'hreflang':
47
+ anchor.hreflang = newVal || '';
48
+ break;
49
+ case 'ping':
50
+ anchor.ping = newVal || '';
51
+ break;
52
+ case 'referrerpolicy':
53
+ anchor.referrerPolicy = newVal || '';
54
+ break;
55
+ case 'type':
56
+ anchor.type = newVal || '';
57
+ break;
58
+ case 'download':
59
+ anchor.download = newVal || '';
60
+ break;
61
+ case 'disabled':
62
+ if (newVal !== null) {
63
+ anchor.removeAttribute('href');
64
+ anchor.style.pointerEvents = 'none';
65
+ anchor.style.cursor = 'default';
66
+ } else {
67
+ const href = this.getAttribute('href');
68
+ if (href) anchor.href = href;
69
+ anchor.style.pointerEvents = '';
70
+ anchor.style.cursor = '';
71
+ }
72
+ break;
73
+ }
74
+ }
75
+
76
+ _syncAllAttrs(anchor) {
77
+ const href = this.getAttribute('href');
78
+ if (href) anchor.href = href;
79
+ const target = this.getAttribute('target');
80
+ if (target) anchor.target = target;
81
+ const rel = this.getAttribute('rel');
82
+ if (rel) anchor.rel = rel;
83
+ const hreflang = this.getAttribute('hreflang');
84
+ if (hreflang) anchor.hreflang = hreflang;
85
+ const ping = this.getAttribute('ping');
86
+ if (ping) anchor.ping = ping;
87
+ const referrerpolicy = this.getAttribute('referrerpolicy');
88
+ if (referrerpolicy) anchor.referrerPolicy = referrerpolicy;
89
+ const type = this.getAttribute('type');
90
+ if (type) anchor.type = type;
91
+ const download = this.getAttribute('download');
92
+ if (download) anchor.download = download;
93
+ if (this.hasAttribute('disabled')) {
94
+ anchor.removeAttribute('href');
95
+ anchor.style.pointerEvents = 'none';
96
+ anchor.style.cursor = 'default';
97
+ }
98
+ }
99
+
100
+ _handleClick(e) {
101
+ if (this.hasAttribute('disabled')) {
102
+ e.preventDefault();
103
+ e.stopImmediatePropagation();
104
+ return;
105
+ }
106
+ }
107
+ }
108
+
109
+ customElements.define('fluent-link', FluentLink);
@@ -0,0 +1,57 @@
1
+ @import url('../../tokens.css');
2
+
3
+ :host { display: inline-flex; }
4
+
5
+ .root {
6
+ display: inline-block;
7
+ }
8
+
9
+ ::slotted([slot='trigger']) {
10
+ anchor-name: --menu-trigger;
11
+ }
12
+
13
+ ::slotted([popover]) {
14
+ margin: 0;
15
+ max-height: var(--menu-max-height, auto);
16
+ position-anchor: --menu-trigger;
17
+ position-area: block-end span-inline-end;
18
+ position-try-fallbacks: flip-block;
19
+ position: absolute;
20
+ z-index: 1;
21
+ }
22
+
23
+ :host([split]) ::slotted([popover]) {
24
+ position-area: block-end span-inline-start;
25
+ }
26
+
27
+ ::slotted([popover]:popover-open) {
28
+ inset: unset;
29
+ }
30
+
31
+ ::slotted([popover]:not(:popover-open)) {
32
+ display: none;
33
+ }
34
+
35
+ :host([split]) .root {
36
+ display: inline-flex;
37
+ }
38
+
39
+ :host([split]) ::slotted([slot='primary-action']) {
40
+ border-inline-end: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
41
+ border-start-end-radius: 0;
42
+ border-end-end-radius: 0;
43
+ }
44
+
45
+ :host([split]) ::slotted([slot='primary-action']:focus-visible) {
46
+ z-index: 1;
47
+ }
48
+
49
+ :host([split]) ::slotted([slot='primary-action'][appearance='primary']) {
50
+ border-inline-end: var(--strokeWidthThin) solid white;
51
+ }
52
+
53
+ :host([split]) ::slotted([slot='trigger']) {
54
+ border-inline-start: 0;
55
+ border-start-start-radius: 0;
56
+ border-end-start-radius: 0;
57
+ }