@bunnix/components 0.10.3 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/@types/index.d.ts +179 -15
- package/README.md +41 -4
- package/package.json +3 -8
- package/src/core/buttons.css +1 -0
- package/src/core/core.css +17 -3
- package/src/core/dialog.css +3 -1
- package/src/core/dialog.mjs +101 -16
- package/src/core/input.css +202 -0
- package/src/core/inputs.mjs +723 -23
- package/src/core/layout.mjs +1 -2
- package/src/core/media.css +36 -1
- package/src/core/media.mjs +13 -13
- package/src/core/menu.css +10 -29
- package/src/core/menu.mjs +159 -70
- package/src/core/outline.mjs +100 -0
- package/src/core/sidebar.mjs +189 -68
- package/src/core/sliderUtils.mjs +51 -0
- package/src/core/table.css +23 -0
- package/src/core/table.mjs +35 -20
- package/src/core/textareaUtils.mjs +31 -0
- package/src/core/utils.mjs +105 -0
- package/src/font-face/Framework7Icons-Regular.woff2 +0 -0
- package/src/index.mjs +3 -1
- package/src/icons/add-circle.svg +0 -1
- package/src/icons/add.svg +0 -1
- package/src/icons/alt.svg +0 -1
- package/src/icons/archive.svg +0 -1
- package/src/icons/arrow-down.svg +0 -1
- package/src/icons/arrow-left.svg +0 -1
- package/src/icons/arrow-right.svg +0 -1
- package/src/icons/arrow-up.svg +0 -1
- package/src/icons/at.svg +0 -1
- package/src/icons/attestation.svg +0 -1
- package/src/icons/battery-25.svg +0 -1
- package/src/icons/bell.svg +0 -3
- package/src/icons/bookmark.svg +0 -1
- package/src/icons/bot.svg +0 -1
- package/src/icons/bubble.svg +0 -1
- package/src/icons/building.svg +0 -3
- package/src/icons/button.svg +0 -1
- package/src/icons/calculate.svg +0 -1
- package/src/icons/calendar.svg +0 -1
- package/src/icons/captions-bubble.svg +0 -1
- package/src/icons/cart.svg +0 -1
- package/src/icons/chart.svg +0 -1
- package/src/icons/check.svg +0 -1
- package/src/icons/chevron-down.svg +0 -1
- package/src/icons/chevron-left.svg +0 -1
- package/src/icons/chevron-right.svg +0 -1
- package/src/icons/clip.svg +0 -1
- package/src/icons/clock.svg +0 -3
- package/src/icons/close-circle.svg +0 -3
- package/src/icons/close.svg +0 -1
- package/src/icons/cloud-download.svg +0 -1
- package/src/icons/cloud-upload.svg +0 -1
- package/src/icons/cloud.svg +0 -1
- package/src/icons/columns-layout.svg +0 -1
- package/src/icons/command.svg +0 -1
- package/src/icons/cube.svg +0 -1
- package/src/icons/delete.svg +0 -3
- package/src/icons/dollar.svg +0 -3
- package/src/icons/download.svg +0 -1
- package/src/icons/draw.svg +0 -1
- package/src/icons/duplicate.svg +0 -3
- package/src/icons/ear.svg +0 -1
- package/src/icons/edit.svg +0 -1
- package/src/icons/exclamation-mark.svg +0 -1
- package/src/icons/eye-open.svg +0 -1
- package/src/icons/eye.svg +0 -1
- package/src/icons/file-html.svg +0 -1
- package/src/icons/file.svg +0 -3
- package/src/icons/finger.svg +0 -1
- package/src/icons/flag.svg +0 -1
- package/src/icons/folder.svg +0 -1
- package/src/icons/function.svg +0 -1
- package/src/icons/gear.svg +0 -1
- package/src/icons/gift.svg +0 -1
- package/src/icons/globe.svg +0 -3
- package/src/icons/grid.svg +0 -1
- package/src/icons/hammer.svg +0 -1
- package/src/icons/hand.svg +0 -1
- package/src/icons/hare.svg +0 -1
- package/src/icons/heart.svg +0 -3
- package/src/icons/home.svg +0 -3
- package/src/icons/image.svg +0 -1
- package/src/icons/inbox.svg +0 -3
- package/src/icons/info.svg +0 -1
- package/src/icons/key.svg +0 -1
- package/src/icons/lamp.svg +0 -1
- package/src/icons/link.svg +0 -1
- package/src/icons/location.svg +0 -1
- package/src/icons/locker.svg +0 -1
- package/src/icons/login.svg +0 -1
- package/src/icons/logout.svg +0 -3
- package/src/icons/mail.svg +0 -3
- package/src/icons/map.svg +0 -3
- package/src/icons/markup.svg +0 -1
- package/src/icons/merge.svg +0 -1
- package/src/icons/more-horizontal.svg +0 -5
- package/src/icons/more-vertical.svg +0 -5
- package/src/icons/mouse.svg +0 -1
- package/src/icons/music-mic.svg +0 -1
- package/src/icons/paintbrush.svg +0 -1
- package/src/icons/palette.svg +0 -1
- package/src/icons/password.svg +0 -1
- package/src/icons/pencil.svg +0 -1
- package/src/icons/people.svg +0 -3
- package/src/icons/percent.svg +0 -1
- package/src/icons/person-add.svg +0 -1
- package/src/icons/person-remove.svg +0 -1
- package/src/icons/person.svg +0 -4
- package/src/icons/phone.svg +0 -1
- package/src/icons/pin.svg +0 -1
- package/src/icons/question-circle.svg +0 -3
- package/src/icons/remove-circle.svg +0 -1
- package/src/icons/return-arrow.svg +0 -1
- package/src/icons/save.svg +0 -1
- package/src/icons/search.svg +0 -1
- package/src/icons/sections.svg +0 -1
- package/src/icons/send.svg +0 -1
- package/src/icons/share.svg +0 -1
- package/src/icons/shine.svg +0 -1
- package/src/icons/sliders.svg +0 -1
- package/src/icons/star.svg +0 -3
- package/src/icons/staroflife.svg +0 -1
- package/src/icons/storage.svg +0 -1
- package/src/icons/success-circle.svg +0 -3
- package/src/icons/swap.svg +0 -1
- package/src/icons/switch.svg +0 -1
- package/src/icons/sync.svg +0 -3
- package/src/icons/table.svg +0 -3
- package/src/icons/tag.svg +0 -3
- package/src/icons/terminal.svg +0 -1
- package/src/icons/text.svg +0 -1
- package/src/icons/thumb-down.svg +0 -1
- package/src/icons/thumb-up.svg +0 -1
- package/src/icons/timer.svg +0 -3
- package/src/icons/toggle.svg +0 -1
- package/src/icons/trash.svg +0 -1
- package/src/icons/tv-music.svg +0 -1
- package/src/icons/update-page.svg +0 -1
- package/src/icons/upload.svg +0 -1
- package/src/icons/video.svg +0 -1
- package/src/icons/wallet.svg +0 -1
- package/src/icons/wand-stars.svg +0 -1
- package/src/icons/waveform.svg +0 -1
- package/src/icons/window.svg +0 -1
- package/src/utils/iconRegistry.generated.mjs +0 -187
- package/src/utils/iconRegistry.mjs +0 -34
package/src/core/sidebar.mjs
CHANGED
|
@@ -1,88 +1,209 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sidebar
|
|
3
|
-
*
|
|
4
|
-
* Sidebar navigation component with items and selection state.
|
|
5
|
-
*
|
|
6
|
-
* Components:
|
|
7
|
-
* - Sidebar: Sidebar navigation with headers and clickable items
|
|
2
|
+
* Sidebar navigation with selectable items and optional nested groups.
|
|
8
3
|
*
|
|
9
4
|
* Features:
|
|
10
|
-
* - State binding for items and
|
|
11
|
-
* - Automatic state resolution (useState object or raw value)
|
|
5
|
+
* - State binding for items and selection key
|
|
12
6
|
* - Headers support for grouping items
|
|
13
|
-
* -
|
|
7
|
+
* - Recursive nested items with internal expand/collapse state
|
|
8
|
+
* - Parent items stay selectable while toggling their child groups
|
|
14
9
|
*/
|
|
15
|
-
import
|
|
16
|
-
import { withNormalizedArgs, withExtractedStyles,
|
|
17
|
-
import { Column, Row } from "./layout.mjs";
|
|
10
|
+
import { Compute, Show, useEffect, useState } from "@bunnix/core";
|
|
11
|
+
import { withNormalizedArgs, withExtractedStyles, resolveCollectionState } from "./utils.mjs";
|
|
12
|
+
import { Column, Row, Spacer } from "./layout.mjs";
|
|
18
13
|
import { Button } from "./buttons.mjs";
|
|
19
14
|
import { Icon } from "./media.mjs";
|
|
20
|
-
import { Heading } from "./typography.mjs";
|
|
15
|
+
import { Heading, Text } from "./typography.mjs";
|
|
21
16
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
: useState(props.items ?? []);
|
|
17
|
+
const resolveSelectionValue = (selection) => {
|
|
18
|
+
if (selection?.get) return selection.get();
|
|
19
|
+
return selection ?? "";
|
|
20
|
+
};
|
|
27
21
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
? props.selected
|
|
31
|
-
: useState(props.selected ?? "");
|
|
22
|
+
const buildExpansionState = (items = [], currentState = {}, selectedKey = "") => {
|
|
23
|
+
const nextState = { ...currentState };
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
25
|
+
for (const item of items) {
|
|
26
|
+
if (Array.isArray(item.children) && item.children.length > 0) {
|
|
27
|
+
if (!(item.key in nextState)) {
|
|
28
|
+
nextState[item.key] = item.key === selectedKey || (item.expanded ?? false);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
Object.assign(nextState, buildExpansionState(item.children, nextState, selectedKey));
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return nextState;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const hasSidebarItem = (items = [], key) => {
|
|
39
|
+
for (const item of items) {
|
|
40
|
+
if (item.key === key) return true;
|
|
41
|
+
if (Array.isArray(item.children) && hasSidebarItem(item.children, key)) return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const renderSidebarItem = ({
|
|
48
|
+
item,
|
|
49
|
+
index,
|
|
50
|
+
level,
|
|
51
|
+
selected,
|
|
52
|
+
expandedItems,
|
|
53
|
+
setSelection,
|
|
54
|
+
toggleExpanded,
|
|
55
|
+
}) => {
|
|
56
|
+
if (item.isHeader) {
|
|
57
|
+
return Heading(
|
|
58
|
+
{
|
|
59
|
+
h4: true,
|
|
60
|
+
color: "tertiary",
|
|
61
|
+
textSize: "1rem",
|
|
62
|
+
marginTop: index > 0 ? "regular" : 0,
|
|
63
|
+
...(level > 0 ? { paddingLeft: level * 20 } : {}),
|
|
64
|
+
},
|
|
65
|
+
item.text,
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const hasChildren = Array.isArray(item.children) && item.children.length > 0;
|
|
70
|
+
const isExpanded = !!expandedItems[item.key];
|
|
71
|
+
const isSelected = selected === item.key;
|
|
72
|
+
|
|
73
|
+
const button = Button(
|
|
74
|
+
{
|
|
75
|
+
variant: isSelected ? "primary" : "tertiary",
|
|
76
|
+
click: () => {
|
|
77
|
+
setSelection(item.key);
|
|
78
|
+
if (hasChildren) toggleExpanded(item.key);
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
Row(
|
|
82
|
+
{
|
|
83
|
+
fillWidth: true,
|
|
84
|
+
alignItems: "center",
|
|
85
|
+
gap: "small",
|
|
86
|
+
...(level > 0 ? { paddingLeft: level * 20 } : {}),
|
|
87
|
+
},
|
|
88
|
+
...(level === 0 && item.icon
|
|
89
|
+
? [
|
|
90
|
+
Icon({
|
|
91
|
+
size: 18,
|
|
92
|
+
name: item.icon,
|
|
93
|
+
...(isSelected ? {} : { color: "secondary" }),
|
|
94
|
+
}),
|
|
95
|
+
]
|
|
96
|
+
: []),
|
|
97
|
+
Text({ weight: "heavy" }, item.text),
|
|
98
|
+
Spacer(),
|
|
99
|
+
...(hasChildren
|
|
100
|
+
? [
|
|
101
|
+
Icon({
|
|
102
|
+
name: isExpanded ? "chevron_down" : "chevron_right",
|
|
103
|
+
size: 16,
|
|
104
|
+
...(isSelected ? {} : { color: "secondary" }),
|
|
105
|
+
}),
|
|
106
|
+
]
|
|
107
|
+
: []),
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (!hasChildren) return button;
|
|
35
112
|
|
|
36
113
|
return Column(
|
|
37
|
-
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
name: item.icon,
|
|
55
|
-
...(selected !== item.key && { color: "secondary" })
|
|
56
|
-
}),
|
|
57
|
-
Row(item.text),
|
|
58
|
-
),
|
|
59
|
-
)
|
|
114
|
+
{ gap: 4 },
|
|
115
|
+
button,
|
|
116
|
+
...(isExpanded
|
|
117
|
+
? [
|
|
118
|
+
Column(
|
|
119
|
+
{ gap: 4 },
|
|
120
|
+
...item.children.map((child, childIndex) =>
|
|
121
|
+
renderSidebarItem({
|
|
122
|
+
item: child,
|
|
123
|
+
index: childIndex,
|
|
124
|
+
level: level + 1,
|
|
125
|
+
selected,
|
|
126
|
+
expandedItems,
|
|
127
|
+
setSelection,
|
|
128
|
+
toggleExpanded,
|
|
129
|
+
}),
|
|
130
|
+
),
|
|
60
131
|
),
|
|
132
|
+
]
|
|
133
|
+
: []),
|
|
134
|
+
);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
const SidebarCore = (props, ...children) => {
|
|
138
|
+
const initialSelection = resolveSelectionValue(props.selection);
|
|
139
|
+
let itemsValue = resolveCollectionState(props.items, []);
|
|
140
|
+
let selectionValue = props.selection?.get && props.selection?.set
|
|
141
|
+
? props.selection
|
|
142
|
+
: useState(props.selection ?? null);
|
|
143
|
+
let expandedItemsValue = useState(
|
|
144
|
+
buildExpansionState(itemsValue.get?.() ?? props.items ?? [], {}, initialSelection),
|
|
145
|
+
);
|
|
146
|
+
const sidebarState = Compute(
|
|
147
|
+
[itemsValue, expandedItemsValue, selectionValue],
|
|
148
|
+
(resolvedItems, expandedItems, selected) => ({
|
|
149
|
+
resolvedItems: resolvedItems ?? [],
|
|
150
|
+
expandedItems: expandedItems ?? {},
|
|
151
|
+
selected,
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
useEffect((nextItems) => {
|
|
156
|
+
const currentState = expandedItemsValue.get() ?? {};
|
|
157
|
+
const resolvedSelection = resolveSelectionValue(selectionValue);
|
|
158
|
+
|
|
159
|
+
if (resolvedSelection && !hasSidebarItem(nextItems ?? [], resolvedSelection)) {
|
|
160
|
+
selectionValue.set && selectionValue.set(null);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const nextState = buildExpansionState(
|
|
164
|
+
nextItems ?? [],
|
|
165
|
+
currentState,
|
|
166
|
+
resolveSelectionValue(selectionValue),
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (JSON.stringify(currentState) !== JSON.stringify(nextState)) {
|
|
170
|
+
expandedItemsValue.set(nextState);
|
|
171
|
+
}
|
|
172
|
+
}, itemsValue);
|
|
173
|
+
|
|
174
|
+
delete props.items;
|
|
175
|
+
delete props.selection;
|
|
176
|
+
|
|
177
|
+
const setSelection = (key) => {
|
|
178
|
+
selectionValue.set && selectionValue.set(key);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const toggleExpanded = (key) => {
|
|
182
|
+
const currentState = expandedItemsValue.get() ?? {};
|
|
183
|
+
expandedItemsValue.set({
|
|
184
|
+
...currentState,
|
|
185
|
+
[key]: !currentState[key],
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
return Show(sidebarState, ({ resolvedItems, expandedItems, selected }) =>
|
|
190
|
+
Column(
|
|
191
|
+
{ ...props, gap: 4 },
|
|
192
|
+
...resolvedItems.map((item, index) =>
|
|
193
|
+
renderSidebarItem({
|
|
194
|
+
item,
|
|
195
|
+
index,
|
|
196
|
+
level: 0,
|
|
197
|
+
selected,
|
|
198
|
+
expandedItems,
|
|
199
|
+
setSelection,
|
|
200
|
+
toggleExpanded,
|
|
201
|
+
}),
|
|
202
|
+
),
|
|
61
203
|
),
|
|
62
204
|
);
|
|
63
205
|
};
|
|
64
206
|
|
|
65
|
-
/**
|
|
66
|
-
* Sidebar navigation component with item selection state.
|
|
67
|
-
*
|
|
68
|
-
* @param {Object} props - Component props
|
|
69
|
-
* @param {Array<{key: string, text: string, icon?: string, isHeader?: boolean}>|*} props.items - Navigation items array or state object containing items
|
|
70
|
-
* @param {string|*} props.selected - Currently selected item key or state object for selection
|
|
71
|
-
* @param {string} [props.padding="regular"] - Padding size: "small" | "regular" | "large"
|
|
72
|
-
* @param {string} [props.class] - Additional CSS classes
|
|
73
|
-
* @param {...*} children - Child elements
|
|
74
|
-
* @returns {*} Sidebar component
|
|
75
|
-
*
|
|
76
|
-
* @example
|
|
77
|
-
* const selected = useState("home");
|
|
78
|
-
* Sidebar({
|
|
79
|
-
* items: [
|
|
80
|
-
* { key: "home", text: "Home", icon: "home" },
|
|
81
|
-
* { key: "settings", text: "Settings", icon: "settings", isHeader: true }
|
|
82
|
-
* ],
|
|
83
|
-
* selected: selected
|
|
84
|
-
* })
|
|
85
|
-
*/
|
|
86
207
|
export const Sidebar = withNormalizedArgs((props, ...children) =>
|
|
87
208
|
withExtractedStyles((finalProps, ...children) =>
|
|
88
209
|
SidebarCore(finalProps, ...children),
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export function toSliderNumber(value, fallback = 0) {
|
|
2
|
+
const nextValue = Number(value);
|
|
3
|
+
return Number.isFinite(nextValue) ? nextValue : fallback;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function isValidSliderSteps(steps) {
|
|
7
|
+
if (!Array.isArray(steps) || steps.length < 2) return false;
|
|
8
|
+
|
|
9
|
+
let previousValue = -Infinity;
|
|
10
|
+
|
|
11
|
+
for (const step of steps) {
|
|
12
|
+
const nextValue = toSliderNumber(step?.value, NaN);
|
|
13
|
+
if (!Number.isFinite(nextValue) || nextValue <= previousValue) return false;
|
|
14
|
+
previousValue = nextValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function findNearestSliderStepIndex(steps, value) {
|
|
21
|
+
if (!isValidSliderSteps(steps)) return 0;
|
|
22
|
+
|
|
23
|
+
const targetValue = toSliderNumber(value, Number(steps[0].value));
|
|
24
|
+
let nearestIndex = 0;
|
|
25
|
+
let nearestDistance = Math.abs(Number(steps[0].value) - targetValue);
|
|
26
|
+
|
|
27
|
+
for (let index = 1; index < steps.length; index += 1) {
|
|
28
|
+
const distance = Math.abs(Number(steps[index].value) - targetValue);
|
|
29
|
+
if (distance < nearestDistance) {
|
|
30
|
+
nearestIndex = index;
|
|
31
|
+
nearestDistance = distance;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return nearestIndex;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getSliderStepValue(steps, index) {
|
|
39
|
+
if (!isValidSliderSteps(steps)) return 0;
|
|
40
|
+
|
|
41
|
+
const safeIndex = Math.min(
|
|
42
|
+
steps.length - 1,
|
|
43
|
+
Math.max(0, Math.round(toSliderNumber(index, 0))),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return Number(steps[safeIndex].value);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function hasSliderStepLabels(steps) {
|
|
50
|
+
return Array.isArray(steps) && steps.some((step) => !!step?.label);
|
|
51
|
+
}
|
package/src/core/table.css
CHANGED
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
.table thead td,
|
|
11
11
|
.table th {
|
|
12
12
|
font-weight: var(--font-weight-heavier);
|
|
13
|
+
height: 40px;
|
|
13
14
|
padding: calc(var(--padding-md) * 0.75) var(--padding-md);
|
|
14
15
|
border-bottom: 1px solid var(--color-border-primary);
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
.table td {
|
|
19
|
+
height: 40px;
|
|
18
20
|
padding: calc(var(--padding-md) * 0.5) var(--padding-md);
|
|
19
21
|
border-bottom: 1px solid var(--color-border-primary);
|
|
20
22
|
}
|
|
@@ -22,3 +24,24 @@
|
|
|
22
24
|
.table tr:last-child td {
|
|
23
25
|
border-bottom: none;
|
|
24
26
|
}
|
|
27
|
+
|
|
28
|
+
.table.table-alternate-rows thead td,
|
|
29
|
+
.table.table-alternate-rows tbody td,
|
|
30
|
+
.table.table-alternate-rows th {
|
|
31
|
+
border-bottom: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.table.table-alternate-rows thead td,
|
|
35
|
+
.table.table-alternate-rows thead th {
|
|
36
|
+
background-color: var(--color-bg-primary-dimmed);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.table.table-alternate-rows thead + tbody tr:nth-child(even) td,
|
|
40
|
+
.table.table-alternate-rows thead + tbody tr:nth-child(even) th {
|
|
41
|
+
background-color: var(--color-bg-primary-dimmed);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.table.table-alternate-rows.table-hide-headers tbody tr:nth-child(odd) td,
|
|
45
|
+
.table.table-alternate-rows.table-hide-headers tbody tr:nth-child(odd) th {
|
|
46
|
+
background-color: var(--color-bg-primary-dimmed);
|
|
47
|
+
}
|
package/src/core/table.mjs
CHANGED
|
@@ -12,39 +12,54 @@
|
|
|
12
12
|
* - Column-based data mapping with headers array (content, key, size)
|
|
13
13
|
* - Row rendering from data objects mapped to header keys
|
|
14
14
|
*/
|
|
15
|
-
import Bunnix from "@bunnix/core";
|
|
16
|
-
import { withNormalizedArgs, withExtractedStyles } from "./utils.mjs";
|
|
15
|
+
import Bunnix, { ForEach } from "@bunnix/core";
|
|
16
|
+
import { withNormalizedArgs, withExtractedStyles, resolveCollectionState } from "./utils.mjs";
|
|
17
17
|
|
|
18
18
|
const { table, colgroup, col, thead, tbody, tr, td } = Bunnix;
|
|
19
19
|
|
|
20
20
|
const TableCore = withNormalizedArgs((props, ...children) => {
|
|
21
21
|
return withExtractedStyles((finalProps, ...children) => {
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
const headersValue = resolveCollectionState(finalProps.headers, []);
|
|
23
|
+
const rowsValue = resolveCollectionState(finalProps.rows, []);
|
|
24
|
+
let type = finalProps.type ?? "regular";
|
|
25
|
+
let border = finalProps.border;
|
|
26
|
+
let hideHeaders = finalProps.hideHeaders ?? false;
|
|
27
|
+
let renderCell = finalProps.renderCell;
|
|
24
28
|
|
|
25
29
|
delete finalProps.headers;
|
|
26
30
|
delete finalProps.rows;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
tr(
|
|
32
|
-
headers.map((h) => {
|
|
33
|
-
if (!h.key) return td("");
|
|
34
|
-
if (!(h.key in r)) return td("");
|
|
35
|
-
return td({ "data-label": h.content }, r[h.key]);
|
|
36
|
-
}),
|
|
37
|
-
),
|
|
38
|
-
);
|
|
31
|
+
delete finalProps.type;
|
|
32
|
+
delete finalProps.border;
|
|
33
|
+
delete finalProps.hideHeaders;
|
|
34
|
+
delete finalProps.renderCell;
|
|
39
35
|
|
|
40
36
|
return table(
|
|
41
37
|
{
|
|
42
38
|
...finalProps,
|
|
43
|
-
class: `table ${finalProps.class || ""}
|
|
39
|
+
class: `table ${type !== "regular" ? `table-${type} ` : ""}${hideHeaders ? "table-hide-headers " : ""}${border ? `border-${border} ` : ""}${finalProps.class || ""}`.trim(),
|
|
44
40
|
},
|
|
45
|
-
colgroup(
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
colgroup(ForEach(headersValue, "key", (h) => col({ width: h.size ?? 0 }))),
|
|
42
|
+
...(!hideHeaders
|
|
43
|
+
? [thead(ForEach(headersValue, "key", (h) => td(h.content ?? "")))]
|
|
44
|
+
: []),
|
|
45
|
+
tbody(
|
|
46
|
+
ForEach(rowsValue, {}, (r, rowIndex) =>
|
|
47
|
+
tr(
|
|
48
|
+
ForEach(headersValue, "key", (h) => {
|
|
49
|
+
if (!h.key) return td("");
|
|
50
|
+
let renderedCell = renderCell?.(r, rowIndex, h.key);
|
|
51
|
+
if (renderedCell !== undefined) {
|
|
52
|
+
return td({ "data-label": h.content }, renderedCell);
|
|
53
|
+
}
|
|
54
|
+
if (!(h.key in r)) return td("");
|
|
55
|
+
return td(
|
|
56
|
+
{ "data-label": h.content },
|
|
57
|
+
r[h.key],
|
|
58
|
+
);
|
|
59
|
+
}),
|
|
60
|
+
),
|
|
61
|
+
),
|
|
62
|
+
),
|
|
48
63
|
);
|
|
49
64
|
})(props, ...children);
|
|
50
65
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function resolveTextAreaLines(value, fallback = 3) {
|
|
2
|
+
const lines = Number(value);
|
|
3
|
+
|
|
4
|
+
if (!Number.isFinite(lines)) return fallback;
|
|
5
|
+
|
|
6
|
+
return Math.max(1, Math.floor(lines));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getTextAreaHeightMetrics({
|
|
10
|
+
lineHeight,
|
|
11
|
+
scrollHeight,
|
|
12
|
+
minLines,
|
|
13
|
+
maxLines,
|
|
14
|
+
verticalInset = 0,
|
|
15
|
+
}) {
|
|
16
|
+
const normalizedMinLines = resolveTextAreaLines(minLines, 3);
|
|
17
|
+
const normalizedMaxLines = Math.max(
|
|
18
|
+
normalizedMinLines,
|
|
19
|
+
resolveTextAreaLines(maxLines, 3),
|
|
20
|
+
);
|
|
21
|
+
const minHeight = (lineHeight * normalizedMinLines) + verticalInset;
|
|
22
|
+
const maxHeight = (lineHeight * normalizedMaxLines) + verticalInset;
|
|
23
|
+
const nextHeight = Math.min(Math.max(scrollHeight, minHeight), maxHeight);
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
minHeight,
|
|
27
|
+
maxHeight,
|
|
28
|
+
nextHeight,
|
|
29
|
+
shouldScroll: scrollHeight > maxHeight,
|
|
30
|
+
};
|
|
31
|
+
}
|
package/src/core/utils.mjs
CHANGED
|
@@ -1,8 +1,32 @@
|
|
|
1
|
+
import { useState } from "@bunnix/core";
|
|
2
|
+
|
|
1
3
|
export const isStateLike = (value) =>
|
|
2
4
|
value &&
|
|
3
5
|
typeof value.get === "function" &&
|
|
4
6
|
typeof value.subscribe === "function";
|
|
5
7
|
|
|
8
|
+
export const resolveCollectionState = (value, fallback = []) =>
|
|
9
|
+
isStateLike(value) ? value : useState(value ?? fallback);
|
|
10
|
+
|
|
11
|
+
const resolvePadding = (value) => {
|
|
12
|
+
if (typeof value === "number") return `${value}px`;
|
|
13
|
+
if (value === "none") return "0";
|
|
14
|
+
if (value === "sm" || value === "small") return "var(--padding-sm)";
|
|
15
|
+
if (value === "md" || value === "regular") return "var(--padding-md)";
|
|
16
|
+
if (value === "lg" || value === "large") return "var(--padding-lg)";
|
|
17
|
+
return value; // pass-through for custom values (e.g. "8px", "1rem")
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const resolveBorderRadius = (value) => {
|
|
21
|
+
if (typeof value === "number") return `${value}px`;
|
|
22
|
+
if (value === "none") return "0";
|
|
23
|
+
if (value === "md" || value === "regular") return "var(--radius-md)";
|
|
24
|
+
if (value === "lg" || value === "large") return "var(--radius-lg)";
|
|
25
|
+
if (value === "pill") return "9999px";
|
|
26
|
+
if (value === "circle") return "9999%";
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
|
|
6
30
|
export function withNormalizedArgs(fn) {
|
|
7
31
|
return (props = {}, ...children) => {
|
|
8
32
|
const isProps =
|
|
@@ -51,6 +75,14 @@ export function withExtractedStyles(fn) {
|
|
|
51
75
|
delete finalProps.textSize;
|
|
52
76
|
}
|
|
53
77
|
|
|
78
|
+
if ("fontSize" in props) {
|
|
79
|
+
style.fontSize =
|
|
80
|
+
typeof props.fontSize === "number"
|
|
81
|
+
? `${props.fontSize}px`
|
|
82
|
+
: props.fontSize;
|
|
83
|
+
delete finalProps.fontSize;
|
|
84
|
+
}
|
|
85
|
+
|
|
54
86
|
if ("weight" in props) {
|
|
55
87
|
if (typeof props.weight === "number") {
|
|
56
88
|
style.fontWeight = props.weight;
|
|
@@ -70,8 +102,26 @@ export function withExtractedStyles(fn) {
|
|
|
70
102
|
delete finalProps.overflow;
|
|
71
103
|
}
|
|
72
104
|
|
|
105
|
+
if ("overflowX" in props) {
|
|
106
|
+
style.overflowX = props.overflowX;
|
|
107
|
+
delete finalProps.overflowX;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if ("overflowY" in props) {
|
|
111
|
+
style.overflowY = props.overflowY;
|
|
112
|
+
delete finalProps.overflowY;
|
|
113
|
+
}
|
|
114
|
+
|
|
73
115
|
if ("bgColor" in props) {
|
|
74
116
|
style.backgroundColor = props.bgColor;
|
|
117
|
+
if (props.bgColor === "primary") style.backgroundColor = "var(--color-bg-primary)";
|
|
118
|
+
if (props.bgColor === "primary-dimmed") style.backgroundColor = "var(--color-bg-primary-dimmed)";
|
|
119
|
+
if (props.bgColor === "secondary") style.backgroundColor = "var(--color-bg-secondary)";
|
|
120
|
+
if (props.bgColor === "success") style.backgroundColor = "var(--color-success)";
|
|
121
|
+
if (props.bgColor === "success-dimmed") style.backgroundColor = "var(--color-success-dimmed)";
|
|
122
|
+
if (props.bgColor === "warning") style.backgroundColor = "var(--color-warning)";
|
|
123
|
+
if (props.bgColor === "warning-dimmed") style.backgroundColor = "var(--color-warning-dimmed)";
|
|
124
|
+
if (props.bgColor === "danger") style.backgroundColor = "var(--color-danger)";
|
|
75
125
|
delete finalProps.bgColor;
|
|
76
126
|
}
|
|
77
127
|
|
|
@@ -141,6 +191,12 @@ export function withExtractedStyles(fn) {
|
|
|
141
191
|
delete finalProps.size;
|
|
142
192
|
}
|
|
143
193
|
|
|
194
|
+
if ("margin" in props) {
|
|
195
|
+
style.margin =
|
|
196
|
+
typeof props.margin === "number" ? `${props.margin}px` : props.margin;
|
|
197
|
+
delete finalProps.margin;
|
|
198
|
+
}
|
|
199
|
+
|
|
144
200
|
if ("marginX" in props) {
|
|
145
201
|
const marginXValue =
|
|
146
202
|
typeof props.marginX === "number"
|
|
@@ -193,6 +249,50 @@ export function withExtractedStyles(fn) {
|
|
|
193
249
|
delete finalProps.marginBottom;
|
|
194
250
|
}
|
|
195
251
|
|
|
252
|
+
if ("padding" in props) {
|
|
253
|
+
style.padding = resolvePadding(props.padding);
|
|
254
|
+
delete finalProps.padding;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if ("paddingX" in props) {
|
|
258
|
+
const v = resolvePadding(props.paddingX);
|
|
259
|
+
style.paddingLeft = v;
|
|
260
|
+
style.paddingRight = v;
|
|
261
|
+
delete finalProps.paddingX;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if ("paddingY" in props) {
|
|
265
|
+
const v = resolvePadding(props.paddingY);
|
|
266
|
+
style.paddingTop = v;
|
|
267
|
+
style.paddingBottom = v;
|
|
268
|
+
delete finalProps.paddingY;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if ("paddingTop" in props) {
|
|
272
|
+
style.paddingTop = resolvePadding(props.paddingTop);
|
|
273
|
+
delete finalProps.paddingTop;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if ("paddingBottom" in props) {
|
|
277
|
+
style.paddingBottom = resolvePadding(props.paddingBottom);
|
|
278
|
+
delete finalProps.paddingBottom;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if ("paddingLeft" in props) {
|
|
282
|
+
style.paddingLeft = resolvePadding(props.paddingLeft);
|
|
283
|
+
delete finalProps.paddingLeft;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if ("paddingRight" in props) {
|
|
287
|
+
style.paddingRight = resolvePadding(props.paddingRight);
|
|
288
|
+
delete finalProps.paddingRight;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if ("borderRadius" in props) {
|
|
292
|
+
style.borderRadius = resolveBorderRadius(props.borderRadius);
|
|
293
|
+
delete finalProps.borderRadius;
|
|
294
|
+
}
|
|
295
|
+
|
|
196
296
|
if ("top" in props) {
|
|
197
297
|
style.top =
|
|
198
298
|
typeof props.top === "number"
|
|
@@ -235,6 +335,11 @@ export function withExtractedStyles(fn) {
|
|
|
235
335
|
delete finalProps.flexShrink;
|
|
236
336
|
}
|
|
237
337
|
|
|
338
|
+
if ("zIndex" in props) {
|
|
339
|
+
style.zIndex = props.zIndex;
|
|
340
|
+
delete finalProps.zIndex;
|
|
341
|
+
}
|
|
342
|
+
|
|
238
343
|
finalProps.style = style;
|
|
239
344
|
return fn(finalProps, ...children);
|
|
240
345
|
};
|
|
Binary file
|
package/src/index.mjs
CHANGED
|
@@ -19,7 +19,7 @@ export { Media, Icon, Spinner, Avatar } from "./core/media.mjs";
|
|
|
19
19
|
export { Button, LinkButton } from "./core/buttons.mjs";
|
|
20
20
|
|
|
21
21
|
// Inputs
|
|
22
|
-
export { TextInput, Select, CheckBox } from "./core/inputs.mjs";
|
|
22
|
+
export { TextInput, TextArea, Select, CheckBox, Switch, SegmentedPicker, Slider } from "./core/inputs.mjs";
|
|
23
23
|
|
|
24
24
|
// Data Display
|
|
25
25
|
export { Table } from "./core/table.mjs";
|
|
@@ -27,7 +27,9 @@ export { Code } from "./core/code.mjs";
|
|
|
27
27
|
|
|
28
28
|
// Navigation
|
|
29
29
|
export { Sidebar } from "./core/sidebar.mjs";
|
|
30
|
+
export { Picker } from "./core/inputs.mjs";
|
|
30
31
|
export { Menu } from "./core/menu.mjs";
|
|
32
|
+
export { Outline } from "./core/outline.mjs";
|
|
31
33
|
|
|
32
34
|
// Feedback
|
|
33
35
|
export { useDialog } from "./core/dialog.mjs";
|
package/src/icons/add-circle.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M12 8V16M16 12H8M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21Z"></path></svg>
|
package/src/icons/add.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor"><path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"></path></svg>
|
package/src/icons/alt.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M4 6H7.76393C8.52148 6 9.214 6.428 9.55279 7.10557L14.4472 16.8944C14.786 17.572 15.4785 18 16.2361 18H20M14 6H20"></path></svg>
|
package/src/icons/archive.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M10 13H14M20 8V19C20 20.1046 19.1046 21 18 21H6C4.89543 21 4 20.1046 4 19V8M21 8V5C21 3.89543 20.1046 3 19 3H5C3.89543 3 3 3.89543 3 5V8H21Z"></path></svg>
|
package/src/icons/arrow-down.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="currentColor" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg"><path d="M 27.9883 5.8633 C 26.7695 5.8633 25.9024 6.7071 25.9024 7.9258 L 25.9024 38.7930 L 26.0898 43.8086 L 19.0586 36.0976 L 13.7383 30.8476 C 13.3633 30.4727 12.8008 30.2851 12.2383 30.2851 C 11.0898 30.2851 10.2227 31.1758 10.2227 32.3242 C 10.2227 32.8867 10.4336 33.3789 10.8789 33.8476 L 26.4414 49.4336 C 26.8867 49.9024 27.4258 50.1367 27.9883 50.1367 C 28.5742 50.1367 29.1133 49.9024 29.5586 49.4336 L 45.1211 33.8476 C 45.5664 33.3789 45.7773 32.8867 45.7773 32.3242 C 45.7773 31.1758 44.9102 30.2851 43.7383 30.2851 C 43.1992 30.2851 42.6367 30.4727 42.2617 30.8476 L 36.9180 36.0976 L 29.9102 43.7852 L 30.0976 38.7930 L 30.0976 7.9258 C 30.0976 6.7071 29.2071 5.8633 27.9883 5.8633 Z"/></svg>
|
package/src/icons/arrow-left.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="currentColor" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg"><path d="M 6.2383 28.0000 C 6.2383 28.5859 6.4961 29.1250 6.9648 29.5703 L 22.5273 45.1094 C 23.0195 45.5547 23.5117 45.7656 24.0508 45.7656 C 25.2227 45.7656 26.1133 44.9219 26.1133 43.7500 C 26.1133 43.1875 25.9024 42.6250 25.5273 42.2734 L 20.3008 36.9297 L 12.3789 29.7344 L 18.0742 30.0859 L 47.6992 30.0859 C 48.9179 30.0859 49.7617 29.2188 49.7617 28.0000 C 49.7617 26.7812 48.9179 25.9141 47.6992 25.9141 L 18.0742 25.9141 L 12.4024 26.2656 L 20.3008 19.0703 L 25.5273 13.7266 C 25.9258 13.3515 26.1133 12.8125 26.1133 12.2500 C 26.1133 11.0781 25.2227 10.2344 24.0508 10.2344 C 23.5117 10.2344 22.9961 10.4219 22.4805 10.9375 L 6.9648 26.4297 C 6.4961 26.8750 6.2383 27.4141 6.2383 28.0000 Z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="currentColor" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg"><path d="M 49.7617 28.0000 C 49.7617 27.4141 49.5275 26.8750 49.0585 26.4297 L 33.5429 10.9375 C 33.0273 10.4219 32.5117 10.2344 31.9492 10.2344 C 30.8008 10.2344 29.9102 11.0781 29.9102 12.2500 C 29.9102 12.8125 30.0976 13.3515 30.4727 13.7266 L 35.7227 19.0703 L 43.6211 26.2656 L 37.9492 25.9141 L 8.3008 25.9141 C 7.1055 25.9141 6.2383 26.7812 6.2383 28.0000 C 6.2383 29.2188 7.1055 30.0859 8.3008 30.0859 L 37.9492 30.0859 L 43.6445 29.7344 L 35.7227 36.9297 L 30.4727 42.2734 C 30.0976 42.6250 29.9102 43.1875 29.9102 43.7500 C 29.9102 44.9219 30.8008 45.7656 31.9492 45.7656 C 32.5117 45.7656 33.0039 45.5547 33.4727 45.1094 L 49.0585 29.5703 C 49.5275 29.1250 49.7617 28.5859 49.7617 28.0000 Z"/></svg>
|
package/src/icons/arrow-up.svg
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg fill="currentColor" viewBox="0 0 56 56" xmlns="http://www.w3.org/2000/svg"><path d="M 27.9883 50.1367 C 29.2071 50.1367 30.0976 49.2930 30.0976 48.0742 L 30.0976 17.2071 L 29.9102 12.2149 L 36.9180 19.9024 L 42.2617 25.1524 C 42.6367 25.5273 43.1992 25.7149 43.7383 25.7149 C 44.9102 25.7149 45.7773 24.8242 45.7773 23.6758 C 45.7773 23.1133 45.5664 22.6211 45.1211 22.1524 L 29.5586 6.5664 C 29.1133 6.0976 28.5742 5.8633 27.9883 5.8633 C 27.4258 5.8633 26.8867 6.0976 26.4414 6.5664 L 10.8789 22.1524 C 10.4336 22.6211 10.2227 23.1133 10.2227 23.6758 C 10.2227 24.8242 11.0898 25.7149 12.2383 25.7149 C 12.8008 25.7149 13.3633 25.5273 13.7383 25.1524 L 19.0586 19.9024 L 26.0898 12.1914 L 25.9024 17.2071 L 25.9024 48.0742 C 25.9024 49.2930 26.7695 50.1367 27.9883 50.1367 Z"/></svg>
|