@yugnex/nexui 2.0.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.
Files changed (138) hide show
  1. package/LICENSE +59 -0
  2. package/css/nexui-base.css +157 -0
  3. package/css/nexui-icons.css +86 -0
  4. package/css/nexui-tokens.css +113 -0
  5. package/css/nexui.css +16 -0
  6. package/dist/assets/geometry.d.ts +98 -0
  7. package/dist/assets/geometry.d.ts.map +1 -0
  8. package/dist/assets/geometry.js +114 -0
  9. package/dist/assets/geometry.js.map +1 -0
  10. package/dist/assets/typography.d.ts +3 -0
  11. package/dist/assets/typography.d.ts.map +1 -0
  12. package/dist/assets/typography.js +178 -0
  13. package/dist/assets/typography.js.map +1 -0
  14. package/dist/core/compiler.d.ts +30 -0
  15. package/dist/core/compiler.d.ts.map +1 -0
  16. package/dist/core/compiler.js +124 -0
  17. package/dist/core/compiler.js.map +1 -0
  18. package/dist/core/cx.d.ts +7 -0
  19. package/dist/core/cx.d.ts.map +1 -0
  20. package/dist/core/cx.js +34 -0
  21. package/dist/core/cx.js.map +1 -0
  22. package/dist/core/matrix.d.ts +118 -0
  23. package/dist/core/matrix.d.ts.map +1 -0
  24. package/dist/core/matrix.js +180 -0
  25. package/dist/core/matrix.js.map +1 -0
  26. package/dist/index.d.ts +27 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +74 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/primitives/avatar.d.ts +8 -0
  31. package/dist/primitives/avatar.d.ts.map +1 -0
  32. package/dist/primitives/avatar.js +146 -0
  33. package/dist/primitives/avatar.js.map +1 -0
  34. package/dist/primitives/badge.d.ts +8 -0
  35. package/dist/primitives/badge.d.ts.map +1 -0
  36. package/dist/primitives/badge.js +88 -0
  37. package/dist/primitives/badge.js.map +1 -0
  38. package/dist/primitives/button.d.ts +10 -0
  39. package/dist/primitives/button.d.ts.map +1 -0
  40. package/dist/primitives/button.js +137 -0
  41. package/dist/primitives/button.js.map +1 -0
  42. package/dist/primitives/checkbox.d.ts +13 -0
  43. package/dist/primitives/checkbox.d.ts.map +1 -0
  44. package/dist/primitives/checkbox.js +107 -0
  45. package/dist/primitives/checkbox.js.map +1 -0
  46. package/dist/primitives/input.d.ts +14 -0
  47. package/dist/primitives/input.d.ts.map +1 -0
  48. package/dist/primitives/input.js +177 -0
  49. package/dist/primitives/input.js.map +1 -0
  50. package/dist/primitives/panel.d.ts +9 -0
  51. package/dist/primitives/panel.d.ts.map +1 -0
  52. package/dist/primitives/panel.js +101 -0
  53. package/dist/primitives/panel.js.map +1 -0
  54. package/dist/primitives/progress.d.ts +8 -0
  55. package/dist/primitives/progress.d.ts.map +1 -0
  56. package/dist/primitives/progress.js +105 -0
  57. package/dist/primitives/progress.js.map +1 -0
  58. package/dist/primitives/separator.d.ts +8 -0
  59. package/dist/primitives/separator.d.ts.map +1 -0
  60. package/dist/primitives/separator.js +69 -0
  61. package/dist/primitives/separator.js.map +1 -0
  62. package/dist/primitives/skeleton.d.ts +8 -0
  63. package/dist/primitives/skeleton.d.ts.map +1 -0
  64. package/dist/primitives/skeleton.js +61 -0
  65. package/dist/primitives/skeleton.js.map +1 -0
  66. package/dist/primitives/spinner.d.ts +8 -0
  67. package/dist/primitives/spinner.d.ts.map +1 -0
  68. package/dist/primitives/spinner.js +64 -0
  69. package/dist/primitives/spinner.js.map +1 -0
  70. package/dist/primitives/status-ring.d.ts +8 -0
  71. package/dist/primitives/status-ring.d.ts.map +1 -0
  72. package/dist/primitives/status-ring.js +101 -0
  73. package/dist/primitives/status-ring.js.map +1 -0
  74. package/dist/primitives/switch.d.ts +12 -0
  75. package/dist/primitives/switch.d.ts.map +1 -0
  76. package/dist/primitives/switch.js +124 -0
  77. package/dist/primitives/switch.js.map +1 -0
  78. package/dist/primitives/text-stream.d.ts +23 -0
  79. package/dist/primitives/text-stream.d.ts.map +1 -0
  80. package/dist/primitives/text-stream.js +167 -0
  81. package/dist/primitives/text-stream.js.map +1 -0
  82. package/dist/tokens/colors.d.ts +127 -0
  83. package/dist/tokens/colors.d.ts.map +1 -0
  84. package/dist/tokens/colors.js +135 -0
  85. package/dist/tokens/colors.js.map +1 -0
  86. package/dist/tokens/motion.d.ts +37 -0
  87. package/dist/tokens/motion.d.ts.map +1 -0
  88. package/dist/tokens/motion.js +93 -0
  89. package/dist/tokens/motion.js.map +1 -0
  90. package/dist/tokens/shadows.d.ts +34 -0
  91. package/dist/tokens/shadows.d.ts.map +1 -0
  92. package/dist/tokens/shadows.js +45 -0
  93. package/dist/tokens/shadows.js.map +1 -0
  94. package/dist/tokens/spacing.d.ts +69 -0
  95. package/dist/tokens/spacing.d.ts.map +1 -0
  96. package/dist/tokens/spacing.js +71 -0
  97. package/dist/tokens/spacing.js.map +1 -0
  98. package/dist/tokens/type.d.ts +166 -0
  99. package/dist/tokens/type.d.ts.map +1 -0
  100. package/dist/tokens/type.js +215 -0
  101. package/dist/tokens/type.js.map +1 -0
  102. package/fonts/NexuiIcons.woff2 +0 -0
  103. package/fonts/NexuiMono-Regular.otf +0 -0
  104. package/fonts/NexuiMono-Regular.woff2 +0 -0
  105. package/fonts/NexuiSans-Bold.otf +0 -0
  106. package/fonts/NexuiSans-Bold.woff2 +0 -0
  107. package/fonts/NexuiSans-Medium.otf +0 -0
  108. package/fonts/NexuiSans-Medium.woff2 +0 -0
  109. package/fonts/NexuiSans-Regular.otf +0 -0
  110. package/fonts/NexuiSans-Regular.woff2 +0 -0
  111. package/native/Cargo.toml +16 -0
  112. package/native/src/lib.rs +127 -0
  113. package/nexui-utils.css +485 -0
  114. package/package.json +58 -0
  115. package/src/assets/geometry.ts +144 -0
  116. package/src/assets/typography.ts +184 -0
  117. package/src/core/compiler.ts +139 -0
  118. package/src/core/cx.ts +50 -0
  119. package/src/core/matrix.ts +195 -0
  120. package/src/index.ts +78 -0
  121. package/src/primitives/avatar.ts +159 -0
  122. package/src/primitives/badge.ts +98 -0
  123. package/src/primitives/button.ts +149 -0
  124. package/src/primitives/checkbox.ts +113 -0
  125. package/src/primitives/input.ts +187 -0
  126. package/src/primitives/panel.ts +111 -0
  127. package/src/primitives/progress.ts +112 -0
  128. package/src/primitives/separator.ts +73 -0
  129. package/src/primitives/skeleton.ts +68 -0
  130. package/src/primitives/spinner.ts +71 -0
  131. package/src/primitives/status-ring.ts +109 -0
  132. package/src/primitives/switch.ts +134 -0
  133. package/src/primitives/text-stream.ts +187 -0
  134. package/src/tokens/colors.ts +149 -0
  135. package/src/tokens/motion.ts +97 -0
  136. package/src/tokens/shadows.ts +58 -0
  137. package/src/tokens/spacing.ts +79 -0
  138. package/src/tokens/type.ts +224 -0
@@ -0,0 +1,113 @@
1
+ // @yugnex/nexui — NexCheckbox Web Component
2
+ // Checkbox with indeterminate state support.
3
+ // Attributes:
4
+ // checked: boolean
5
+ // indeterminate: boolean
6
+ // disabled: boolean
7
+ // label: string
8
+ // size: sm | md | lg
9
+
10
+ import { nexui_compiler } from "../core/compiler";
11
+ import { NexuiIcons } from "../assets/geometry";
12
+
13
+ export class NexCheckbox extends HTMLElement {
14
+ private _checked = false;
15
+ private _indeterminate = false;
16
+
17
+ static get observedAttributes() {
18
+ return ["checked", "indeterminate", "disabled", "label", "size"];
19
+ }
20
+
21
+ constructor() {
22
+ super();
23
+ this.attachShadow({ mode: "open" });
24
+ }
25
+
26
+ connectedCallback() {
27
+ this._checked = this.hasAttribute("checked");
28
+ this._indeterminate = this.hasAttribute("indeterminate");
29
+ this.render();
30
+ }
31
+
32
+ attributeChangedCallback(name: string) {
33
+ if (name === "checked") this._checked = this.hasAttribute("checked");
34
+ if (name === "indeterminate") this._indeterminate = this.hasAttribute("indeterminate");
35
+ this.render();
36
+ }
37
+
38
+ get checked() { return this._checked; }
39
+ set checked(v: boolean) {
40
+ this._checked = v;
41
+ this._indeterminate = false;
42
+ v ? this.setAttribute("checked", "") : this.removeAttribute("checked");
43
+ this.removeAttribute("indeterminate");
44
+ this.render();
45
+ }
46
+
47
+ private toggle() {
48
+ if (this.hasAttribute("disabled")) return;
49
+ this.checked = !this._checked;
50
+ this.dispatchEvent(new CustomEvent("change", { detail: { checked: this._checked }, bubbles: true }));
51
+ }
52
+
53
+ private render() {
54
+ if (!this.shadowRoot) return;
55
+
56
+ const size = this.getAttribute("size") ?? "md";
57
+ const label = this.getAttribute("label") ?? "";
58
+ const disabled = this.hasAttribute("disabled");
59
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
60
+
61
+ const szMap = { sm: 14, md: 16, lg: 20 };
62
+ const sz = szMap[size as keyof typeof szMap] ?? 16;
63
+ const iconSz = Math.round(sz * 0.7);
64
+ const fs = size === "sm" ? "11px" : size === "lg" ? "13px" : "12px";
65
+
66
+ const isActive = this._checked || this._indeterminate;
67
+ const icon = this._indeterminate
68
+ ? `<svg width="${iconSz}" height="${iconSz}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><path d="M5 12h14"/></svg>`
69
+ : `<svg width="${iconSz}" height="${iconSz}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="${NexuiIcons.check}"/></svg>`;
70
+
71
+ this.shadowRoot.innerHTML = `
72
+ <style>
73
+ ${themeCSS}
74
+ :host { display: inline-flex; align-items: center; gap: 8px; cursor: ${disabled ? "not-allowed" : "pointer"}; }
75
+ .box {
76
+ width: ${sz}px;
77
+ height: ${sz}px;
78
+ border-radius: 4px;
79
+ border: 1.5px solid ${isActive ? "var(--nx-accent, #E89010)" : "var(--nx-border-strong, rgba(255,255,255,0.16))"};
80
+ background: ${isActive ? "var(--nx-accent, #E89010)" : "var(--nx-bg-elevated, #1C2128)"};
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: center;
84
+ color: #000;
85
+ opacity: ${disabled ? 0.45 : 1};
86
+ transition: background 120ms ease, border-color 120ms ease;
87
+ flex-shrink: 0;
88
+ outline: none;
89
+ }
90
+ .box:focus-visible {
91
+ box-shadow: 0 0 0 2px var(--nx-bg-base, #0D1117), 0 0 0 4px var(--nx-accent, #E89010);
92
+ }
93
+ .icon { display: ${isActive ? "flex" : "none"}; pointer-events: none; }
94
+ label {
95
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
96
+ font-size: ${fs};
97
+ color: ${disabled ? "var(--nx-text-4, #484F58)" : "var(--nx-text-2, #8B949E)"};
98
+ user-select: none;
99
+ }
100
+ </style>
101
+ <div class="box" role="checkbox" aria-checked="${this._indeterminate ? "mixed" : this._checked}" tabindex="${disabled ? -1 : 0}" part="box">
102
+ <span class="icon" aria-hidden="true">${icon}</span>
103
+ </div>
104
+ ${label ? `<label part="label">${label}</label>` : ""}
105
+ `;
106
+
107
+ const box = this.shadowRoot.querySelector(".box");
108
+ box?.addEventListener("click", () => this.toggle());
109
+ box?.addEventListener("keydown", (e) => {
110
+ if ((e as KeyboardEvent).key === " ") { e.preventDefault(); this.toggle(); }
111
+ });
112
+ }
113
+ }
@@ -0,0 +1,187 @@
1
+ // @yugnex/nexui — NexInput Web Component
2
+ // Full-featured input with label, helper text, error state, and prefix/suffix slots.
3
+ // Attributes:
4
+ // type: text | email | password | search | number | url (default: text)
5
+ // label: string
6
+ // placeholder: string
7
+ // helper: string — helper text below input
8
+ // error: string — error message (replaces helper, sets error state)
9
+ // size: sm | md | lg
10
+ // disabled: boolean
11
+ // required: boolean
12
+ // value: initial value
13
+
14
+ import { nexui_compiler } from "../core/compiler";
15
+
16
+ export class NexInput extends HTMLElement {
17
+ private input: HTMLInputElement | null = null;
18
+
19
+ static get observedAttributes() {
20
+ return ["type", "label", "placeholder", "helper", "error", "size", "disabled", "required", "value"];
21
+ }
22
+
23
+ constructor() {
24
+ super();
25
+ this.attachShadow({ mode: "open" });
26
+ }
27
+
28
+ connectedCallback() {
29
+ this.render();
30
+ this.setupProxy();
31
+ }
32
+
33
+ attributeChangedCallback() {
34
+ this.render();
35
+ this.setupProxy();
36
+ }
37
+
38
+ // Expose input value as a property on the custom element
39
+ get value(): string {
40
+ return this.input?.value ?? this.getAttribute("value") ?? "";
41
+ }
42
+ set value(v: string) {
43
+ if (this.input) this.input.value = v;
44
+ this.setAttribute("value", v);
45
+ }
46
+
47
+ focus() { this.input?.focus(); }
48
+ blur() { this.input?.blur(); }
49
+
50
+ private setupProxy() {
51
+ const input = this.shadowRoot?.querySelector<HTMLInputElement>("input");
52
+ if (!input) return;
53
+ this.input = input;
54
+
55
+ // Re-dispatch events from shadow root so they bubble out
56
+ const forward = (e: Event) => this.dispatchEvent(new (e.constructor as typeof Event)(e.type, e));
57
+ input.addEventListener("input", forward);
58
+ input.addEventListener("change", forward);
59
+ input.addEventListener("focus", forward);
60
+ input.addEventListener("blur", forward);
61
+ input.addEventListener("keydown", (e) => this.dispatchEvent(new KeyboardEvent("keydown", e)));
62
+ }
63
+
64
+ private render() {
65
+ if (!this.shadowRoot) return;
66
+
67
+ const type = this.getAttribute("type") ?? "text";
68
+ const label = this.getAttribute("label") ?? "";
69
+ const placeholder = this.getAttribute("placeholder") ?? "";
70
+ const helper = this.getAttribute("helper") ?? "";
71
+ const error = this.getAttribute("error") ?? "";
72
+ const size = this.getAttribute("size") ?? "md";
73
+ const disabled = this.hasAttribute("disabled");
74
+ const required = this.hasAttribute("required");
75
+ const value = this.getAttribute("value") ?? "";
76
+ const inputId = `nx-input-${Math.random().toString(36).slice(2, 7)}`;
77
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
78
+ const hasError = error.length > 0;
79
+
80
+ const sizeVars: Record<string, { fs: string; ph: string; radius: string; }> = {
81
+ sm: { fs: "12px", ph: "6px 10px", radius: "5px" },
82
+ md: { fs: "13px", ph: "8px 12px", radius: "6px" },
83
+ lg: { fs: "14px", ph: "10px 14px", radius: "7px" },
84
+ };
85
+ const sv = sizeVars[size] ?? sizeVars.md;
86
+
87
+ this.shadowRoot.innerHTML = `
88
+ <style>
89
+ ${themeCSS}
90
+ :host {
91
+ display: block;
92
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
93
+ }
94
+ .field {
95
+ display: flex;
96
+ flex-direction: column;
97
+ gap: 5px;
98
+ }
99
+ label {
100
+ font-size: 12px;
101
+ font-weight: 500;
102
+ color: var(--nx-text-2, #8B949E);
103
+ letter-spacing: 0.02em;
104
+ user-select: none;
105
+ }
106
+ label .req {
107
+ color: var(--nx-error, #EF4444);
108
+ margin-left: 2px;
109
+ }
110
+ .input-wrap {
111
+ position: relative;
112
+ display: flex;
113
+ align-items: center;
114
+ }
115
+ input {
116
+ width: 100%;
117
+ background: var(--nx-bg-elevated, #1C2128);
118
+ border: 1px solid ${hasError ? "var(--nx-error,#EF4444)" : "var(--nx-border,rgba(255,255,255,0.08))"};
119
+ border-radius: ${sv.radius};
120
+ color: var(--nx-text, #E6EDF3);
121
+ font-family: inherit;
122
+ font-size: ${sv.fs};
123
+ padding: ${sv.ph};
124
+ line-height: 1.5;
125
+ outline: none;
126
+ transition: border-color 150ms ease, box-shadow 150ms ease, background 150ms ease;
127
+ box-sizing: border-box;
128
+ -webkit-appearance: none;
129
+ }
130
+ input::placeholder {
131
+ color: var(--nx-text-4, #484F58);
132
+ }
133
+ input:hover:not(:disabled):not(:focus) {
134
+ border-color: var(--nx-border-strong, rgba(255,255,255,0.16));
135
+ }
136
+ input:focus {
137
+ border-color: ${hasError ? "var(--nx-error,#EF4444)" : "var(--nx-border-focus,rgba(232,144,16,0.60))"};
138
+ box-shadow: 0 0 0 3px ${hasError ? "rgba(239,68,68,0.15)" : "rgba(232,144,16,0.12)"};
139
+ background: var(--nx-bg-surface, #161B22);
140
+ }
141
+ input:disabled {
142
+ opacity: 0.45;
143
+ cursor: not-allowed;
144
+ }
145
+ .prefix-slot, .suffix-slot {
146
+ position: absolute;
147
+ top: 50%;
148
+ transform: translateY(-50%);
149
+ color: var(--nx-text-3, #6E7681);
150
+ pointer-events: none;
151
+ }
152
+ .prefix-slot { left: 10px; }
153
+ .suffix-slot { right: 10px; }
154
+ :host([has-prefix]) input { padding-left: 34px; }
155
+ :host([has-suffix]) input { padding-right: 34px; }
156
+ .help {
157
+ font-size: 11px;
158
+ color: ${hasError ? "var(--nx-error,#EF4444)" : "var(--nx-text-3,#6E7681)"};
159
+ line-height: 1.4;
160
+ }
161
+ </style>
162
+ <div class="field" part="field">
163
+ ${label ? `<label for="${inputId}">${label}${required ? '<span class="req" aria-hidden="true">*</span>' : ""}</label>` : ""}
164
+ <div class="input-wrap">
165
+ <span class="prefix-slot" aria-hidden="true"><slot name="prefix"></slot></span>
166
+ <input
167
+ id="${inputId}"
168
+ type="${type}"
169
+ placeholder="${placeholder}"
170
+ ${value ? `value="${value}"` : ""}
171
+ ${disabled ? "disabled" : ""}
172
+ ${required ? "required" : ""}
173
+ ${hasError ? `aria-invalid="true" aria-describedby="${inputId}-help"` : ""}
174
+ part="input"
175
+ />
176
+ <span class="suffix-slot" aria-hidden="true"><slot name="suffix"></slot></span>
177
+ </div>
178
+ ${error || helper
179
+ ? `<span class="help" id="${inputId}-help" role="${hasError ? "alert" : ""}">${error || helper}</span>`
180
+ : ""}
181
+ </div>
182
+ `;
183
+
184
+ this.input = this.shadowRoot.querySelector("input");
185
+ if (this.input && value) this.input.value = value;
186
+ }
187
+ }
@@ -0,0 +1,111 @@
1
+ // @yugnex/nexui — NexPanel Web Component
2
+ // A themed container that uses the LayoutMatrix bitmask system.
3
+ // Shadow DOM safe: injects theme vars + compiled matrix CSS into its own shadow root.
4
+
5
+ import { nexui_compiler } from "../core/compiler";
6
+ import { LayoutMatrix } from "../core/matrix";
7
+
8
+ export class NexPanel extends HTMLElement {
9
+ static get observedAttributes() {
10
+ return ["matrix", "variant", "padding", "elevation"];
11
+ }
12
+
13
+ constructor() {
14
+ super();
15
+ this.attachShadow({ mode: "open" });
16
+ }
17
+
18
+ connectedCallback() {
19
+ this.render();
20
+ }
21
+
22
+ attributeChangedCallback() {
23
+ this.render();
24
+ }
25
+
26
+ private getMask(): number {
27
+ const raw = this.getAttribute("matrix");
28
+ if (raw !== null) return parseInt(raw, 16);
29
+
30
+ // Build from convenience attributes if matrix is not set
31
+ let mask = 0;
32
+ const padding = this.getAttribute("padding") ?? "md";
33
+ const padMap: Record<string, number> = {
34
+ none: LayoutMatrix.PAD_NONE, xs: LayoutMatrix.PAD_XS,
35
+ sm: LayoutMatrix.PAD_SM, md: LayoutMatrix.PAD_MD,
36
+ lg: LayoutMatrix.PAD_LG, xl: LayoutMatrix.PAD_XL,
37
+ };
38
+ mask |= padMap[padding] ?? LayoutMatrix.PAD_MD;
39
+ return mask;
40
+ }
41
+
42
+ private render() {
43
+ if (!this.shadowRoot) return;
44
+
45
+ const mask = this.getMask();
46
+ const variant = this.getAttribute("variant") ?? "base";
47
+ const elevation = this.getAttribute("elevation") ?? "0";
48
+
49
+ const bgMap: Record<string, string> = {
50
+ "void": "var(--nx-bg-void)",
51
+ "base": "var(--nx-bg-base)",
52
+ "surface": "var(--nx-bg-surface)",
53
+ "elevated": "var(--nx-bg-elevated)",
54
+ "overlay": "var(--nx-bg-overlay)",
55
+ "accent": "var(--nx-accent-dim)",
56
+ "live": "var(--nx-live-dim)",
57
+ "success": "var(--nx-success-dim)",
58
+ "error": "var(--nx-error-dim)",
59
+ "warning": "var(--nx-warning-dim)",
60
+ };
61
+
62
+ const shadowMap: Record<string, string> = {
63
+ "0": "none",
64
+ "1": "0 1px 3px rgba(0,0,0,0.50)",
65
+ "2": "0 4px 6px rgba(0,0,0,0.40), 0 2px 4px rgba(0,0,0,0.30)",
66
+ "3": "0 10px 15px rgba(0,0,0,0.50), 0 4px 6px rgba(0,0,0,0.30)",
67
+ "4": "0 20px 25px rgba(0,0,0,0.55), 0 8px 10px rgba(0,0,0,0.30)",
68
+ };
69
+
70
+ const matrixCSS = nexui_compiler.compileBitmaskToString(mask);
71
+ const matrixClass = `nx-m-${mask.toString(16).toUpperCase()}`;
72
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
73
+
74
+ this.shadowRoot.innerHTML = `
75
+ <style>
76
+ ${themeCSS}
77
+ ${matrixCSS}
78
+ :host {
79
+ display: block;
80
+ }
81
+ .panel {
82
+ background: ${bgMap[variant] ?? bgMap["base"]};
83
+ border: 1px solid var(--nx-border);
84
+ border-radius: 8px;
85
+ box-shadow: ${shadowMap[elevation] ?? "none"};
86
+ color: var(--nx-text);
87
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
88
+ transition: border-color 150ms ease, box-shadow 150ms ease;
89
+ }
90
+ .panel[data-variant="elevated"] {
91
+ border-color: var(--nx-border-strong);
92
+ }
93
+ .panel[data-variant="accent"] {
94
+ border-color: var(--nx-accent-border);
95
+ }
96
+ .panel[data-variant="live"] {
97
+ border-color: var(--nx-live-border);
98
+ }
99
+ .panel[data-variant="error"] {
100
+ border-color: var(--nx-error-dim);
101
+ }
102
+ .panel[data-variant="success"] {
103
+ border-color: var(--nx-success-dim);
104
+ }
105
+ </style>
106
+ <div class="panel ${matrixClass}" data-variant="${variant}">
107
+ <slot></slot>
108
+ </div>
109
+ `;
110
+ }
111
+ }
@@ -0,0 +1,112 @@
1
+ // @yugnex/nexui — NexProgress Web Component
2
+ // Linear and circular progress indicators.
3
+ // Attributes:
4
+ // value: 0-100 (default 0). Omit for indeterminate.
5
+ // variant: linear | circular (default: linear)
6
+ // size: sm | md | lg
7
+ // color: accent | live | success | error | warning (default: accent)
8
+ // label: string — accessible label
9
+ // show-value: boolean — renders the percentage text
10
+
11
+ import { nexui_compiler } from "../core/compiler";
12
+
13
+ export class NexProgress extends HTMLElement {
14
+ static get observedAttributes() {
15
+ return ["value", "variant", "size", "color", "label", "show-value"];
16
+ }
17
+
18
+ constructor() {
19
+ super();
20
+ this.attachShadow({ mode: "open" });
21
+ }
22
+
23
+ connectedCallback() { this.render(); }
24
+ attributeChangedCallback() { this.render(); }
25
+
26
+ private render() {
27
+ if (!this.shadowRoot) return;
28
+
29
+ const rawVal = this.getAttribute("value");
30
+ const value = rawVal !== null ? Math.min(100, Math.max(0, parseFloat(rawVal))) : null;
31
+ const variant = this.getAttribute("variant") ?? "linear";
32
+ const size = this.getAttribute("size") ?? "md";
33
+ const color = this.getAttribute("color") ?? "accent";
34
+ const label = this.getAttribute("label") ?? "Progress";
35
+ const showValue = this.getAttribute("show-value") === "true";
36
+ const indeterminate = value === null;
37
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
38
+
39
+ const colorMap: Record<string, string> = {
40
+ accent: "var(--nx-accent, #E89010)",
41
+ live: "var(--nx-live, #0FD4C6)",
42
+ success: "var(--nx-success, #22C55E)",
43
+ error: "var(--nx-error, #EF4444)",
44
+ warning: "var(--nx-warning, #EAB308)",
45
+ };
46
+ const fillColor = colorMap[color] ?? colorMap.accent;
47
+
48
+ if (variant === "circular") {
49
+ const szMap = { sm: 32, md: 48, lg: 64 };
50
+ const sz = szMap[size as keyof typeof szMap] ?? 48;
51
+ const r = (sz / 2) - 4;
52
+ const circ = 2 * Math.PI * r;
53
+ const offset = indeterminate ? circ * 0.25 : circ - (value! / 100) * circ;
54
+
55
+ this.shadowRoot.innerHTML = `
56
+ <style>
57
+ ${themeCSS}
58
+ :host { display: inline-flex; align-items: center; gap: 8px; }
59
+ .arc {
60
+ transition: stroke-dashoffset 400ms cubic-bezier(0,0,0.2,1);
61
+ transform-origin: center;
62
+ transform: rotate(-90deg);
63
+ }
64
+ .indeterminate { animation: nx-spin-arc 1.4s linear infinite; }
65
+ @keyframes nx-spin-arc { to { transform: rotate(270deg); } }
66
+ .val { font-family: var(--nx-font-mono, monospace); font-size: ${Math.round(sz * 0.22)}px; fill: var(--nx-text, #E6EDF3); }
67
+ </style>
68
+ <svg width="${sz}" height="${sz}" viewBox="0 0 ${sz} ${sz}" aria-label="${label}${value !== null ? `: ${value}%` : ""}" role="progressbar" aria-valuenow="${value ?? ""}" aria-valuemin="0" aria-valuemax="100">
69
+ <circle cx="${sz/2}" cy="${sz/2}" r="${r}" fill="none" stroke="var(--nx-border, rgba(255,255,255,0.08))" stroke-width="4"/>
70
+ <circle class="arc${indeterminate ? " indeterminate" : ""}" cx="${sz/2}" cy="${sz/2}" r="${r}" fill="none"
71
+ stroke="${fillColor}" stroke-width="4" stroke-linecap="round"
72
+ stroke-dasharray="${circ}" stroke-dashoffset="${offset}"/>
73
+ ${showValue && !indeterminate ? `<text class="val" x="${sz/2}" y="${sz/2 + sz * 0.08}" text-anchor="middle" font-weight="600">${Math.round(value!)}%</text>` : ""}
74
+ </svg>
75
+ ${showValue && !indeterminate ? "" : ""}
76
+ `;
77
+ } else {
78
+ const hMap = { sm: "3px", md: "5px", lg: "8px" };
79
+ const h = hMap[size as keyof typeof hMap] ?? "5px";
80
+
81
+ this.shadowRoot.innerHTML = `
82
+ <style>
83
+ ${themeCSS}
84
+ :host { display: block; }
85
+ .track {
86
+ width: 100%;
87
+ height: ${h};
88
+ background: var(--nx-border, rgba(255,255,255,0.08));
89
+ border-radius: 9999px;
90
+ overflow: hidden;
91
+ }
92
+ .fill {
93
+ height: 100%;
94
+ background: ${fillColor};
95
+ border-radius: 9999px;
96
+ transition: width 400ms cubic-bezier(0,0,0.2,1);
97
+ ${indeterminate ? `animation: nx-indeterminate 1.6s ease-in-out infinite; width: 40%;` : `width: ${value}%;`}
98
+ }
99
+ .label-row { display: flex; justify-content: space-between; margin-bottom: 6px; font-family: var(--nx-font-sans, system-ui); font-size: 11px; color: var(--nx-text-3, #6E7681); }
100
+ @keyframes nx-indeterminate {
101
+ 0% { transform: translateX(-100%); }
102
+ 100% { transform: translateX(350%); }
103
+ }
104
+ </style>
105
+ <div role="progressbar" aria-label="${label}" aria-valuenow="${value ?? ""}" aria-valuemin="0" aria-valuemax="100">
106
+ ${label || showValue ? `<div class="label-row"><span>${label}</span>${showValue && !indeterminate ? `<span>${Math.round(value!)}%</span>` : ""}</div>` : ""}
107
+ <div class="track"><div class="fill"></div></div>
108
+ </div>
109
+ `;
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,73 @@
1
+ // @yugnex/nexui — NexSeparator Web Component
2
+ // Horizontal or vertical divider line.
3
+ // Attributes:
4
+ // orientation: horizontal | vertical (default: horizontal)
5
+ // variant: default | strong | muted (default: default)
6
+ // label: string — centered label text
7
+
8
+ import { nexui_compiler } from "../core/compiler";
9
+
10
+ export class NexSeparator extends HTMLElement {
11
+ static get observedAttributes() {
12
+ return ["orientation", "variant", "label"];
13
+ }
14
+
15
+ constructor() {
16
+ super();
17
+ this.attachShadow({ mode: "open" });
18
+ }
19
+
20
+ connectedCallback() { this.render(); }
21
+ attributeChangedCallback() { this.render(); }
22
+
23
+ private render() {
24
+ if (!this.shadowRoot) return;
25
+
26
+ const orientation = this.getAttribute("orientation") ?? "horizontal";
27
+ const variant = this.getAttribute("variant") ?? "default";
28
+ const label = this.getAttribute("label") ?? "";
29
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
30
+
31
+ const colorMap: Record<string, string> = {
32
+ default: "var(--nx-border, rgba(255,255,255,0.08))",
33
+ strong: "var(--nx-border-strong, rgba(255,255,255,0.16))",
34
+ muted: "rgba(255,255,255,0.04)",
35
+ };
36
+ const borderColor = colorMap[variant] ?? colorMap.default;
37
+
38
+ if (orientation === "vertical") {
39
+ this.shadowRoot.innerHTML = `
40
+ <style>
41
+ ${themeCSS}
42
+ :host { display: inline-block; width: 1px; align-self: stretch; }
43
+ .line { width: 1px; height: 100%; background: ${borderColor}; }
44
+ </style>
45
+ <div class="line" role="separator" aria-orientation="vertical"></div>
46
+ `;
47
+ } else if (label) {
48
+ this.shadowRoot.innerHTML = `
49
+ <style>
50
+ ${themeCSS}
51
+ :host { display: block; }
52
+ .row { display: flex; align-items: center; gap: 12px; }
53
+ .line { flex: 1; height: 1px; background: ${borderColor}; }
54
+ .text { font-family: var(--nx-font-sans, system-ui); font-size: 11px; font-weight: 500; color: var(--nx-text-4, #484F58); letter-spacing: 0.06em; text-transform: uppercase; white-space: nowrap; }
55
+ </style>
56
+ <div class="row" role="separator">
57
+ <div class="line"></div>
58
+ <span class="text">${label}</span>
59
+ <div class="line"></div>
60
+ </div>
61
+ `;
62
+ } else {
63
+ this.shadowRoot.innerHTML = `
64
+ <style>
65
+ ${themeCSS}
66
+ :host { display: block; }
67
+ .line { width: 100%; height: 1px; background: ${borderColor}; }
68
+ </style>
69
+ <div class="line" role="separator" aria-orientation="horizontal"></div>
70
+ `;
71
+ }
72
+ }
73
+ }
@@ -0,0 +1,68 @@
1
+ // @yugnex/nexui — NexSkeleton Web Component
2
+ // Loading placeholder with shimmer animation.
3
+ // Attributes:
4
+ // variant: text | circle | rect (default: rect)
5
+ // width: CSS value (default: 100%)
6
+ // height: CSS value (default: 16px for text, 40px for rect)
7
+ // lines: number — for text variant, renders N stacked lines
8
+ // animate: boolean (default: true)
9
+
10
+ import { nexui_compiler } from "../core/compiler";
11
+
12
+ export class NexSkeleton extends HTMLElement {
13
+ static get observedAttributes() {
14
+ return ["variant", "width", "height", "lines", "animate"];
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this.attachShadow({ mode: "open" });
20
+ }
21
+
22
+ connectedCallback() { this.render(); }
23
+ attributeChangedCallback() { this.render(); }
24
+
25
+ private render() {
26
+ if (!this.shadowRoot) return;
27
+
28
+ const variant = this.getAttribute("variant") ?? "rect";
29
+ const width = this.getAttribute("width") ?? "100%";
30
+ const animate = this.getAttribute("animate") !== "false";
31
+ const lines = Math.max(1, parseInt(this.getAttribute("lines") ?? "1", 10));
32
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
33
+
34
+ let height = this.getAttribute("height");
35
+ if (!height) {
36
+ height = variant === "text" ? "12px" : variant === "circle" ? width : "40px";
37
+ }
38
+
39
+ const baseStyle = `
40
+ background: linear-gradient(
41
+ 90deg,
42
+ var(--nx-bg-elevated, #1C2128) 25%,
43
+ var(--nx-bg-overlay, #21262D) 50%,
44
+ var(--nx-bg-elevated, #1C2128) 75%
45
+ );
46
+ background-size: 200% 100%;
47
+ ${animate ? "animation: nx-shimmer 1.8s ease-in-out infinite;" : ""}
48
+ border-radius: ${variant === "circle" ? "50%" : variant === "text" ? "4px" : "6px"};
49
+ `;
50
+
51
+ if (variant === "text" && lines > 1) {
52
+ const lineEls = Array.from({ length: lines }, (_, i) => {
53
+ const w = i === lines - 1 ? "65%" : "100%";
54
+ return `<div style="${baseStyle} width:${w}; height:${height}; margin-bottom:${i < lines - 1 ? "8px" : "0"};"></div>`;
55
+ }).join("");
56
+
57
+ this.shadowRoot.innerHTML = `
58
+ <style>${themeCSS} :host{display:block;} @keyframes nx-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}</style>
59
+ <div role="status" aria-label="Loading..." style="width:${width};">${lineEls}</div>
60
+ `;
61
+ } else {
62
+ this.shadowRoot.innerHTML = `
63
+ <style>${themeCSS} :host{display:block;} @keyframes nx-shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}</style>
64
+ <div role="status" aria-label="Loading..." style="${baseStyle} width:${width}; height:${height};"></div>
65
+ `;
66
+ }
67
+ }
68
+ }