@rettangoli/ui 0.1.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 (59) hide show
  1. package/README.md +49 -0
  2. package/dist/rettangoli-iife-layout.min.js +728 -0
  3. package/dist/rettangoli-iife-ui.min.js +830 -0
  4. package/package.json +53 -0
  5. package/src/common/BaseElement.js +182 -0
  6. package/src/common.js +190 -0
  7. package/src/components/dialog/dialog.handlers.js +5 -0
  8. package/src/components/dialog/dialog.store.js +25 -0
  9. package/src/components/dialog/dialog.view.yaml +44 -0
  10. package/src/components/dropdownMenu/dropdownMenu.handlers.js +18 -0
  11. package/src/components/dropdownMenu/dropdownMenu.store.js +25 -0
  12. package/src/components/dropdownMenu/dropdownMenu.view.yaml +54 -0
  13. package/src/components/form/form.handlers.js +63 -0
  14. package/src/components/form/form.store.js +45 -0
  15. package/src/components/form/form.view.yaml +174 -0
  16. package/src/components/navbar/navbar.examples.yaml +86 -0
  17. package/src/components/navbar/navbar.handlers.js +10 -0
  18. package/src/components/navbar/navbar.store.js +46 -0
  19. package/src/components/navbar/navbar.view.yaml +74 -0
  20. package/src/components/pageOutline/pageOutline.handlers.js +69 -0
  21. package/src/components/pageOutline/pageOutline.store.js +40 -0
  22. package/src/components/pageOutline/pageOutline.view.yaml +34 -0
  23. package/src/components/popover/popover.handlers.js +5 -0
  24. package/src/components/popover/popover.store.js +12 -0
  25. package/src/components/popover/popover.view.yaml +57 -0
  26. package/src/components/select/select.handlers.js +61 -0
  27. package/src/components/select/select.store.js +65 -0
  28. package/src/components/select/select.view.yaml +50 -0
  29. package/src/components/sidebar/sidebar.handlers.js +36 -0
  30. package/src/components/sidebar/sidebar.store.js +142 -0
  31. package/src/components/sidebar/sidebar.view.yaml +190 -0
  32. package/src/components/table/table.handlers.js +55 -0
  33. package/src/components/table/table.store.js +72 -0
  34. package/src/components/table/table.view.yaml +103 -0
  35. package/src/entry-iife-layout.js +15 -0
  36. package/src/entry-iife-ui.js +22 -0
  37. package/src/index.js +17 -0
  38. package/src/lib/uhtml.js +9 -0
  39. package/src/primitives/button.js +306 -0
  40. package/src/primitives/colorPicker.js +213 -0
  41. package/src/primitives/image.js +234 -0
  42. package/src/primitives/input.js +218 -0
  43. package/src/primitives/slider.js +269 -0
  44. package/src/primitives/svg.js +95 -0
  45. package/src/primitives/text.js +141 -0
  46. package/src/primitives/textarea.js +103 -0
  47. package/src/primitives/view.js +217 -0
  48. package/src/setup.js +16 -0
  49. package/src/styles/anchorStyles.js +13 -0
  50. package/src/styles/buttonMarginStyles.js +84 -0
  51. package/src/styles/cursorStyles.js +12 -0
  52. package/src/styles/flexChildStyles.js +43 -0
  53. package/src/styles/flexDirectionStyles.js +74 -0
  54. package/src/styles/marginStyles.js +13 -0
  55. package/src/styles/paddingSvgStyles.js +120 -0
  56. package/src/styles/scrollStyles.js +22 -0
  57. package/src/styles/textColorStyles.js +14 -0
  58. package/src/styles/textStyles.js +62 -0
  59. package/src/styles/viewStyles.js +114 -0
@@ -0,0 +1,65 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ isOpen: false,
3
+ position: {
4
+ x: 0,
5
+ y: 0,
6
+ },
7
+ selectedValue: null,
8
+ selectedLabel: null,
9
+ });
10
+
11
+ export const toViewData = ({ state, props }) => {
12
+ // Calculate display label
13
+ let displayLabel = props.placeholder || 'Select an option';
14
+
15
+ // Use state's selected value if available, otherwise use props.selectedValue
16
+ const currentValue = state.selectedValue !== null ? state.selectedValue : props.selectedValue;
17
+
18
+ if (currentValue !== null && currentValue !== undefined && props.options) {
19
+ const selectedOption = props.options.find(opt => opt.value === currentValue);
20
+ if (selectedOption) {
21
+ displayLabel = selectedOption.label;
22
+ }
23
+ } else if (state.selectedLabel) {
24
+ displayLabel = state.selectedLabel;
25
+ }
26
+
27
+ // Map options to include isSelected flag and computed background color
28
+ const optionsWithSelection = (props.options || []).map(option => ({
29
+ ...option,
30
+ isSelected: option.value === currentValue,
31
+ bgc: option.value === currentValue ? 'mu' : ''
32
+ }));
33
+
34
+ return {
35
+ isOpen: state.isOpen,
36
+ position: state.position,
37
+ options: optionsWithSelection,
38
+ selectedValue: currentValue,
39
+ selectedLabel: displayLabel,
40
+ placeholder: props.placeholder || 'Select an option'
41
+ };
42
+ }
43
+
44
+ export const selectState = ({ state }) => {
45
+ return state;
46
+ }
47
+
48
+ export const openOptionsPopover = (state, payload) => {
49
+ const { position } = payload;
50
+ state.position = position;
51
+ state.isOpen = true;
52
+ }
53
+
54
+ export const closeOptionsPopover = (state) => {
55
+ state.isOpen = false;
56
+ }
57
+
58
+ export const updateSelectOption = (state, option) => {
59
+ state.selectedValue = option.value;
60
+ state.selectedLabel = option.label;
61
+ state.isOpen = false;
62
+ }
63
+
64
+
65
+
@@ -0,0 +1,50 @@
1
+ elementName: rtgl-select
2
+
3
+ viewDataSchema:
4
+ type: object
5
+
6
+ propsSchema:
7
+ type: object
8
+ properties:
9
+ options:
10
+ type: array
11
+ items:
12
+ type: object
13
+ properties:
14
+ id:
15
+ type: string
16
+ label:
17
+ type: string
18
+ value:
19
+ type: any
20
+ selectedValue:
21
+ type: any
22
+ placeholder:
23
+ type: string
24
+ onChange:
25
+ type: function
26
+
27
+ refs:
28
+ select-button:
29
+ eventListeners:
30
+ click:
31
+ handler: handleButtonClick
32
+ popover:
33
+ eventListeners:
34
+ click-overlay:
35
+ handler: handleClickOptionsPopoverOverlay
36
+ option-*:
37
+ eventListeners:
38
+ click:
39
+ handler: handleOptionClick
40
+
41
+ events: {}
42
+
43
+ template:
44
+ - rtgl-button#select-button v=ol:
45
+ - ${selectedLabel}
46
+ - rtgl-popover#popover .isOpen=isOpen .position=position:
47
+ - rtgl-view wh=300 g=xs slot=content bgc=background br=md:
48
+ - $for option, i in options:
49
+ - rtgl-view#option-${i} w=f h-bgc=ac ph=lg pv=md cur=p br=md bgc=${option.bgc}:
50
+ - rtgl-text: ${option.label}
@@ -0,0 +1,36 @@
1
+ export const handleHeaderClick = (e, deps) => {
2
+ const { store, dispatchEvent } = deps;
3
+
4
+ let path;
5
+
6
+ const header = store.selectHeader();
7
+
8
+ if (e.currentTarget.id === 'header-label') {
9
+ path = header.labelPath;
10
+ } else if (e.currentTarget.id === 'header-image') {
11
+ path = header.image.path;
12
+ } else if (e.currentTarget.id === 'header') {
13
+ path = header.path;
14
+ }
15
+
16
+ dispatchEvent(new CustomEvent('headerClick', {
17
+ detail: {
18
+ path
19
+ },
20
+ bubbles: true,
21
+ composed: true
22
+ }));
23
+ }
24
+
25
+ export const handleItemClick = (e, deps) => {
26
+ const { store, dispatchEvent } = deps;
27
+ const id = e.currentTarget.id.replace('item-', '');
28
+ const item = store.selectItem(id);
29
+ dispatchEvent(new CustomEvent('itemClick', {
30
+ detail: {
31
+ item,
32
+ },
33
+ bubbles: true,
34
+ composed: true
35
+ }));
36
+ }
@@ -0,0 +1,142 @@
1
+ export const INITIAL_STATE = Object.freeze({});
2
+
3
+ const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
4
+
5
+ const stringifyAttrs = (attrs) => {
6
+ return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
7
+ }
8
+
9
+ function flattenItems(items, selectedItemId = null) {
10
+ let result = [];
11
+
12
+ for (const item of items) {
13
+ const itemId = item.id || item.href || item.path;
14
+ const isSelected = selectedItemId === itemId;
15
+
16
+ // Add the parent item if it's not just a group label
17
+ result.push({
18
+ id: itemId,
19
+ title: item.title,
20
+ href: item.href,
21
+ type: item.type || 'item',
22
+ icon: item.icon,
23
+ hrefAttr: item.href ? `href=${item.href}` : '',
24
+ isSelected,
25
+ itemBgc: isSelected ? 'ac' : 'bg',
26
+ itemHoverBgc: isSelected ? 'ac' : 'mu',
27
+ });
28
+
29
+ // Add child items if they exist
30
+ if (item.items && Array.isArray(item.items)) {
31
+ for (const subItem of item.items) {
32
+ const subItemId = subItem.id || subItem.href || subItem.path;
33
+ const isSubSelected = selectedItemId === subItemId;
34
+
35
+ result.push({
36
+ id: subItemId,
37
+ title: subItem.title,
38
+ href: subItem.href,
39
+ type: subItem.type || 'item',
40
+ icon: subItem.icon,
41
+ hrefAttr: subItem.href ? `href=${subItem.href}` : '',
42
+ isSelected: isSubSelected,
43
+ itemBgc: isSubSelected ? 'ac' : 'bg',
44
+ itemHoverBgc: isSubSelected ? 'ac' : 'mu',
45
+ });
46
+ }
47
+ }
48
+ }
49
+
50
+ return result;
51
+ }
52
+
53
+ export const toViewData = ({ state, props, attrs }) => {
54
+ const attrsHeader = attrs.header ? JSON.parse(decodeURIComponent(attrs.header)) : props.header;
55
+ const attrsItems = attrs.items ? JSON.parse(decodeURIComponent(attrs.items)) : props.items;
56
+ const selectedItemId = attrs.selectedItemId || props.selectedItemId;
57
+
58
+ const containerAttrString = stringifyAttrs(attrs);
59
+ const mode = attrs.mode || 'full';
60
+ const header = attrsHeader || {
61
+ label: '',
62
+ path: '',
63
+ image: {
64
+ src: '',
65
+ alt: '',
66
+ width: 0,
67
+ height: 0,
68
+ },
69
+ };
70
+
71
+ const items = attrsItems ? flattenItems(attrsItems, selectedItemId) : [];
72
+
73
+ // Computed values based on mode
74
+ const sidebarWidth = mode === 'full' ? 272 : 64;
75
+ const headerAlign = mode === 'full' ? 'fs' : 'c';
76
+ const itemAlign = mode === 'full' ? 'fs' : 'c';
77
+ const headerPadding = mode === 'full' ? 'lg' : 'sm';
78
+ const itemPadding = mode === 'full' ? 'md' : 'sm';
79
+ const itemHeight = mode === 'shrunk-lg' ? 48 : 40;
80
+ const iconSize = mode === 'shrunk-lg' ? 28 : 20;
81
+ const firstLetterSize = mode === 'shrunk-lg' ? 'md' : 'sm';
82
+ const showLabels = mode === 'full';
83
+ const showGroupLabels = mode === 'full';
84
+
85
+ // For items with icons in full mode, we need left alignment within the container
86
+ // but the container itself should use flex-start alignment
87
+ const itemContentAlign = mode === 'full' ? 'fs' : 'c';
88
+
89
+ // Item container alignment - only set for shrunk modes, leave default for full mode
90
+ const itemAlignAttr = mode === 'full' ? '' : `ah=${itemAlign}`;
91
+
92
+ // Item width - for shrunk modes, make it square to constrain the highlight
93
+ const itemWidth = mode === 'full' ? 'f' : itemHeight;
94
+
95
+ // Header width - should match item width for alignment
96
+ const headerWidth = itemWidth;
97
+
98
+ const ah = mode === 'shrunk-lg' || mode === 'shrunk-md' ? 'c' : '';
99
+
100
+ return {
101
+ containerAttrString,
102
+ mode,
103
+ header,
104
+ items,
105
+ sidebarWidth,
106
+ headerAlign,
107
+ itemAlign,
108
+ headerPadding,
109
+ itemPadding,
110
+ itemHeight,
111
+ iconSize,
112
+ firstLetterSize,
113
+ showLabels,
114
+ showGroupLabels,
115
+ itemContentAlign,
116
+ itemAlignAttr,
117
+ itemWidth,
118
+ headerWidth,
119
+ selectedItemId,
120
+ ah,
121
+ };
122
+ }
123
+
124
+ export const selectHeader = ({ state, props, attrs }) => {
125
+ const attrsHeader = attrs.header ? JSON.parse(decodeURIComponent(attrs.header)) : props.header;
126
+ return attrsHeader;
127
+ }
128
+
129
+ export const selectActiveItem = ({ state, props }) => {
130
+ const items = props.items ? flattenItems(props.items) : [];
131
+ return items.find(item => item.active);
132
+ }
133
+
134
+ export const selectItem = ({ state, props, attrs }, id) => {
135
+ const attrsItems = attrs.items ? JSON.parse(decodeURIComponent(attrs.items)) : props.items;
136
+ const items = attrsItems ? flattenItems(attrsItems) : [];
137
+ return items.find(item => item.id === id);
138
+ }
139
+
140
+ export const setState = (state) => {
141
+ // State management if needed
142
+ }
@@ -0,0 +1,190 @@
1
+ elementName: rtgl-sidebar
2
+
3
+ viewDataSchema:
4
+ type: object
5
+ properties:
6
+ containerAttrString:
7
+ type: string
8
+ mode:
9
+ type: string
10
+ enum: ['full', 'shrunk', 'shrunk-lg']
11
+ default: 'full'
12
+ sidebarWidth:
13
+ type: number
14
+ headerAlign:
15
+ type: string
16
+ itemAlign:
17
+ type: string
18
+ headerPadding:
19
+ type: string
20
+ itemPadding:
21
+ type: string
22
+ itemHeight:
23
+ type: number
24
+ iconSize:
25
+ type: number
26
+ firstLetterSize:
27
+ type: string
28
+ showLabels:
29
+ type: boolean
30
+ showGroupLabels:
31
+ type: boolean
32
+ itemContentAlign:
33
+ type: string
34
+ itemAlignAttr:
35
+ type: string
36
+ itemWidth:
37
+ type: string
38
+ headerWidth:
39
+ type: string
40
+ selectedItemId:
41
+ type: string
42
+ header:
43
+ type: object
44
+ properties:
45
+ label:
46
+ type: string
47
+ href:
48
+ type: string
49
+ image:
50
+ type: object
51
+ properties:
52
+ src:
53
+ type: string
54
+ width:
55
+ type: number
56
+ height:
57
+ type: number
58
+ alt:
59
+ type: string
60
+ items:
61
+ type: array
62
+ items:
63
+ type: object
64
+ properties:
65
+ title:
66
+ type: string
67
+ slug:
68
+ type: string
69
+ type:
70
+ type: string
71
+ active:
72
+ type: boolean
73
+ icon:
74
+ type: string
75
+
76
+ propsSchema:
77
+ type: object
78
+ properties:
79
+ selectedItemId:
80
+ type: string
81
+ header:
82
+ type: object
83
+ properties:
84
+ label:
85
+ type: string
86
+ href:
87
+ type: string
88
+ image:
89
+ type: object
90
+ properties:
91
+ src:
92
+ type: string
93
+ width:
94
+ type: number
95
+ height:
96
+ type: number
97
+ alt:
98
+ type: string
99
+ items:
100
+ type: array
101
+ items:
102
+ type: object
103
+ properties:
104
+ title:
105
+ type: string
106
+ slug:
107
+ type: string
108
+ type:
109
+ type: string
110
+ items:
111
+ type: array
112
+
113
+ refs:
114
+ header-image:
115
+ eventListeners:
116
+ click:
117
+ handler: handleHeaderClick
118
+ header-label:
119
+ eventListeners:
120
+ click:
121
+ handler: handleHeaderClick
122
+ header:
123
+ eventListeners:
124
+ click:
125
+ handler: handleHeaderClick
126
+ item-*:
127
+ eventListeners:
128
+ click:
129
+ handler: handleItemClick
130
+
131
+ events:
132
+ headerClick:
133
+ type: object
134
+ properties:
135
+ path:
136
+ type: string
137
+
138
+ anchors:
139
+ - &headerContent
140
+ - $if header.image && header.image.src:
141
+ - $if header.image.href:
142
+ - a href=${header.image.href}:
143
+ - rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
144
+ $elif header.image.path:
145
+ - rtgl-view#header-image cur=p:
146
+ - rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
147
+ $else:
148
+ - rtgl-image w=${header.image.width} h=${header.image.height} src=${header.image.src} alt="${header.image.alt}":
149
+ - $if header.label && showLabels:
150
+ - $if header.labelHref:
151
+ - rtgl-text href=${header.labelHref} s=lg: "${header.label}"
152
+ $elif header.labelPath:
153
+ - rtgl-view#header-label cur=p:
154
+ - rtgl-text s=lg: "${header.label}"
155
+ $else:
156
+ - rtgl-text s=lg: "${header.label}"
157
+
158
+ template:
159
+ - rtgl-view h=f w=${sidebarWidth} bwr=xs ah=${ah} ${containerAttrString}:
160
+ - rtgl-view ph=${headerPadding} pv=lg:
161
+ - $if header.href:
162
+ - rtgl-view href=${header.href} d=h av=c ah=${headerAlign} g=lg w=${headerWidth}:
163
+ - *headerContent
164
+ $else:
165
+ - rtgl-view#header d=h av=c ah=${headerAlign} g=lg w=${headerWidth} cur=p:
166
+ - *headerContent
167
+
168
+ - rtgl-view w=f ph=${headerPadding} pb=lg g=xs ah=${ah}:
169
+ - $for item, i in items:
170
+ - $if item.type == "groupLabel":
171
+ - $if showGroupLabels:
172
+ - rtgl-view mt=md h=32 av=c ph=md:
173
+ - rtgl-text s=xs c=mu-fg: "${item.title}"
174
+ $else:
175
+ - rtgl-view mt=md h=1 bgc=mu-bg:
176
+ $else:
177
+ - rtgl-view#item-${item.id} ${item.hrefAttr} h=${itemHeight} av=c ${itemAlignAttr} ph=${itemPadding} w=${itemWidth} h-bgc=${item.itemHoverBgc} br=lg bgc=${item.itemBgc} cur=p title="${item.title}":
178
+ - $if item.icon:
179
+ - $if showLabels:
180
+ - rtgl-view d=h ah=${itemContentAlign} g=sm:
181
+ - rtgl-svg wh=16 svg=${item.icon} c=fg:
182
+ - rtgl-text s=sm: "${item.title}"
183
+ $else:
184
+ - rtgl-svg wh=${iconSize} svg=${item.icon} c=fg:
185
+ $else:
186
+ - $if showLabels:
187
+ - rtgl-text s=sm: "${item.title}"
188
+ $else:
189
+ - rtgl-view wh=${iconSize} br=f bgc=mu av=c ah=c:
190
+ - rtgl-text s=${firstLetterSize} c=fg: "${item.title.charAt(0).toUpperCase()}"
@@ -0,0 +1,55 @@
1
+ export const handleOnMount = (deps) => {
2
+ // No special initialization needed for basic table
3
+ };
4
+
5
+ export const handleRowClick = (e, deps) => {
6
+ const { dispatchEvent, props } = deps;
7
+ const rowIndex = parseInt(e.currentTarget.id.replace("row-", ""));
8
+ const rowData = props.data?.rows?.[rowIndex];
9
+
10
+ if (rowData) {
11
+ dispatchEvent(
12
+ new CustomEvent("row-click", {
13
+ detail: {
14
+ rowIndex,
15
+ rowData,
16
+ },
17
+ }),
18
+ );
19
+ }
20
+ };
21
+
22
+ export const handleHeaderClick = (e, deps) => {
23
+ const { store, render, dispatchEvent } = deps;
24
+ const columnKey = e.currentTarget.id.replace("header-", "");
25
+ const currentSort = store.selectSortInfo();
26
+
27
+ // Determine new sort direction
28
+ let newDirection = 'asc';
29
+ if (currentSort.column === columnKey) {
30
+ if (currentSort.direction === 'asc') {
31
+ newDirection = 'desc';
32
+ } else if (currentSort.direction === 'desc') {
33
+ newDirection = null; // Clear sort
34
+ }
35
+ }
36
+
37
+ if (newDirection) {
38
+ store.setSortColumn({ column: columnKey, direction: newDirection });
39
+ } else {
40
+ store.clearSort();
41
+ }
42
+
43
+ render();
44
+
45
+ // Dispatch custom event for external handling
46
+ dispatchEvent(
47
+ new CustomEvent("header-click", {
48
+ detail: {
49
+ column: columnKey,
50
+ direction: newDirection,
51
+ sortInfo: newDirection ? { column: columnKey, direction: newDirection } : null,
52
+ },
53
+ }),
54
+ );
55
+ };
@@ -0,0 +1,72 @@
1
+ export const INITIAL_STATE = Object.freeze({
2
+ sortColumn: null,
3
+ sortDirection: null,
4
+ });
5
+
6
+ const blacklistedAttrs = ['id', 'class', 'style', 'slot'];
7
+
8
+ const stringifyAttrs = (attrs) => {
9
+ return Object.entries(attrs).filter(([key]) => !blacklistedAttrs.includes(key)).map(([key, value]) => `${key}=${value}`).join(' ');
10
+ }
11
+
12
+ const getNestedValue = (obj, path) => {
13
+ const keys = path.split('.');
14
+ let result = obj;
15
+
16
+ for (const key of keys) {
17
+ if (result === null || result === undefined) {
18
+ return null;
19
+ }
20
+ result = result[key];
21
+ }
22
+
23
+ return result;
24
+ }
25
+
26
+ export const toViewData = ({ state, props, attrs }) => {
27
+ const containerAttrString = stringifyAttrs(attrs);
28
+ const data = props.data || { columns: [], rows: [] };
29
+
30
+ // Transform rows to create cells array for easier template access
31
+ const transformedRows = data.rows.map((row, rowIndex) => {
32
+ const cells = data.columns.map(column => {
33
+ const value = getNestedValue(row, column.key);
34
+ return {
35
+ key: column.key,
36
+ value: value !== null && value !== undefined ? String(value) : ''
37
+ };
38
+ });
39
+
40
+ return {
41
+ index: rowIndex,
42
+ cells: cells
43
+ };
44
+ });
45
+
46
+ return {
47
+ containerAttrString,
48
+ columns: data.columns || [],
49
+ rows: transformedRows || [],
50
+ };
51
+ }
52
+
53
+ export const selectState = ({ state }) => {
54
+ return state;
55
+ }
56
+
57
+ export const selectSortInfo = ({ state }) => {
58
+ return {
59
+ column: state.sortColumn,
60
+ direction: state.sortDirection
61
+ };
62
+ }
63
+
64
+ export const setSortColumn = (state, { column, direction }) => {
65
+ state.sortColumn = column;
66
+ state.sortDirection = direction;
67
+ }
68
+
69
+ export const clearSort = (state) => {
70
+ state.sortColumn = null;
71
+ state.sortDirection = null;
72
+ }
@@ -0,0 +1,103 @@
1
+ elementName: rtgl-table
2
+
3
+ viewDataSchema:
4
+ type: object
5
+ properties:
6
+ columns:
7
+ type: array
8
+ rows:
9
+ type: array
10
+ containerAttrString:
11
+ type: string
12
+
13
+ propsSchema:
14
+ type: object
15
+ properties:
16
+ data:
17
+ type: object
18
+ properties:
19
+ columns:
20
+ type: array
21
+ items:
22
+ type: object
23
+ properties:
24
+ key:
25
+ type: string
26
+ label:
27
+ type: string
28
+ rows:
29
+ type: array
30
+ items:
31
+ type: object
32
+ responsive:
33
+ type: boolean
34
+ default: true
35
+
36
+ refs:
37
+ row-*:
38
+ eventListeners:
39
+ click:
40
+ handler: handleRowClick
41
+ header-*:
42
+ eventListeners:
43
+ click:
44
+ handler: handleHeaderClick
45
+
46
+ events: {}
47
+
48
+ styles:
49
+ table:
50
+ width: 100%
51
+ border-collapse: collapse
52
+ border-spacing: 0
53
+
54
+ thead:
55
+ border-bottom: 2px solid var(--border)
56
+
57
+ th:
58
+ padding: 12px
59
+ text-align: left
60
+ font-weight: 500
61
+ color: var(--foreground)
62
+ background-color: var(--muted)
63
+ cursor: pointer
64
+ position: sticky
65
+ top: 0
66
+ z-index: 1
67
+
68
+ tbody tr:
69
+ border-bottom: 1px solid var(--border)
70
+
71
+ tbody tr:last-child:
72
+ border-bottom: none
73
+
74
+ td:
75
+ padding: 12px
76
+ color: var(--foreground)
77
+
78
+ .table-container:
79
+ width: 100%
80
+ height: 100%
81
+ overflow: auto
82
+
83
+ .empty-state:
84
+ text-align: center
85
+ padding: 24px
86
+ color: var(--muted-foreground)
87
+
88
+ template:
89
+ - rtgl-view.table-container ${containerAttrString}:
90
+ - $if rows.length > 0:
91
+ - table:
92
+ - thead:
93
+ - tr:
94
+ - $for column, i in columns:
95
+ - th#header-${column.key}: ${column.label}
96
+ - tbody:
97
+ - $for row, rowIndex in rows:
98
+ - tr#row-${row.index}:
99
+ - $for cell, cellIndex in row.cells:
100
+ - td: ${cell.value}
101
+ $else:
102
+ - rtgl-view.empty-state w=f p=xl:
103
+ - rtgl-text c=mu-fg: No data available