@kupola/kupola 1.2.0 → 1.4.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 (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +330 -286
  3. package/adapters/axios.d.ts +34 -0
  4. package/adapters/axios.js +122 -0
  5. package/adapters/navios-http.d.ts +110 -0
  6. package/adapters/navios-http.js +151 -0
  7. package/css/components-ext.css +157 -0
  8. package/css/components.css +69 -0
  9. package/css/theme-dark.css +5 -5
  10. package/css/utilities.css +119 -1
  11. package/dist/css/accessibility.css +119 -0
  12. package/dist/css/animations.css +224 -0
  13. package/dist/css/brand-themes.css +300 -0
  14. package/dist/css/colors_and_type.css +441 -0
  15. package/dist/css/components-ext.css +4165 -0
  16. package/dist/css/components.css +1483 -0
  17. package/dist/css/kupola.css +1 -12
  18. package/dist/css/kupola.min.css +1 -0
  19. package/dist/css/responsive.css +697 -0
  20. package/dist/css/scaffold.css +145 -0
  21. package/dist/css/states.css +316 -0
  22. package/dist/css/theme-dark.css +296 -0
  23. package/dist/css/theme-light.css +296 -0
  24. package/dist/css/utilities.css +171 -0
  25. package/dist/icons.svg +284 -0
  26. package/dist/kupola.cjs.js +17409 -257
  27. package/dist/kupola.cjs.js.map +1 -1
  28. package/dist/kupola.css +9000 -0
  29. package/dist/kupola.esm.js +17183 -7597
  30. package/dist/kupola.esm.js.map +1 -1
  31. package/dist/kupola.min.css +1 -0
  32. package/dist/kupola.min.js +2 -0
  33. package/dist/kupola.min.js.map +1 -0
  34. package/dist/kupola.umd.js +17415 -257
  35. package/dist/kupola.umd.js.map +1 -1
  36. package/dist/plugins/vite-plugin-kupola.js +120 -0
  37. package/dist/types/kupola.d.ts +421 -323
  38. package/js/calendar.js +334 -25
  39. package/js/carousel.js +182 -48
  40. package/js/collapse.js +148 -34
  41. package/js/color-picker.js +416 -108
  42. package/js/component.js +8 -19
  43. package/js/countdown.js +9 -8
  44. package/js/data-bind.js +73 -16
  45. package/js/datepicker.js +488 -110
  46. package/js/depends.js +710 -0
  47. package/js/dialog.js +4 -2
  48. package/js/drawer.js +172 -8
  49. package/js/dropdown.js +272 -17
  50. package/js/dynamic-tags.js +156 -40
  51. package/js/fileupload.js +9 -8
  52. package/js/form.js +280 -254
  53. package/js/global-events.js +281 -188
  54. package/js/heatmap.js +10 -7
  55. package/js/i18n.js +18 -10
  56. package/js/icons.js +141 -161
  57. package/js/image-preview.js +146 -2
  58. package/js/initializer.js +151 -71
  59. package/js/kupola-core.js +123 -45
  60. package/js/kupola-lifecycle.js +13 -11
  61. package/js/message.js +8 -1
  62. package/js/modal.js +207 -59
  63. package/js/notification.js +8 -1
  64. package/js/numberinput.js +9 -8
  65. package/js/pagination.js +263 -0
  66. package/js/registry.js +29 -12
  67. package/js/select.js +482 -27
  68. package/js/slide-captcha.js +11 -2
  69. package/js/slider.js +442 -25
  70. package/js/statcard.js +9 -7
  71. package/js/table.js +1210 -0
  72. package/js/tag.js +268 -14
  73. package/js/theme-standalone.js +88 -0
  74. package/js/theme.js +14 -43
  75. package/js/timepicker.js +335 -66
  76. package/js/tooltip.js +317 -86
  77. package/js/utils.js +6 -2
  78. package/js/validation.js +6 -2
  79. package/js/virtual-list.js +11 -7
  80. package/js/web-components.js +288 -0
  81. package/package.json +81 -67
  82. package/plugins/vite-plugin-kupola.js +120 -0
  83. package/scripts/build-css.cjs +113 -0
  84. package/scripts/build-svg-sprite.cjs +107 -0
  85. package/types/kupola.d.ts +421 -323
  86. package/CHANGELOG.md +0 -130
  87. package/INTEGRATION.md +0 -440
  88. package/PROJECT_SUMMARY.md +0 -312
  89. package/SKILL.md +0 -572
  90. package/dist/utils/utils/Kupola.cs +0 -77
  91. package/dist/utils/utils/Kupola.java +0 -104
  92. package/dist/utils/utils/kupola.go +0 -120
  93. package/dist/utils/utils/kupola.js +0 -63
  94. package/dist/utils/utils/kupola.py +0 -1392
  95. package/dist/utils/utils/kupola.rb +0 -69
  96. package/js/composition-api.js +0 -458
  97. package/js/error-handler.js +0 -181
  98. package/js/http.js +0 -419
  99. package/js/kupola-devtools.js +0 -598
  100. package/js/performance.js +0 -250
  101. package/js/router.js +0 -396
  102. package/js/security.js +0 -189
  103. package/js/test-utils.js +0 -251
  104. package/templates/base.html +0 -30
  105. package/templates/base_dashboard.html +0 -99
  106. package/utils/Kupola.cs +0 -77
  107. package/utils/Kupola.java +0 -104
  108. package/utils/kupola.go +0 -120
  109. package/utils/kupola.js +0 -63
  110. package/utils/kupola.py +0 -1392
  111. package/utils/kupola.rb +0 -69
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Axios adapter for Kupola HTTP client plugin system.
3
+ */
4
+
5
+ export interface AxiosAdapterOptions {
6
+ /** Additional Axios request config to merge into every request */
7
+ [key: string]: any;
8
+ }
9
+
10
+ /**
11
+ * Create a Kupola-compatible HTTP client from an Axios instance.
12
+ *
13
+ * @param axiosInstance - An Axios instance (from axios.create() or default axios)
14
+ * @param options - Additional Axios request config to merge into every request
15
+ * @returns Kupola HTTP client object with a `fetch` function
16
+ */
17
+ export function createAxiosAdapter(
18
+ axiosInstance: any,
19
+ options?: AxiosAdapterOptions
20
+ ): {
21
+ fetch(url: string, fetchOptions?: {
22
+ method?: string;
23
+ headers?: Record<string, string>;
24
+ body?: string;
25
+ }): Promise<{
26
+ ok: boolean;
27
+ status: number;
28
+ statusText: string;
29
+ headers: Record<string, string>;
30
+ url: string;
31
+ json(): Promise<any>;
32
+ text(): Promise<string>;
33
+ }>;
34
+ };
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Axios adapter for Kupola HTTP client plugin system.
3
+ *
4
+ * Allows using Axios (with interceptors, custom config, etc.) as the HTTP
5
+ * client for all useDeps() / useQuery() requests.
6
+ *
7
+ * @module adapters/axios
8
+ *
9
+ * @example
10
+ * import { configureHttpClient } from 'kupola';
11
+ * import { createAxiosAdapter } from 'kupola/adapters/axios';
12
+ * import axios from 'axios';
13
+ *
14
+ * const api = axios.create({ baseURL: '/api', timeout: 10000 });
15
+ *
16
+ * // Axios interceptors work as expected
17
+ * api.interceptors.request.use(config => {
18
+ * config.headers.Authorization = `Bearer ${getToken()}`;
19
+ * return config;
20
+ * });
21
+ *
22
+ * configureHttpClient(createAxiosAdapter(api));
23
+ */
24
+
25
+ /**
26
+ * Create a Kupola-compatible HTTP client from an Axios instance.
27
+ *
28
+ * @param {Object} axiosInstance - An Axios instance (from axios.create() or default axios)
29
+ * @param {Object} [options] - Additional Axios request config to merge into every request
30
+ * @returns {{ fetch: Function }} Kupola HTTP client object
31
+ */
32
+ export function createAxiosAdapter(axiosInstance, options = {}) {
33
+ if (!axiosInstance || typeof axiosInstance.request !== 'function') {
34
+ throw new TypeError('[Kupola] createAxiosAdapter: invalid Axios instance (must have .request method)');
35
+ }
36
+
37
+ return {
38
+ /**
39
+ * Fetch function compatible with Kupola's HTTP client interface.
40
+ * Translates Fetch API semantics to Axios, and Axios response back to
41
+ * Fetch API Response shape.
42
+ *
43
+ * @param {string} url - Request URL
44
+ * @param {Object} [fetchOptions] - Fetch-style options
45
+ * @param {string} [fetchOptions.method='GET'] - HTTP method
46
+ * @param {Object} [fetchOptions.headers] - Request headers
47
+ * @param {string} [fetchOptions.body] - Request body (JSON string)
48
+ * @returns {Promise<Object>} Response-like object { ok, status, headers, json(), text() }
49
+ */
50
+ fetch: async function (url, fetchOptions = {}) {
51
+ // Build Axios config
52
+ const axiosConfig = {
53
+ url,
54
+ method: (fetchOptions.method || 'GET').toLowerCase(),
55
+ headers: fetchOptions.headers || {},
56
+ // Don't throw on non-2xx — let Kupola handle error status
57
+ validateStatus: () => true,
58
+ ...options
59
+ };
60
+
61
+ // Parse body if present (Fetch sends body as JSON string)
62
+ if (fetchOptions.body) {
63
+ try {
64
+ axiosConfig.data = JSON.parse(fetchOptions.body);
65
+ } catch (_) {
66
+ axiosConfig.data = fetchOptions.body;
67
+ }
68
+ }
69
+
70
+ try {
71
+ const response = await axiosInstance.request(axiosConfig);
72
+
73
+ // Cache the parsed data to avoid re-parsing on multiple .json() calls
74
+ let _cachedData;
75
+ let _dataParsed = false;
76
+
77
+ return {
78
+ ok: response.status >= 200 && response.status < 300,
79
+ status: response.status,
80
+ statusText: response.statusText || '',
81
+ headers: response.headers || {},
82
+ url,
83
+
84
+ json() {
85
+ if (!_dataParsed) {
86
+ _cachedData = response.data;
87
+ _dataParsed = true;
88
+ }
89
+ return Promise.resolve(_cachedData);
90
+ },
91
+
92
+ text() {
93
+ if (!_dataParsed) {
94
+ _cachedData = typeof response.data === 'string'
95
+ ? response.data
96
+ : JSON.stringify(response.data);
97
+ _dataParsed = true;
98
+ }
99
+ return Promise.resolve(_cachedData);
100
+ }
101
+ };
102
+ } catch (error) {
103
+ // Network error or request was blocked
104
+ return {
105
+ ok: false,
106
+ status: 0,
107
+ statusText: error.message || 'Network Error',
108
+ headers: {},
109
+ url,
110
+
111
+ json() {
112
+ return Promise.resolve({ error: error.message });
113
+ },
114
+
115
+ text() {
116
+ return Promise.resolve(error.message);
117
+ }
118
+ };
119
+ }
120
+ }
121
+ };
122
+ }
@@ -0,0 +1,110 @@
1
+ /**
2
+ * @navios/http adapter for Kupola HTTP client plugin system.
3
+ *
4
+ * @navios/http is a lightweight, fetch-based axios replacement that supports
5
+ * interceptors, multiple response types, and works with Next.js / RSC.
6
+ */
7
+
8
+ /** Additional @navios/http request config fields that can be merged into every request. */
9
+ export interface NaviosAdapterOptions {
10
+ /** Response type: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'stream' */
11
+ responseType?: string;
12
+ /** Custom status validator, e.g. (status) => status < 500 */
13
+ validateStatus?: (status: number) => boolean;
14
+ /** Request credentials: 'omit' | 'same-origin' | 'include' */
15
+ credentials?: string;
16
+ /** URL search parameters */
17
+ params?: Record<string, any> | URLSearchParams;
18
+ /** Allow any other @navios/http requestConfig fields */
19
+ [key: string]: any;
20
+ }
21
+
22
+ /** Minimal @navios/http client interface required by the adapter. */
23
+ export interface NaviosHttpClient {
24
+ request(config: any): Promise<{
25
+ data: any;
26
+ status: number;
27
+ statusText?: string;
28
+ headers?: Record<string, string>;
29
+ config?: any;
30
+ }>;
31
+ get(url: string, config?: any): Promise<any>;
32
+ post(url: string, data?: any, config?: any): Promise<any>;
33
+ put(url: string, data?: any, config?: any): Promise<any>;
34
+ patch(url: string, data?: any, config?: any): Promise<any>;
35
+ delete(url: string, config?: any): Promise<any>;
36
+ head(url: string, config?: any): Promise<any>;
37
+ options(url: string, config?: any): Promise<any>;
38
+ defaults: Record<string, any>;
39
+ interceptors: {
40
+ request: { use(onFulfilled: Function, onRejected?: Function): number };
41
+ response: { use(onFulfilled: Function, onRejected?: Function): number };
42
+ };
43
+ }
44
+
45
+ /**
46
+ * Create a Kupola-compatible HTTP client from a @navios/http instance.
47
+ *
48
+ * @param naviosClient - A @navios/http client instance (from create() or default import)
49
+ * @param options - Additional @navios/http request config to merge into every request
50
+ * @returns Kupola HTTP client object with a `fetch` function
51
+ */
52
+ export function createNaviosAdapter(
53
+ naviosClient: NaviosHttpClient,
54
+ options?: NaviosAdapterOptions
55
+ ): {
56
+ fetch(url: string, fetchOptions?: {
57
+ method?: string;
58
+ headers?: Record<string, string>;
59
+ body?: string;
60
+ }): Promise<{
61
+ ok: boolean;
62
+ status: number;
63
+ statusText: string;
64
+ headers: Record<string, string>;
65
+ url: string;
66
+ json(): Promise<any>;
67
+ text(): Promise<string>;
68
+ }>;
69
+ };
70
+ /**
71
+ * @navios/http adapter for Kupola HTTP client plugin system.
72
+ */
73
+
74
+ export interface NaviosAdapterOptions {
75
+ /** Additional request config to merge into every request */
76
+ [key: string]: any;
77
+ }
78
+
79
+ /**
80
+ * Create a Kupola-compatible HTTP client from a @navios/http instance.
81
+ *
82
+ * @param naviosClient - A @navios/http client instance (from create())
83
+ * @param options - Additional request config to merge into every request
84
+ * @returns Kupola HTTP client object with a `fetch` function
85
+ */
86
+ export function createNaviosAdapter(
87
+ naviosClient: {
88
+ request(config: any): Promise<{
89
+ status: number;
90
+ statusText?: string;
91
+ headers?: Record<string, string>;
92
+ data: any;
93
+ }>;
94
+ },
95
+ options?: NaviosAdapterOptions
96
+ ): {
97
+ fetch(url: string, fetchOptions?: {
98
+ method?: string;
99
+ headers?: Record<string, string>;
100
+ body?: string;
101
+ }): Promise<{
102
+ ok: boolean;
103
+ status: number;
104
+ statusText: string;
105
+ headers: Record<string, string>;
106
+ url: string;
107
+ json(): Promise<any>;
108
+ text(): Promise<string>;
109
+ }>;
110
+ };
@@ -0,0 +1,151 @@
1
+ /**
2
+ * @navios/http adapter for Kupola HTTP client plugin system.
3
+ *
4
+ * Allows using @navios/http (with interceptors, custom config, etc.) as the HTTP
5
+ * client for all useDeps() / useQuery() requests.
6
+ *
7
+ * @navios/http is a lightweight, fetch-based axios replacement that supports
8
+ * interceptors, multiple response types, and works with Next.js / RSC.
9
+ *
10
+ * @module adapters/navios-http
11
+ *
12
+ * @example
13
+ * import { configureHttpClient } from 'kupola';
14
+ * import { createNaviosAdapter } from 'kupola/adapters/navios-http';
15
+ * import { create } from '@navios/http';
16
+ *
17
+ * const client = create({ baseURL: '/api' });
18
+ *
19
+ * // @navios/http interceptors work as expected
20
+ * client.interceptors.request.use((config) => {
21
+ * config.headers['Authorization'] = `Bearer ${getToken()}`;
22
+ * return config;
23
+ * });
24
+ *
25
+ * configureHttpClient(createNaviosAdapter(client));
26
+ */
27
+
28
+ /**
29
+ * Create a Kupola-compatible HTTP client from a @navios/http instance.
30
+ *
31
+ * Translates Kupola's fetch-style calls into @navios/http requests,
32
+ * and maps @navios/http responses back to Kupola's standard format:
33
+ * { ok, status, statusText, headers, url, json(), text() }
34
+ *
35
+ * @param {Object} naviosClient - A @navios/http client instance (from create() or default import)
36
+ * Must expose: .request(config), .get(url, config), .post(url, data, config), etc.
37
+ * @param {Object} [options] - Additional @navios/http request config merged into every request.
38
+ * Supports all @navios/http requestConfig fields:
39
+ * - responseType {string}: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'formData' | 'stream'
40
+ * - validateStatus {Function}: custom status validator, e.g. () => true
41
+ * - credentials {string}: 'omit' | 'same-origin' | 'include'
42
+ * - params {Object|URLSearchParams}: URL search parameters
43
+ * @returns {{ fetch: Function }} Kupola HTTP client object
44
+ */
45
+ export function createNaviosAdapter(naviosClient, options = {}) {
46
+ if (!naviosClient || typeof naviosClient.request !== 'function') {
47
+ throw new TypeError('[Kupola] createNaviosAdapter: invalid @navios/http client (must have .request method)');
48
+ }
49
+
50
+ return {
51
+ /**
52
+ * Fetch function compatible with Kupola's HTTP client interface.
53
+ * Translates Fetch API semantics to @navios/http, and response back to
54
+ * Fetch API Response shape.
55
+ *
56
+ * @param {string} url - Request URL
57
+ * @param {Object} [fetchOptions] - Fetch-style options
58
+ * @param {string} [fetchOptions.method='GET'] - HTTP method
59
+ * @param {Object} [fetchOptions.headers] - Request headers
60
+ * @param {string} [fetchOptions.body] - Request body (JSON string)
61
+ * @returns {Promise<Object>} Response-like object { ok, status, headers, json(), text() }
62
+ */
63
+ fetch: async function (url, fetchOptions = {}) {
64
+ const method = (fetchOptions.method || 'GET').toUpperCase();
65
+
66
+ // Build request config for @navios/http
67
+ // Note: @navios/http uses `data` for body (axios-style), not `body` (fetch-style)
68
+ const requestConfig = {
69
+ url,
70
+ method,
71
+ headers: fetchOptions.headers || {},
72
+ // Accept all status codes here; Kupola handles ok/status checking itself
73
+ validateStatus: () => true,
74
+ ...options
75
+ };
76
+
77
+ // Parse body if present (Fetch sends body as JSON string,
78
+ // but @navios/http accepts objects directly as `data`)
79
+ if (fetchOptions.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
80
+ try {
81
+ requestConfig.data = JSON.parse(fetchOptions.body);
82
+ } catch (_) {
83
+ requestConfig.data = fetchOptions.body;
84
+ }
85
+ }
86
+
87
+ try {
88
+ const response = await naviosClient.request(requestConfig);
89
+
90
+ // Cache the parsed data to avoid re-parsing on multiple .json() calls
91
+ let _cachedData;
92
+ let _dataParsed = false;
93
+
94
+ return {
95
+ ok: response.status >= 200 && response.status < 300,
96
+ status: response.status,
97
+ statusText: response.statusText || '',
98
+ headers: response.headers || {},
99
+ url,
100
+
101
+ json() {
102
+ if (!_dataParsed) {
103
+ _cachedData = response.data;
104
+ _dataParsed = true;
105
+ }
106
+ return Promise.resolve(_cachedData);
107
+ },
108
+
109
+ text() {
110
+ if (!_dataParsed) {
111
+ _cachedData = typeof response.data === 'string'
112
+ ? response.data
113
+ : JSON.stringify(response.data);
114
+ _dataParsed = true;
115
+ }
116
+ return Promise.resolve(_cachedData);
117
+ }
118
+ };
119
+ } catch (error) {
120
+ // Network error or interceptor rejection.
121
+ // @navios/http error shape: { message, response?: { status, headers, data }, config }
122
+ const errStatus = (error.response && error.response.status) || 0;
123
+ const errHeaders = (error.response && error.response.headers) || {};
124
+
125
+ return {
126
+ ok: false,
127
+ status: errStatus,
128
+ statusText: error.message || 'Network Error',
129
+ headers: errHeaders,
130
+ url,
131
+
132
+ json() {
133
+ // Try to return structured error data if available
134
+ if (error.response && error.response.data !== undefined) {
135
+ return Promise.resolve(error.response.data);
136
+ }
137
+ return Promise.resolve({ error: error.message });
138
+ },
139
+
140
+ text() {
141
+ if (error.response && error.response.data !== undefined) {
142
+ const d = error.response.data;
143
+ return Promise.resolve(typeof d === 'string' ? d : JSON.stringify(d));
144
+ }
145
+ return Promise.resolve(error.message);
146
+ }
147
+ };
148
+ }
149
+ }
150
+ };
151
+ }
@@ -1403,6 +1403,42 @@
1403
1403
  border-radius: 50%;
1404
1404
  }
1405
1405
 
1406
+ /* IMP-009: Skeleton size variants */
1407
+ .ds-skeleton--text {
1408
+ height: 14px;
1409
+ margin-bottom: var(--spacer-8);
1410
+ }
1411
+
1412
+ .ds-skeleton--heading {
1413
+ height: 24px;
1414
+ width: 40%;
1415
+ margin-bottom: var(--spacer-12);
1416
+ }
1417
+
1418
+ .ds-skeleton--block {
1419
+ height: 200px;
1420
+ }
1421
+
1422
+ .ds-skeleton--avatar {
1423
+ width: 40px;
1424
+ height: 40px;
1425
+ }
1426
+
1427
+ .ds-skeleton--button {
1428
+ height: 28px;
1429
+ width: 80px;
1430
+ }
1431
+
1432
+ .ds-skeleton--input {
1433
+ height: 28px;
1434
+ width: 100%;
1435
+ }
1436
+
1437
+ .ds-skeleton--table {
1438
+ height: 200px;
1439
+ width: 100%;
1440
+ }
1441
+
1406
1442
  @keyframes ds-skeleton-pulse {
1407
1443
  0%, 100% { opacity: 1; }
1408
1444
  50% { opacity: 0.5; }
@@ -2661,6 +2697,17 @@
2661
2697
  right: 2px;
2662
2698
  }
2663
2699
 
2700
+ /* IMP-006: Badge info variant */
2701
+ .ds-badge--info {
2702
+ background: var(--status-info-surface-l1);
2703
+ color: var(--status-info-default);
2704
+ }
2705
+
2706
+ .ds-badge--neutral {
2707
+ background: var(--bg-overlay-l2);
2708
+ color: var(--text-tertiary);
2709
+ }
2710
+
2664
2711
  /* ── Tooltip ──────────────────────────────────────────────── */
2665
2712
  [data-tooltip] {
2666
2713
  position: relative;
@@ -4162,4 +4209,114 @@
4162
4209
 
4163
4210
  .ds-virtual-list--horizontal .ds-virtual-list__item:last-child {
4164
4211
  border-right: none;
4212
+ }
4213
+
4214
+ /* ── Tree (IMP-008) ──────────────────────────────────────── */
4215
+ .ds-tree {
4216
+ list-style: none;
4217
+ padding: 0;
4218
+ margin: 0;
4219
+ font-size: var(--body-sm-font-size, 13px);
4220
+ }
4221
+
4222
+ .ds-tree ul {
4223
+ list-style: none;
4224
+ padding: 0;
4225
+ margin: 0;
4226
+ }
4227
+
4228
+ .ds-tree__item {
4229
+ display: flex;
4230
+ align-items: center;
4231
+ gap: var(--spacer-6);
4232
+ padding: var(--spacer-4) var(--spacer-8);
4233
+ border-radius: var(--radius-4);
4234
+ cursor: default;
4235
+ transition: background-color 0.12s ease;
4236
+ user-select: none;
4237
+ }
4238
+
4239
+ .ds-tree__item:hover {
4240
+ background-color: var(--bg-overlay-l1);
4241
+ }
4242
+
4243
+ .ds-tree__item.is-selected {
4244
+ background-color: var(--bg-brand-popup);
4245
+ }
4246
+
4247
+ .ds-tree__toggle {
4248
+ display: inline-flex;
4249
+ align-items: center;
4250
+ justify-content: center;
4251
+ width: 16px;
4252
+ height: 16px;
4253
+ flex-shrink: 0;
4254
+ cursor: pointer;
4255
+ color: var(--icon-secondary);
4256
+ transition: transform 0.15s ease;
4257
+ background: none;
4258
+ border: none;
4259
+ padding: 0;
4260
+ }
4261
+
4262
+ .ds-tree__toggle::before {
4263
+ content: '';
4264
+ display: block;
4265
+ width: 0;
4266
+ height: 0;
4267
+ border-left: 5px solid currentColor;
4268
+ border-top: 3.5px solid transparent;
4269
+ border-bottom: 3.5px solid transparent;
4270
+ }
4271
+
4272
+ .ds-tree__toggle.is-open {
4273
+ transform: rotate(90deg);
4274
+ }
4275
+
4276
+ .ds-tree__toggle.is-leaf {
4277
+ visibility: hidden;
4278
+ }
4279
+
4280
+ .ds-tree__checkbox {
4281
+ width: 14px;
4282
+ height: 14px;
4283
+ flex-shrink: 0;
4284
+ accent-color: var(--bg-brand);
4285
+ cursor: pointer;
4286
+ }
4287
+
4288
+ .ds-tree__icon {
4289
+ width: 16px;
4290
+ height: 16px;
4291
+ flex-shrink: 0;
4292
+ color: var(--icon-secondary);
4293
+ }
4294
+
4295
+ .ds-tree__label {
4296
+ flex: 1;
4297
+ color: var(--text-default);
4298
+ overflow: hidden;
4299
+ text-overflow: ellipsis;
4300
+ white-space: nowrap;
4301
+ }
4302
+
4303
+ .ds-tree__badge {
4304
+ font-size: var(--body-xs-font-size, 10px);
4305
+ color: var(--text-tertiary);
4306
+ margin-left: auto;
4307
+ }
4308
+
4309
+ .ds-tree__children {
4310
+ padding-left: var(--spacer-20);
4311
+ border-left: 1px solid var(--border-neutral-l1);
4312
+ margin-left: 7px;
4313
+ }
4314
+
4315
+ /* Tree line guides */
4316
+ .ds-tree--lined .ds-tree__children {
4317
+ border-left-color: var(--border-neutral-l1);
4318
+ }
4319
+
4320
+ .ds-tree--compact .ds-tree__item {
4321
+ padding: var(--spacer-2) var(--spacer-8);
4165
4322
  }
@@ -540,6 +540,24 @@
540
540
  .ds-input.is-error { border-color: var(--bg-error-default); }
541
541
  .ds-input.is-success { border-color: var(--bg-success-default); }
542
542
 
543
+ /* BUG-003 fix: ensure <select class="ds-input"> matches <input> height/padding */
544
+ select.ds-input,
545
+ .ds-input select {
546
+ height: 28px;
547
+ padding: 0 var(--spacer-24) 0 var(--spacer-12);
548
+ appearance: none;
549
+ -webkit-appearance: none;
550
+ -moz-appearance: none;
551
+ background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%239599A6' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'></polyline></svg>");
552
+ background-repeat: no-repeat;
553
+ background-position: right 8px center;
554
+ background-size: 12px;
555
+ cursor: pointer;
556
+ font: inherit;
557
+ color: var(--text-default);
558
+ }
559
+ select.ds-input::-ms-expand { display: none; }
560
+
543
561
  .ds-input__status-icon {
544
562
  position: absolute;
545
563
  right: 12px;
@@ -980,6 +998,18 @@
980
998
  }
981
999
  .ds-table__pagination { display: flex; align-items: center; gap: var(--spacer-8); }
982
1000
 
1001
+ /* Table variants (IMP-001) */
1002
+ .ds-table--striped tbody tr:nth-child(even) { background: var(--bg-base-secondary); }
1003
+ .ds-table--hover tbody tr:hover { background: var(--bg-overlay-l1); cursor: default; }
1004
+ .ds-table--compact th,
1005
+ .ds-table--compact td { padding: var(--spacer-6) var(--spacer-8); font-size: var(--body-xs-font-size); }
1006
+
1007
+ /* Table cell utilities */
1008
+ .ds-td--strong { font-weight: 500; color: var(--text-default); }
1009
+ .ds-td--actions { display: flex; gap: var(--spacer-4); align-items: center; }
1010
+ .ds-td--mono { font-family: var(--code-editor-font-family); font-variant-numeric: tabular-nums; }
1011
+ .ds-td--truncate { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1012
+
983
1013
  /* ── Tabs ──────────────────────────────────────────────────── */
984
1014
  /* ===== Tabs ===== */
985
1015
  .ds-tabs { display: flex; gap: var(--spacer-24); border-bottom: 1px solid var(--border-neutral-l1); }
@@ -1311,6 +1341,45 @@
1311
1341
  margin-top: var(--spacer-16);
1312
1342
  }
1313
1343
 
1344
+ /* IMP-004: Flat aliases — use these without BEM __ nesting */
1345
+ .ds-form-group {
1346
+ display: flex;
1347
+ flex-direction: column;
1348
+ gap: var(--spacer-6);
1349
+ margin-bottom: var(--spacer-12);
1350
+ }
1351
+
1352
+ .ds-form-label {
1353
+ display: block;
1354
+ font-size: var(--body-sm-font-size);
1355
+ font-weight: var(--font-weight-medium);
1356
+ color: var(--text-default);
1357
+ margin-bottom: var(--spacer-4);
1358
+ }
1359
+
1360
+ .ds-form-row {
1361
+ display: grid;
1362
+ grid-template-columns: repeat(2, 1fr);
1363
+ gap: var(--spacer-12);
1364
+ }
1365
+
1366
+ .ds-form-row--3 {
1367
+ grid-template-columns: repeat(3, 1fr);
1368
+ }
1369
+
1370
+ .ds-form-actions {
1371
+ display: flex;
1372
+ justify-content: flex-end;
1373
+ gap: var(--spacer-8);
1374
+ margin-top: var(--spacer-16);
1375
+ }
1376
+
1377
+ .ds-form-hint {
1378
+ font-size: var(--body-xs-font-size);
1379
+ color: var(--text-tertiary);
1380
+ margin-top: var(--spacer-2);
1381
+ }
1382
+
1314
1383
  .ds-radio-group {
1315
1384
  display: flex;
1316
1385
  gap: var(--spacer-16);