@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,195 @@
1
+ // @yugnex/nexui — Layout Matrix
2
+ // Encodes layout intent as a composable 32-bit bitmask.
3
+ // Bits 0-3: padding · Bits 4-7: flex/display · Bits 8-11: border-radius
4
+ // Bits 12-15: gap · Bits 16-19: width · Bits 20-23: overflow
5
+ // Compose with bitwise OR: PAD_MD | FLEX_ROW | ALIGN_CENTER | RAD_MD
6
+
7
+ export const enum LayoutMatrix {
8
+ // Padding (bits 0-3)
9
+ PAD_NONE = 0x0,
10
+ PAD_XS = 0x1, // 0.25rem / 4px
11
+ PAD_SM = 0x2, // 0.5rem / 8px
12
+ PAD_MD = 0x3, // 0.75rem / 12px
13
+ PAD_LG = 0x4, // 1rem / 16px
14
+ PAD_XL = 0x5, // 1.5rem / 24px
15
+ PAD_2XL = 0x6, // 2rem / 32px
16
+
17
+ // Display / Flex direction (bits 4-7)
18
+ FLEX_ROW = 0x10, // display:flex; flex-direction:row
19
+ FLEX_COL = 0x20, // display:flex; flex-direction:column
20
+ GRID_2 = 0x30, // display:grid; grid-template-columns:repeat(2,1fr)
21
+ GRID_3 = 0x40, // display:grid; grid-template-columns:repeat(3,1fr)
22
+ GRID_4 = 0x50, // display:grid; grid-template-columns:repeat(4,1fr)
23
+
24
+ // Alignment (bits 5-6 override when used with FLEX_*)
25
+ ALIGN_START = 0x60, // align-items:flex-start
26
+ ALIGN_CENTER = 0x70, // align-items:center
27
+ ALIGN_END = 0x80, // align-items:flex-end
28
+ ALIGN_STRETCH = 0x90, // align-items:stretch
29
+
30
+ // Justify content (bits 6-7 secondary)
31
+ JUSTIFY_START = 0xA0, // justify-content:flex-start
32
+ JUSTIFY_CENTER = 0xB0, // justify-content:center
33
+ JUSTIFY_END = 0xC0, // justify-content:flex-end
34
+ JUSTIFY_BET = 0xD0, // justify-content:space-between
35
+ JUSTIFY_AROUND = 0xE0, // justify-content:space-around
36
+ JUSTIFY_EVEN = 0xF0, // justify-content:space-evenly
37
+
38
+ // Border radius (bits 8-11)
39
+ RAD_NONE = 0x000,
40
+ RAD_XS = 0x100, // 2px
41
+ RAD_SM = 0x200, // 4px
42
+ RAD_MD = 0x300, // 6px
43
+ RAD_LG = 0x400, // 8px
44
+ RAD_XL = 0x500, // 12px
45
+ RAD_2XL = 0x600, // 16px
46
+ RAD_FULL = 0x700, // 9999px
47
+
48
+ // Gap (bits 12-15)
49
+ GAP_NONE = 0x0000,
50
+ GAP_XS = 0x1000, // 0.25rem / 4px
51
+ GAP_SM = 0x2000, // 0.5rem / 8px
52
+ GAP_MD = 0x3000, // 1rem / 16px
53
+ GAP_LG = 0x4000, // 1.5rem / 24px
54
+ GAP_XL = 0x5000, // 2rem / 32px
55
+
56
+ // Width (bits 16-19)
57
+ W_AUTO = 0x00000,
58
+ W_FULL = 0x10000, // width:100%
59
+ W_SCREEN = 0x20000, // width:100vw
60
+ W_FIT = 0x30000, // width:fit-content
61
+ W_MIN = 0x40000, // width:min-content
62
+ W_MAX = 0x50000, // width:max-content
63
+
64
+ // Overflow (bits 20-23)
65
+ OVERFLOW_VIS = 0x000000, // overflow:visible (default)
66
+ OVERFLOW_HIDDEN = 0x100000, // overflow:hidden
67
+ OVERFLOW_AUTO = 0x200000, // overflow:auto
68
+ OVERFLOW_SCROLL = 0x300000, // overflow:scroll
69
+ }
70
+
71
+ // Maps bitmask bits to CSS strings — used by the compiler
72
+ export const MATRIX_PAD_MAP: Record<number, string> = {
73
+ 0x1: "padding:0.25rem;",
74
+ 0x2: "padding:0.5rem;",
75
+ 0x3: "padding:0.75rem;",
76
+ 0x4: "padding:1rem;",
77
+ 0x5: "padding:1.5rem;",
78
+ 0x6: "padding:2rem;",
79
+ };
80
+
81
+ export const MATRIX_DISPLAY_MAP: Record<number, string> = {
82
+ 0x10: "display:flex;flex-direction:row;",
83
+ 0x20: "display:flex;flex-direction:column;",
84
+ 0x30: "display:grid;grid-template-columns:repeat(2,1fr);",
85
+ 0x40: "display:grid;grid-template-columns:repeat(3,1fr);",
86
+ 0x50: "display:grid;grid-template-columns:repeat(4,1fr);",
87
+ 0x60: "align-items:flex-start;",
88
+ 0x70: "align-items:center;",
89
+ 0x80: "align-items:flex-end;",
90
+ 0x90: "align-items:stretch;",
91
+ 0xA0: "justify-content:flex-start;",
92
+ 0xB0: "justify-content:center;",
93
+ 0xC0: "justify-content:flex-end;",
94
+ 0xD0: "justify-content:space-between;",
95
+ 0xE0: "justify-content:space-around;",
96
+ 0xF0: "justify-content:space-evenly;",
97
+ };
98
+
99
+ export const MATRIX_RADIUS_MAP: Record<number, string> = {
100
+ 0x100: "border-radius:2px;",
101
+ 0x200: "border-radius:4px;",
102
+ 0x300: "border-radius:6px;",
103
+ 0x400: "border-radius:8px;",
104
+ 0x500: "border-radius:12px;",
105
+ 0x600: "border-radius:16px;",
106
+ 0x700: "border-radius:9999px;",
107
+ };
108
+
109
+ export const MATRIX_GAP_MAP: Record<number, string> = {
110
+ 0x1000: "gap:0.25rem;",
111
+ 0x2000: "gap:0.5rem;",
112
+ 0x3000: "gap:1rem;",
113
+ 0x4000: "gap:1.5rem;",
114
+ 0x5000: "gap:2rem;",
115
+ };
116
+
117
+ export const MATRIX_WIDTH_MAP: Record<number, string> = {
118
+ 0x10000: "width:100%;",
119
+ 0x20000: "width:100vw;",
120
+ 0x30000: "width:fit-content;",
121
+ 0x40000: "width:min-content;",
122
+ 0x50000: "width:max-content;",
123
+ };
124
+
125
+ export const MATRIX_OVERFLOW_MAP: Record<number, string> = {
126
+ 0x100000: "overflow:hidden;",
127
+ 0x200000: "overflow:auto;",
128
+ 0x300000: "overflow:scroll;",
129
+ };
130
+
131
+ // Theme token maps — CSS custom property to value
132
+ export const NEXUI_THEMES = {
133
+ void: {
134
+ "--nx-bg-void": "#05070C",
135
+ "--nx-bg-base": "#0D1117",
136
+ "--nx-bg-surface": "#161B22",
137
+ "--nx-bg-elevated": "#1C2128",
138
+ "--nx-bg-overlay": "#21262D",
139
+ "--nx-border": "rgba(255,255,255,0.08)",
140
+ "--nx-border-strong": "rgba(255,255,255,0.16)",
141
+ "--nx-border-focus": "rgba(232,144,16,0.60)",
142
+ "--nx-text": "#E6EDF3",
143
+ "--nx-text-2": "#8B949E",
144
+ "--nx-text-3": "#6E7681",
145
+ "--nx-text-4": "#484F58",
146
+ "--nx-text-inv": "#05070C",
147
+ "--nx-accent": "#E89010",
148
+ "--nx-accent-dim": "rgba(232,144,16,0.09)",
149
+ "--nx-accent-border": "rgba(232,144,16,0.22)",
150
+ "--nx-accent-text": "#F5B342",
151
+ "--nx-live": "#0FD4C6",
152
+ "--nx-live-dim": "rgba(15,212,198,0.09)",
153
+ "--nx-live-border": "rgba(15,212,198,0.22)",
154
+ "--nx-success": "#22C55E",
155
+ "--nx-success-dim": "rgba(34,197,94,0.10)",
156
+ "--nx-error": "#EF4444",
157
+ "--nx-error-dim": "rgba(239,68,68,0.10)",
158
+ "--nx-warning": "#EAB308",
159
+ "--nx-warning-dim": "rgba(234,179,8,0.10)",
160
+ "--nx-font-sans": "'NexuiSans','Inter',system-ui,-apple-system,sans-serif",
161
+ "--nx-font-mono": "'NexuiMono','JetBrains Mono','Fira Code',ui-monospace,monospace",
162
+ },
163
+ terminal: {
164
+ "--nx-bg-void": "#000000",
165
+ "--nx-bg-base": "#050505",
166
+ "--nx-bg-surface": "#0C0E12",
167
+ "--nx-bg-elevated": "#111418",
168
+ "--nx-bg-overlay": "#161B22",
169
+ "--nx-border": "#1E293B",
170
+ "--nx-border-strong": "#2D3748",
171
+ "--nx-border-focus": "rgba(16,185,129,0.60)",
172
+ "--nx-text": "#10B981",
173
+ "--nx-text-2": "#6EE7B7",
174
+ "--nx-text-3": "#34D399",
175
+ "--nx-text-4": "#065F46",
176
+ "--nx-text-inv": "#000000",
177
+ "--nx-accent": "#E89010",
178
+ "--nx-accent-dim": "rgba(232,144,16,0.09)",
179
+ "--nx-accent-border": "rgba(232,144,16,0.22)",
180
+ "--nx-accent-text": "#F5B342",
181
+ "--nx-live": "#10B981",
182
+ "--nx-live-dim": "rgba(16,185,129,0.09)",
183
+ "--nx-live-border": "rgba(16,185,129,0.22)",
184
+ "--nx-success": "#10B981",
185
+ "--nx-success-dim": "rgba(16,185,129,0.10)",
186
+ "--nx-error": "#EF4444",
187
+ "--nx-error-dim": "rgba(239,68,68,0.10)",
188
+ "--nx-warning": "#EAB308",
189
+ "--nx-warning-dim": "rgba(234,179,8,0.10)",
190
+ "--nx-font-sans": "'NexuiMono','JetBrains Mono',ui-monospace,monospace",
191
+ "--nx-font-mono": "'NexuiMono','JetBrains Mono',ui-monospace,monospace",
192
+ },
193
+ } as const;
194
+
195
+ export type NexuiTheme = keyof typeof NEXUI_THEMES;
package/src/index.ts ADDED
@@ -0,0 +1,78 @@
1
+ // @yugnex/nexui — Main Entry Point v2.0
2
+
3
+ export * from "./tokens/colors";
4
+ export * from "./tokens/spacing";
5
+ export * from "./tokens/motion";
6
+ export * from "./tokens/shadows";
7
+ export * from "./tokens/type";
8
+ export * from "./core/matrix";
9
+ export * from "./core/compiler";
10
+ export * from "./core/cx";
11
+ export * from "./assets/typography";
12
+ export * from "./assets/geometry";
13
+
14
+ export { NexPanel } from "./primitives/panel";
15
+ export { NexStatusRing } from "./primitives/status-ring";
16
+ export { NexTextStream } from "./primitives/text-stream";
17
+ export { NexButton } from "./primitives/button";
18
+ export { NexBadge } from "./primitives/badge";
19
+ export { NexInput } from "./primitives/input";
20
+ export { NexAvatar } from "./primitives/avatar";
21
+ export { NexProgress } from "./primitives/progress";
22
+ export { NexSwitch } from "./primitives/switch";
23
+ export { NexCheckbox } from "./primitives/checkbox";
24
+ export { NexSkeleton } from "./primitives/skeleton";
25
+ export { NexSeparator } from "./primitives/separator";
26
+ export { NexSpinner } from "./primitives/spinner";
27
+
28
+ import { nexui_compiler } from "./core/compiler";
29
+ import { NexuiTypographySheet } from "./assets/typography";
30
+ import { NexPanel } from "./primitives/panel";
31
+ import { NexStatusRing } from "./primitives/status-ring";
32
+ import { NexTextStream } from "./primitives/text-stream";
33
+ import { NexButton } from "./primitives/button";
34
+ import { NexBadge } from "./primitives/badge";
35
+ import { NexInput } from "./primitives/input";
36
+ import { NexAvatar } from "./primitives/avatar";
37
+ import { NexProgress } from "./primitives/progress";
38
+ import { NexSwitch } from "./primitives/switch";
39
+ import { NexCheckbox } from "./primitives/checkbox";
40
+ import { NexSkeleton } from "./primitives/skeleton";
41
+ import { NexSeparator } from "./primitives/separator";
42
+ import { NexSpinner } from "./primitives/spinner";
43
+ import type { NexuiTheme } from "./core/matrix";
44
+
45
+ const ELEMENTS: Array<[string, CustomElementConstructor]> = [
46
+ ["nex-panel", NexPanel],
47
+ ["nex-status-ring", NexStatusRing],
48
+ ["nex-text-stream", NexTextStream],
49
+ ["nex-button", NexButton],
50
+ ["nex-badge", NexBadge],
51
+ ["nex-input", NexInput],
52
+ ["nex-avatar", NexAvatar],
53
+ ["nex-progress", NexProgress],
54
+ ["nex-switch", NexSwitch],
55
+ ["nex-checkbox", NexCheckbox],
56
+ ["nex-skeleton", NexSkeleton],
57
+ ["nex-separator", NexSeparator],
58
+ ["nex-spinner", NexSpinner],
59
+ ];
60
+
61
+ export async function initializeNexuiEngine(defaultTheme: NexuiTheme = "void"): Promise<void> {
62
+ if (typeof window === "undefined") return;
63
+ if (!document.getElementById("yugnex-nexui-typography")) {
64
+ const s = document.createElement("style");
65
+ s.id = "yugnex-nexui-typography";
66
+ s.textContent = NexuiTypographySheet;
67
+ document.head.appendChild(s);
68
+ }
69
+ nexui_compiler.mountGlobalTheme(defaultTheme);
70
+ for (const [tag, ctor] of ELEMENTS) {
71
+ if (!customElements.get(tag)) customElements.define(tag, ctor);
72
+ }
73
+ }
74
+
75
+ export function setNexuiTheme(theme: NexuiTheme): void {
76
+ nexui_compiler.switchTheme(theme);
77
+ window.dispatchEvent(new CustomEvent("nexui:theme-change", { detail: { theme } }));
78
+ }
@@ -0,0 +1,159 @@
1
+ // @yugnex/nexui — NexAvatar Web Component
2
+ // User avatar: image with initials fallback + optional status indicator.
3
+ // Attributes:
4
+ // src: image URL
5
+ // name: full name — used for initials and alt text
6
+ // size: xs | sm | md | lg | xl (default: md)
7
+ // status: online | away | busy | offline (optional)
8
+ // shape: circle | square (default: circle)
9
+
10
+ import { nexui_compiler } from "../core/compiler";
11
+
12
+ function getInitials(name: string): string {
13
+ const parts = name.trim().split(/\s+/).filter(Boolean);
14
+ if (parts.length === 0) return "?";
15
+ if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
16
+ return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
17
+ }
18
+
19
+ // Deterministic hue from name string for initials background
20
+ function nameToHue(name: string): number {
21
+ let hash = 0;
22
+ for (let i = 0; i < name.length; i++) {
23
+ hash = (hash * 31 + name.charCodeAt(i)) >>> 0;
24
+ }
25
+ return hash % 360;
26
+ }
27
+
28
+ export class NexAvatar extends HTMLElement {
29
+ static get observedAttributes() {
30
+ return ["src", "name", "size", "status", "shape"];
31
+ }
32
+
33
+ constructor() {
34
+ super();
35
+ this.attachShadow({ mode: "open" });
36
+ }
37
+
38
+ connectedCallback() {
39
+ this.render();
40
+ }
41
+
42
+ attributeChangedCallback() {
43
+ this.render();
44
+ }
45
+
46
+ private render() {
47
+ if (!this.shadowRoot) return;
48
+
49
+ const src = this.getAttribute("src") ?? "";
50
+ const name = this.getAttribute("name") ?? "";
51
+ const size = this.getAttribute("size") ?? "md";
52
+ const status = this.getAttribute("status") ?? "";
53
+ const shape = this.getAttribute("shape") ?? "circle";
54
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
55
+
56
+ const sizeMap: Record<string, { px: number; fs: number; dot: number; }> = {
57
+ xs: { px: 20, fs: 8, dot: 5 },
58
+ sm: { px: 28, fs: 11, dot: 7 },
59
+ md: { px: 36, fs: 14, dot: 9 },
60
+ lg: { px: 48, fs: 18, dot: 11 },
61
+ xl: { px: 64, fs: 24, dot: 13 },
62
+ };
63
+ const s = sizeMap[size] ?? sizeMap.md;
64
+
65
+ const borderRadius = shape === "square"
66
+ ? `${Math.round(s.px * 0.2)}px`
67
+ : "50%";
68
+
69
+ const initials = name ? getInitials(name) : "?";
70
+ const hue = nameToHue(name || "?");
71
+ const fallbackBg = `hsl(${hue}, 35%, 22%)`;
72
+ const fallbackFg = `hsl(${hue}, 70%, 72%)`;
73
+
74
+ const statusColor: Record<string, string> = {
75
+ online: "#22C55E",
76
+ away: "#EAB308",
77
+ busy: "#EF4444",
78
+ offline: "#484F58",
79
+ };
80
+
81
+ const dotColor = statusColor[status] ?? "";
82
+ const dotBorder = size === "xs" ? "1px" : "2px";
83
+
84
+ const imgContent = src
85
+ ? `<img src="${src}" alt="${name ? `Avatar of ${name}` : "User avatar"}" loading="lazy" />`
86
+ : `<span class="initials" aria-hidden="true">${initials}</span>`;
87
+
88
+ this.shadowRoot.innerHTML = `
89
+ <style>
90
+ ${themeCSS}
91
+ :host {
92
+ display: inline-block;
93
+ position: relative;
94
+ width: ${s.px}px;
95
+ height: ${s.px}px;
96
+ flex-shrink: 0;
97
+ }
98
+ .avatar {
99
+ width: ${s.px}px;
100
+ height: ${s.px}px;
101
+ border-radius: ${borderRadius};
102
+ background: ${fallbackBg};
103
+ border: 1px solid var(--nx-border, rgba(255,255,255,0.08));
104
+ overflow: hidden;
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ user-select: none;
109
+ }
110
+ img {
111
+ width: 100%;
112
+ height: 100%;
113
+ object-fit: cover;
114
+ display: block;
115
+ }
116
+ .initials {
117
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
118
+ font-size: ${s.fs}px;
119
+ font-weight: 600;
120
+ color: ${fallbackFg};
121
+ line-height: 1;
122
+ letter-spacing: 0.02em;
123
+ }
124
+ ${status ? `.dot {
125
+ position: absolute;
126
+ bottom: 0;
127
+ right: 0;
128
+ width: ${s.dot}px;
129
+ height: ${s.dot}px;
130
+ border-radius: 50%;
131
+ background: ${dotColor};
132
+ border: ${dotBorder} solid var(--nx-bg-base, #0D1117);
133
+ }` : ""}
134
+ </style>
135
+ <div class="avatar" role="img" aria-label="${name ? `Avatar of ${name}` : "User avatar"}" part="avatar">
136
+ ${imgContent}
137
+ </div>
138
+ ${status ? `<span class="dot" aria-label="${status}" title="${status}"></span>` : ""}
139
+ `;
140
+
141
+ // Hide initials when image loads successfully
142
+ if (src) {
143
+ const img = this.shadowRoot.querySelector<HTMLImageElement>("img");
144
+ const initialsEl = this.shadowRoot.querySelector<HTMLSpanElement>(".initials");
145
+ if (img && initialsEl) {
146
+ img.addEventListener("error", () => {
147
+ img.style.display = "none";
148
+ // Create and append initials span if image fails
149
+ const span = document.createElement("span");
150
+ span.className = "initials";
151
+ span.setAttribute("aria-hidden", "true");
152
+ span.textContent = initials;
153
+ span.style.cssText = `font-family:var(--nx-font-sans,system-ui,sans-serif);font-size:${s.fs}px;font-weight:600;color:${fallbackFg};line-height:1;letter-spacing:0.02em;`;
154
+ img.parentElement?.appendChild(span);
155
+ });
156
+ }
157
+ }
158
+ }
159
+ }
@@ -0,0 +1,98 @@
1
+ // @yugnex/nexui — NexBadge Web Component
2
+ // Inline status chip for labels, counts, and state indicators.
3
+ // Attributes:
4
+ // variant: default | accent | live | success | error | warning | muted
5
+ // size: sm | md
6
+ // dot: boolean — shows a pulsing presence dot before the text
7
+
8
+ import { nexui_compiler } from "../core/compiler";
9
+
10
+ export class NexBadge extends HTMLElement {
11
+ static get observedAttributes() {
12
+ return ["variant", "size", "dot"];
13
+ }
14
+
15
+ constructor() {
16
+ super();
17
+ this.attachShadow({ mode: "open" });
18
+ }
19
+
20
+ connectedCallback() {
21
+ this.render();
22
+ }
23
+
24
+ attributeChangedCallback() {
25
+ this.render();
26
+ }
27
+
28
+ private render() {
29
+ if (!this.shadowRoot) return;
30
+
31
+ const variant = this.getAttribute("variant") ?? "default";
32
+ const size = this.getAttribute("size") ?? "md";
33
+ const showDot = this.getAttribute("dot") === "true";
34
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
35
+
36
+ const variantCSS: Record<string, string> = {
37
+ default: `background:var(--nx-bg-elevated,#1C2128); color:var(--nx-text-2,#8B949E); border:1px solid var(--nx-border,rgba(255,255,255,0.08));`,
38
+ accent: `background:var(--nx-accent-dim,rgba(232,144,16,0.09)); color:var(--nx-accent-text,#F5B342); border:1px solid var(--nx-accent-border,rgba(232,144,16,0.22));`,
39
+ live: `background:var(--nx-live-dim,rgba(15,212,198,0.09)); color:var(--nx-live,#0FD4C6); border:1px solid var(--nx-live-border,rgba(15,212,198,0.22));`,
40
+ success: `background:var(--nx-success-dim,rgba(34,197,94,0.10)); color:var(--nx-success,#22C55E); border:1px solid rgba(34,197,94,0.25);`,
41
+ error: `background:var(--nx-error-dim,rgba(239,68,68,0.10)); color:var(--nx-error,#EF4444); border:1px solid rgba(239,68,68,0.25);`,
42
+ warning: `background:var(--nx-warning-dim,rgba(234,179,8,0.10)); color:var(--nx-warning,#EAB308); border:1px solid rgba(234,179,8,0.25);`,
43
+ muted: `background:rgba(255,255,255,0.04); color:var(--nx-text-3,#6E7681); border:1px solid transparent;`,
44
+ };
45
+
46
+ const dotColor: Record<string, string> = {
47
+ default: "var(--nx-text-4,#484F58)",
48
+ accent: "var(--nx-accent,#E89010)",
49
+ live: "var(--nx-live,#0FD4C6)",
50
+ success: "var(--nx-success,#22C55E)",
51
+ error: "var(--nx-error,#EF4444)",
52
+ warning: "var(--nx-warning,#EAB308)",
53
+ muted: "var(--nx-text-4,#484F58)",
54
+ };
55
+
56
+ const sizeCSS = size === "sm"
57
+ ? `font-size:10px; padding:2px 6px; border-radius:4px; gap:4px;`
58
+ : `font-size:11px; padding:3px 8px; border-radius:5px; gap:5px;`;
59
+
60
+ const dotSize = size === "sm" ? 5 : 6;
61
+
62
+ this.shadowRoot.innerHTML = `
63
+ <style>
64
+ ${themeCSS}
65
+ :host {
66
+ display: inline-flex;
67
+ }
68
+ .badge {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ font-family: var(--nx-font-mono, ui-monospace, monospace);
72
+ font-weight: 500;
73
+ letter-spacing: 0.04em;
74
+ white-space: nowrap;
75
+ line-height: 1;
76
+ ${sizeCSS}
77
+ ${variantCSS[variant] ?? variantCSS.default}
78
+ }
79
+ .dot {
80
+ width: ${dotSize}px;
81
+ height: ${dotSize}px;
82
+ border-radius: 50%;
83
+ background: ${dotColor[variant] ?? dotColor.default};
84
+ flex-shrink: 0;
85
+ animation: ${variant === "live" ? "nx-pulse 2s ease-in-out infinite" : "none"};
86
+ }
87
+ @keyframes nx-pulse {
88
+ 0%, 100% { opacity: 1; }
89
+ 50% { opacity: 0.35; }
90
+ }
91
+ </style>
92
+ <span class="badge" part="badge">
93
+ ${showDot ? '<span class="dot" aria-hidden="true"></span>' : ""}
94
+ <slot></slot>
95
+ </span>
96
+ `;
97
+ }
98
+ }
@@ -0,0 +1,149 @@
1
+ // @yugnex/nexui — NexButton Web Component
2
+ // Attributes:
3
+ // variant: primary | secondary | ghost | danger | outline | accent | live
4
+ // size: sm | md | lg
5
+ // loading: boolean
6
+ // disabled: boolean (native HTML attribute)
7
+ // icon-only: boolean (square aspect ratio)
8
+
9
+ import { nexui_compiler } from "../core/compiler";
10
+
11
+ export class NexButton extends HTMLElement {
12
+ private btn: HTMLButtonElement | null = null;
13
+
14
+ static get observedAttributes() {
15
+ return ["variant", "size", "loading", "disabled", "icon-only", "type"];
16
+ }
17
+
18
+ constructor() {
19
+ super();
20
+ this.attachShadow({ mode: "open" });
21
+ }
22
+
23
+ connectedCallback() {
24
+ this.render();
25
+ this.setupDelegation();
26
+ }
27
+
28
+ attributeChangedCallback() {
29
+ this.render();
30
+ this.setupDelegation();
31
+ }
32
+
33
+ // Forward clicks from the host element to the internal button
34
+ private setupDelegation() {
35
+ this.onclick = (e) => {
36
+ if (this.hasAttribute("disabled") || this.getAttribute("loading") === "true") {
37
+ e.stopImmediatePropagation();
38
+ return;
39
+ }
40
+ };
41
+ }
42
+
43
+ private render() {
44
+ if (!this.shadowRoot) return;
45
+
46
+ const variant = this.getAttribute("variant") ?? "primary";
47
+ const size = this.getAttribute("size") ?? "md";
48
+ const loading = this.getAttribute("loading") === "true";
49
+ const disabled = this.hasAttribute("disabled") || loading;
50
+ const iconOnly = this.getAttribute("icon-only") === "true";
51
+ const type = this.getAttribute("type") ?? "button";
52
+ const themeCSS = nexui_compiler.getThemeCSS(nexui_compiler.getActiveTheme(), ":host");
53
+
54
+ const sizeStyles: Record<string, string> = {
55
+ sm: `font-size:11px; padding:${iconOnly ? "5px" : "5px 10px"}; min-height:28px; border-radius:5px; gap:6px;`,
56
+ md: `font-size:13px; padding:${iconOnly ? "7px" : "7px 14px"}; min-height:34px; border-radius:6px; gap:8px;`,
57
+ lg: `font-size:14px; padding:${iconOnly ? "9px" : "9px 18px"}; min-height:40px; border-radius:7px; gap:10px;`,
58
+ };
59
+
60
+ const variantStyles: Record<string, string> = {
61
+ primary: `background:var(--nx-accent,#E89010); color:#000; border:none;`,
62
+ secondary: `background:var(--nx-bg-elevated,#1C2128); color:var(--nx-text,#E6EDF3); border:1px solid var(--nx-border-strong,rgba(255,255,255,0.16));`,
63
+ ghost: `background:transparent; color:var(--nx-text,#E6EDF3); border:none;`,
64
+ outline: `background:transparent; color:var(--nx-text,#E6EDF3); border:1px solid var(--nx-border-strong,rgba(255,255,255,0.16));`,
65
+ danger: `background:var(--nx-error,#EF4444); color:#fff; border:none;`,
66
+ accent: `background:var(--nx-accent-dim,rgba(232,144,16,0.09)); color:var(--nx-accent-text,#F5B342); border:1px solid var(--nx-accent-border,rgba(232,144,16,0.22));`,
67
+ live: `background:var(--nx-live-dim,rgba(15,212,198,0.09)); color:var(--nx-live,#0FD4C6); border:1px solid var(--nx-live-border,rgba(15,212,198,0.22));`,
68
+ };
69
+
70
+ const hoverStyles: Record<string, string> = {
71
+ primary: `filter:brightness(1.1);`,
72
+ secondary: `background:var(--nx-bg-overlay,#21262D);`,
73
+ ghost: `background:rgba(255,255,255,0.06);`,
74
+ outline: `background:rgba(255,255,255,0.04); border-color:rgba(255,255,255,0.24);`,
75
+ danger: `filter:brightness(1.1);`,
76
+ accent: `background:rgba(232,144,16,0.14);`,
77
+ live: `background:rgba(15,212,198,0.14);`,
78
+ };
79
+
80
+ const iconSize = size === "sm" ? 12 : size === "lg" ? 16 : 14;
81
+
82
+ this.shadowRoot.innerHTML = `
83
+ <style>
84
+ ${themeCSS}
85
+ :host {
86
+ display: inline-block;
87
+ }
88
+ button {
89
+ display: inline-flex;
90
+ align-items: center;
91
+ justify-content: center;
92
+ font-family: var(--nx-font-sans, system-ui, sans-serif);
93
+ font-weight: 500;
94
+ letter-spacing: 0.01em;
95
+ cursor: pointer;
96
+ user-select: none;
97
+ white-space: nowrap;
98
+ text-decoration: none;
99
+ transition: background 150ms ease, color 150ms ease, border-color 150ms ease, filter 150ms ease, opacity 150ms ease, box-shadow 150ms ease;
100
+ ${sizeStyles[size] ?? sizeStyles.md}
101
+ ${variantStyles[variant] ?? variantStyles.primary}
102
+ ${iconOnly ? `aspect-ratio: 1; padding: ${size === "sm" ? "5px" : size === "lg" ? "9px" : "7px"};` : ""}
103
+ }
104
+ button:hover:not(:disabled):not(.loading) {
105
+ ${hoverStyles[variant] ?? ""}
106
+ }
107
+ button:active:not(:disabled):not(.loading) {
108
+ transform: translateY(1px);
109
+ filter: brightness(0.95);
110
+ }
111
+ button:focus-visible {
112
+ outline: none;
113
+ box-shadow: 0 0 0 2px var(--nx-bg-base, #0D1117), 0 0 0 4px var(--nx-accent, #E89010);
114
+ }
115
+ button:disabled, button.loading {
116
+ opacity: 0.45;
117
+ cursor: not-allowed;
118
+ }
119
+ .spinner {
120
+ width: ${iconSize}px;
121
+ height: ${iconSize}px;
122
+ border: 1.5px solid transparent;
123
+ border-top-color: currentColor;
124
+ border-radius: 50%;
125
+ animation: nx-spin 600ms linear infinite;
126
+ flex-shrink: 0;
127
+ }
128
+ @keyframes nx-spin {
129
+ to { transform: rotate(360deg); }
130
+ }
131
+ ::slotted(svg) {
132
+ width: ${iconSize}px;
133
+ height: ${iconSize}px;
134
+ flex-shrink: 0;
135
+ }
136
+ </style>
137
+ <button
138
+ type="${type}"
139
+ ${disabled ? "disabled" : ""}
140
+ ${loading ? 'class="loading"' : ""}
141
+ part="button"
142
+ >
143
+ ${loading ? '<span class="spinner" aria-hidden="true"></span>' : ""}
144
+ <slot></slot>
145
+ </button>
146
+ `;
147
+ this.btn = this.shadowRoot.querySelector("button");
148
+ }
149
+ }