@epoch32/visage 1.2.0 → 1.3.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.
Files changed (64) hide show
  1. package/dist/api/index.js +4 -4
  2. package/dist/charts/index.js +5 -5
  3. package/dist/devtools/index.js +7 -7
  4. package/dist/dom/index.js +3 -3
  5. package/dist/form/index.js +2 -2
  6. package/dist/{index-grhdtcb5.js → index-0qat98zs.js} +3 -3
  7. package/dist/{index-h6tjqk6e.js → index-1jcd8gjf.js} +28 -17
  8. package/dist/{index-6kfe1nr4.js → index-1yhbg34h.js} +41 -9
  9. package/dist/{index-9fvarkrx.js → index-3y73zgbk.js} +1 -1
  10. package/dist/{index-y9bq7h9g.js → index-5jhzwtdz.js} +2 -2
  11. package/dist/{index-a4xns6cc.js → index-65j7a9et.js} +1 -1
  12. package/dist/{index-dyvgtp43.js → index-9bez1tmf.js} +2 -2
  13. package/dist/{index-b5wc752a.js → index-aagpn02x.js} +346 -5
  14. package/dist/{index-df5e614s.js → index-awcrz19g.js} +1 -1
  15. package/dist/{index-8x8xz80v.js → index-b7emqmej.js} +3 -3
  16. package/dist/{index-061jf06a.js → index-cgepvvd4.js} +1 -1
  17. package/dist/{index-1xtrtz1t.js → index-dmhmde2z.js} +2 -2
  18. package/dist/{index-0madxn60.js → index-enc8cmea.js} +1 -1
  19. package/dist/{index-js199er4.js → index-g1drdx7k.js} +12 -1
  20. package/dist/{index-zr6cvset.js → index-ptj8rt6b.js} +5 -5
  21. package/dist/{index-8czhpea8.js → index-x6whpv54.js} +30 -19
  22. package/dist/{index-5s255b3w.js → index-xby428n5.js} +23 -22
  23. package/dist/{index-x2rs3vab.js → index-y3nprgn7.js} +1 -1
  24. package/dist/index.d.ts +1 -1
  25. package/dist/index.js +32 -18
  26. package/dist/jsx-runtime/index.js +4 -4
  27. package/dist/query/index.js +3 -3
  28. package/dist/router/index.js +5 -5
  29. package/dist/rpc/index.js +4 -4
  30. package/dist/server/index.js +2 -2
  31. package/dist/signals/index.js +1 -1
  32. package/dist/signals/system.d.ts +2 -0
  33. package/dist/style/index.js +6 -6
  34. package/dist/template/index.js +2 -2
  35. package/dist/ui/bottom-bar/action.d.ts +3 -0
  36. package/dist/ui/bottom-bar/badge.d.ts +3 -0
  37. package/dist/ui/bottom-bar/context.d.ts +3 -0
  38. package/dist/ui/bottom-bar/index.d.ts +19 -0
  39. package/dist/ui/bottom-bar/item.d.ts +3 -0
  40. package/dist/ui/bottom-bar/root.d.ts +3 -0
  41. package/dist/ui/bottom-bar/separator.d.ts +3 -0
  42. package/dist/ui/bottom-bar/types.d.ts +72 -0
  43. package/dist/ui/index.d.ts +1 -0
  44. package/dist/ui/index.js +21 -7
  45. package/dist/virtual/index.js +5 -5
  46. package/package.json +1 -1
  47. package/src/dom/control-flow.ts +31 -21
  48. package/src/form/form.ts +29 -17
  49. package/src/index.ts +14 -0
  50. package/src/signals/effect.ts +2 -2
  51. package/src/signals/scope.ts +10 -8
  52. package/src/signals/store.ts +32 -1
  53. package/src/signals/system.ts +8 -0
  54. package/src/template/template.ts +12 -1
  55. package/src/ui/bottom-bar/action.tsx +60 -0
  56. package/src/ui/bottom-bar/badge.tsx +51 -0
  57. package/src/ui/bottom-bar/context.ts +8 -0
  58. package/src/ui/bottom-bar/index.ts +29 -0
  59. package/src/ui/bottom-bar/item.tsx +119 -0
  60. package/src/ui/bottom-bar/root.tsx +148 -0
  61. package/src/ui/bottom-bar/separator.tsx +26 -0
  62. package/src/ui/bottom-bar/types.ts +79 -0
  63. package/src/ui/index.ts +16 -0
  64. package/src/virtual/virtualizer.ts +31 -18
package/src/form/form.ts CHANGED
@@ -54,6 +54,32 @@ export function createForm<T extends Record<string, any> = Record<string, any>>(
54
54
  const submitCount = signal(0);
55
55
  const customErrors = signal<Partial<Record<keyof T, string>>>({});
56
56
 
57
+ const getValues = (): T => {
58
+ const res: Record<string, any> = {};
59
+ for (const key in fields) {
60
+ res[key] = (fields as any)[key].value();
61
+ }
62
+ return res as T;
63
+ };
64
+
65
+ const schemaValidation = computed(() => {
66
+ if (!options.schema) {
67
+ return { success: true, issuesMap: new Map<string, string>() };
68
+ }
69
+ const currentValues = getValues();
70
+ const res = validateWithSchema(options.schema, currentValues);
71
+ const issuesMap = new Map<string, string>();
72
+ if (!res.success && res.issues) {
73
+ for (const issue of res.issues) {
74
+ const pathKey = String(issue.path);
75
+ if (!issuesMap.has(pathKey)) {
76
+ issuesMap.set(pathKey, issue.message);
77
+ }
78
+ }
79
+ }
80
+ return { success: res.success, issuesMap };
81
+ });
82
+
57
83
  for (const key in initialValues) {
58
84
  const initVal = initialValues[key];
59
85
  const valSignal = signal(initVal);
@@ -74,25 +100,12 @@ export function createForm<T extends Record<string, any> = Record<string, any>>(
74
100
 
75
101
  if (!options.schema) return null;
76
102
 
77
- const currentValues = getValues();
78
- const res = validateWithSchema(options.schema, currentValues);
79
- if (!res.success && res.issues) {
80
- const match = res.issues.find((i) => i.path === key || i.path === `${key}`);
81
- return match ? match.message : null;
82
- }
83
- return null;
103
+ const validation = schemaValidation();
104
+ return validation.issuesMap.get(key) ?? validation.issuesMap.get(String(key)) ?? null;
84
105
  })
85
106
  };
86
107
  }
87
108
 
88
- const getValues = (): T => {
89
- const res: Record<string, any> = {};
90
- for (const key in fields) {
91
- res[key] = (fields as any)[key].value();
92
- }
93
- return res as T;
94
- };
95
-
96
109
  const errors = computed<Partial<Record<keyof T, string>>>(() => {
97
110
  const errMap: Partial<Record<keyof T, string>> = {};
98
111
  for (const key in fields) {
@@ -118,8 +131,7 @@ export function createForm<T extends Record<string, any> = Record<string, any>>(
118
131
 
119
132
  const validate = (): boolean => {
120
133
  if (!options.schema) return true;
121
- const res = validateWithSchema(options.schema, getValues());
122
- return res.success;
134
+ return schemaValidation().success;
123
135
  };
124
136
 
125
137
  const handleSubmit = async (e?: Event): Promise<boolean> => {
package/src/index.ts CHANGED
@@ -308,6 +308,19 @@ export {
308
308
  type AccordionProps,
309
309
  type AccordionSingleProps,
310
310
  AccordionTrigger,
311
+ BottomBar,
312
+ BottomBarAction,
313
+ type BottomBarActionProps,
314
+ BottomBarBadge,
315
+ type BottomBarBadgeProps,
316
+ type BottomBarContextValue,
317
+ BottomBarItem,
318
+ type BottomBarItemConfig,
319
+ type BottomBarItemProps,
320
+ type BottomBarProps,
321
+ BottomBarRoot,
322
+ BottomBarSeparator,
323
+ type BottomBarSeparatorProps,
311
324
  type ColumnDef,
312
325
  Command,
313
326
  CommandDialog,
@@ -394,6 +407,7 @@ export {
394
407
  trapFocus,
395
408
  useAccordionContext,
396
409
  useAccordionItemContext,
410
+ useBottomBarContext,
397
411
  useCommandContext,
398
412
  useDialogContext,
399
413
  useDropdownMenuContext,
@@ -5,8 +5,8 @@ import { Computed } from "./computed";
5
5
  import type { Scope } from "./scope";
6
6
  import {
7
7
  addPendingEffect,
8
- batchDepth,
9
8
  flushEffects,
9
+ getBatchDepth,
10
10
  getCurrentScope,
11
11
  getCurrentSubscriber,
12
12
  removePendingEffect,
@@ -57,7 +57,7 @@ export class Effect implements ISubscriber {
57
57
  this.state = targetState;
58
58
  addPendingEffect(this);
59
59
 
60
- if (batchDepth === 0) {
60
+ if (getBatchDepth() === 0) {
61
61
  flushEffects();
62
62
  }
63
63
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Visage Reactive Scope & Lifecycle Management
3
3
  */
4
- import { currentScope, currentSubscriber, getCurrentScope, setCurrentScope } from "./system";
4
+ import { getCurrentScope, getCurrentSubscriber, setCurrentScope } from "./system";
5
5
  import type { CleanupFn, ISubscriber } from "./types";
6
6
 
7
7
  /**
@@ -14,7 +14,7 @@ export class Scope {
14
14
  isDisposed = false;
15
15
  context?: Map<symbol, any>;
16
16
 
17
- constructor(parent: Scope | null = currentScope) {
17
+ constructor(parent: Scope | null = getCurrentScope()) {
18
18
  this.parent = parent;
19
19
  if (parent && !parent.isDisposed) {
20
20
  parent.cleanups.push(() => this.dispose());
@@ -72,10 +72,12 @@ export class Scope {
72
72
  * ```
73
73
  */
74
74
  export function onCleanup(fn: CleanupFn): void {
75
- if (currentSubscriber && "addCleanup" in currentSubscriber) {
76
- (currentSubscriber as any).addCleanup(fn);
77
- } else if (currentScope) {
78
- currentScope.addCleanup(fn);
75
+ const curSub = getCurrentSubscriber();
76
+ const curScope = getCurrentScope();
77
+ if (curSub && "addCleanup" in curSub) {
78
+ (curSub as any).addCleanup(fn);
79
+ } else if (curScope) {
80
+ curScope.addCleanup(fn);
79
81
  }
80
82
  }
81
83
 
@@ -101,7 +103,7 @@ export function onCleanup(fn: CleanupFn): void {
101
103
  * @returns The return value of `fn`
102
104
  */
103
105
  export function createRoot<T>(fn: (dispose: () => void) => T): T {
104
- const prevScope = currentScope;
106
+ const prevScope = getCurrentScope();
105
107
  const scope = new Scope(null);
106
108
  setCurrentScope(scope);
107
109
  try {
@@ -115,7 +117,7 @@ export function createRoot<T>(fn: (dispose: () => void) => T): T {
115
117
  * Run a function with a specified reactive scope active.
116
118
  */
117
119
  export function runInScope<T>(scope: Scope | null, fn: () => T): T {
118
- const prevScope = currentScope;
120
+ const prevScope = getCurrentScope();
119
121
  setCurrentScope(scope);
120
122
  try {
121
123
  return fn();
@@ -5,6 +5,7 @@ import { isSignal } from "./types";
5
5
  const STORE_RAW = Symbol("visage.store.raw");
6
6
  const STORE_SIGNALS = Symbol("visage.store.signals");
7
7
  const STORE_PROXY = Symbol("visage.store.proxy");
8
+ const STORE_ITERATE = Symbol("visage.store.iterate");
8
9
 
9
10
  export type Store<T> = T;
10
11
 
@@ -61,9 +62,29 @@ function createStoreProxy<T extends object>(target: T): T {
61
62
  return value;
62
63
  },
63
64
 
65
+ has(target, prop) {
66
+ if (prop === STORE_RAW || prop === STORE_PROXY) return true;
67
+ if (typeof prop === "string" || typeof prop === "symbol") {
68
+ const sig = getOrCreatePropertySignal(target, prop);
69
+ sig.get();
70
+ }
71
+ return Reflect.has(target, prop);
72
+ },
73
+
74
+ ownKeys(target) {
75
+ const iterSig = getOrCreatePropertySignal(target, STORE_ITERATE);
76
+ iterSig.get();
77
+ return Reflect.ownKeys(target);
78
+ },
79
+
80
+ getOwnPropertyDescriptor(target, prop) {
81
+ return Reflect.getOwnPropertyDescriptor(target, prop);
82
+ },
83
+
64
84
  set(target, prop, value, receiver) {
85
+ const hadProp = Reflect.has(target, prop);
65
86
  const prevValue = Reflect.get(target, prop, receiver);
66
- if (Object.is(prevValue, value)) {
87
+ if (hadProp && Object.is(prevValue, value)) {
67
88
  return true;
68
89
  }
69
90
 
@@ -72,10 +93,18 @@ function createStoreProxy<T extends object>(target: T): T {
72
93
  const sig = getOrCreatePropertySignal(target, prop);
73
94
  sig.set(value);
74
95
 
96
+ // If a new property was added, notify key iteration
97
+ if (!hadProp) {
98
+ const iterSig = getOrCreatePropertySignal(target, STORE_ITERATE);
99
+ iterSig.update((prev: number = 0) => prev + 1);
100
+ }
101
+
75
102
  // If array length or property keys changed, notify length/keys
76
103
  if (Array.isArray(target) && prop !== "length") {
77
104
  const lenSig = getOrCreatePropertySignal(target, "length");
78
105
  lenSig.set(target.length);
106
+ const iterSig = getOrCreatePropertySignal(target, STORE_ITERATE);
107
+ iterSig.update((prev: number = 0) => prev + 1);
79
108
  }
80
109
  }
81
110
  return success;
@@ -87,6 +116,8 @@ function createStoreProxy<T extends object>(target: T): T {
87
116
  if (hadProp && success) {
88
117
  const sig = getOrCreatePropertySignal(target, prop);
89
118
  sig.set(undefined);
119
+ const iterSig = getOrCreatePropertySignal(target, STORE_ITERATE);
120
+ iterSig.update((prev: number = 0) => prev + 1);
90
121
  }
91
122
  return success;
92
123
  }
@@ -59,6 +59,14 @@ export function removePendingEffect(eff: any): void {
59
59
  ctx.pendingEffects.delete(eff);
60
60
  }
61
61
 
62
+ export function getBatchDepth(): number {
63
+ return ctx.batchDepth;
64
+ }
65
+
66
+ export function getIsFlushing(): boolean {
67
+ return ctx.isFlushing;
68
+ }
69
+
62
70
  const MAX_FLUSH_COUNT = 100;
63
71
 
64
72
  export function flushEffects(): void {
@@ -66,11 +66,22 @@ export function html(
66
66
 
67
67
  for (const bind of bindings) {
68
68
  if (bind.isAttr && bind.attrName) {
69
- const targetEl = findElementWithAttributeValue(root, bind.attrName, bind.id);
69
+ const targetEl = findElementWithAttributeValue(root as HTMLElement, bind.attrName, bind.id);
70
70
  if (targetEl) {
71
71
  targetEl.removeAttribute(bind.attrName);
72
72
  setAttribute(targetEl, bind.attrName, bind.value);
73
73
  }
74
+ } else if (bind.isAttr && !bind.attrName) {
75
+ const propAttr = `data-visage-prop-${bind.id.replace(MARKER_PREFIX, "")}`;
76
+ const targetEl = findElementWithAttributeValue(root as HTMLElement, propAttr, bind.id);
77
+ if (targetEl) {
78
+ targetEl.removeAttribute(propAttr);
79
+ if (typeof bind.value === "object" && bind.value !== null) {
80
+ for (const key of Object.keys(bind.value)) {
81
+ setAttribute(targetEl, key, bind.value[key]);
82
+ }
83
+ }
84
+ }
74
85
  } else if (!bind.isAttr) {
75
86
  const commentNode = findCommentNode(root, bind.id);
76
87
  if (commentNode?.parentNode) {
@@ -0,0 +1,60 @@
1
+ import type { Child } from "../../dom/render";
2
+ import type { BottomBarActionProps } from "./types";
3
+
4
+ export function BottomBarAction(props: BottomBarActionProps): Child {
5
+ const {
6
+ as: Tag = "button",
7
+ icon,
8
+ label,
9
+ disabled,
10
+ onClick,
11
+ children,
12
+ class: className,
13
+ style,
14
+ ...rest
15
+ } = props;
16
+
17
+ const defaultActionStyle = {
18
+ display: "inline-flex",
19
+ alignItems: "center",
20
+ justifyContent: "center",
21
+ width: "44px",
22
+ height: "44px",
23
+ minWidth: "44px",
24
+ minHeight: "44px",
25
+ borderRadius: "50%",
26
+ background: "linear-gradient(135deg, #ec4899 0%, #a855f7 100%)",
27
+ color: "#ffffff",
28
+ border: "none",
29
+ boxShadow: "0 4px 14px rgba(236, 72, 153, 0.45)",
30
+ cursor: "pointer",
31
+ outline: "none",
32
+ userSelect: "none",
33
+ WebkitTapHighlightColor: "transparent",
34
+ transition: "transform 0.15s ease, box-shadow 0.15s ease",
35
+ boxSizing: "border-box"
36
+ };
37
+
38
+ const combinedStyle = style ? { ...defaultActionStyle, ...style } : defaultActionStyle;
39
+
40
+ return (
41
+ <Tag
42
+ type={Tag === "button" ? "button" : undefined}
43
+ role="button"
44
+ aria-label={label ?? "Primary action"}
45
+ disabled={disabled}
46
+ class={`visage-bottom-bar-action ${className ?? ""}`.trim()}
47
+ style={combinedStyle}
48
+ onClick={onClick}
49
+ {...rest}
50
+ >
51
+ {children !== undefined
52
+ ? typeof children === "function"
53
+ ? children()
54
+ : children
55
+ : typeof icon === "function"
56
+ ? icon()
57
+ : icon}
58
+ </Tag>
59
+ );
60
+ }
@@ -0,0 +1,51 @@
1
+ import type { Child } from "../../dom/render";
2
+ import { computed } from "../../signals";
3
+ import type { BottomBarBadgeProps } from "./types";
4
+
5
+ export function BottomBarBadge(props: BottomBarBadgeProps): Child {
6
+ const { as: Tag = "span", count, children, class: className, style, ...rest } = props;
7
+
8
+ const displayContent = computed(() => {
9
+ if (children !== undefined) {
10
+ return typeof children === "function" ? children() : children;
11
+ }
12
+ if (count !== undefined) {
13
+ return typeof count === "function" ? count() : count;
14
+ }
15
+ return "";
16
+ });
17
+
18
+ const defaultBadgeStyle = {
19
+ position: "absolute",
20
+ top: "3px",
21
+ right: "8px",
22
+ minWidth: "16px",
23
+ height: "16px",
24
+ padding: "0 4px",
25
+ borderRadius: "9999px",
26
+ backgroundColor: "#ef4444",
27
+ color: "#ffffff",
28
+ fontSize: "9px",
29
+ fontWeight: 700,
30
+ lineHeight: "16px",
31
+ textAlign: "center",
32
+ display: "inline-flex",
33
+ alignItems: "center",
34
+ justifyContent: "center",
35
+ boxShadow: "0 0 0 1.5px rgba(18, 18, 24, 0.9)",
36
+ pointerEvents: "none",
37
+ boxSizing: "border-box"
38
+ };
39
+
40
+ const combinedStyle = style ? { ...defaultBadgeStyle, ...style } : defaultBadgeStyle;
41
+
42
+ return (
43
+ <Tag
44
+ class={`visage-bottom-bar-badge ${className ?? ""}`.trim()}
45
+ style={combinedStyle}
46
+ {...rest}
47
+ >
48
+ {displayContent}
49
+ </Tag>
50
+ );
51
+ }
@@ -0,0 +1,8 @@
1
+ import { createContext, useContext } from "../../signals/context";
2
+ import type { BottomBarContextValue } from "./types";
3
+
4
+ export const BottomBarContext = createContext<BottomBarContextValue | null>(null);
5
+
6
+ export function useBottomBarContext(): BottomBarContextValue | null {
7
+ return useContext(BottomBarContext);
8
+ }
@@ -0,0 +1,29 @@
1
+ import { BottomBarAction } from "./action";
2
+ import { BottomBarBadge } from "./badge";
3
+ import { BottomBarItem } from "./item";
4
+ import { BottomBarRoot } from "./root";
5
+ import { BottomBarSeparator } from "./separator";
6
+
7
+ export { BottomBarAction } from "./action";
8
+ export { BottomBarBadge } from "./badge";
9
+ export { BottomBarContext, useBottomBarContext } from "./context";
10
+ export { BottomBarItem } from "./item";
11
+ export { BottomBarRoot } from "./root";
12
+ export { BottomBarSeparator } from "./separator";
13
+ export type {
14
+ BottomBarActionProps,
15
+ BottomBarBadgeProps,
16
+ BottomBarContextValue,
17
+ BottomBarItemConfig,
18
+ BottomBarItemProps,
19
+ BottomBarProps,
20
+ BottomBarSeparatorProps
21
+ } from "./types";
22
+
23
+ export const BottomBar = Object.assign(BottomBarRoot, {
24
+ Root: BottomBarRoot,
25
+ Item: BottomBarItem,
26
+ Action: BottomBarAction,
27
+ Badge: BottomBarBadge,
28
+ Separator: BottomBarSeparator
29
+ });
@@ -0,0 +1,119 @@
1
+ import type { Child } from "../../dom/render";
2
+ import { computed } from "../../signals";
3
+ import { BottomBarBadge } from "./badge";
4
+ import { useBottomBarContext } from "./context";
5
+ import type { BottomBarItemProps } from "./types";
6
+
7
+ export function BottomBarItem(props: BottomBarItemProps): Child {
8
+ const ctx = useBottomBarContext();
9
+ const isSelected = computed(() => (ctx ? ctx.value() === props.value : false));
10
+
11
+ const handleClick = (e: MouseEvent) => {
12
+ const isDisabled =
13
+ typeof props.disabled === "function"
14
+ ? (props.disabled as () => boolean)()
15
+ : Boolean(props.disabled);
16
+ if (isDisabled) {
17
+ e.preventDefault();
18
+ return;
19
+ }
20
+
21
+ if (ctx) {
22
+ ctx.setValue(props.value);
23
+ }
24
+ if (props.onClick) {
25
+ props.onClick(e);
26
+ }
27
+ };
28
+
29
+ const {
30
+ as: Tag = "button",
31
+ value,
32
+ icon,
33
+ label,
34
+ badge,
35
+ disabled,
36
+ onClick: _oc,
37
+ children,
38
+ class: className,
39
+ style,
40
+ ...rest
41
+ } = props;
42
+
43
+ const defaultItemStyle = {
44
+ position: "relative",
45
+ display: "flex",
46
+ flexDirection: "column",
47
+ alignItems: "center",
48
+ justifyContent: "center",
49
+ minWidth: "52px",
50
+ minHeight: "44px",
51
+ padding: "6px 8px",
52
+ borderRadius: "16px",
53
+ border: "none",
54
+ background: () => (isSelected() ? "rgba(255, 255, 255, 0.08)" : "transparent"),
55
+ color: () => (isSelected() ? "#ec4899" : "rgba(255, 255, 255, 0.65)"),
56
+ cursor: "pointer",
57
+ outline: "none",
58
+ userSelect: "none",
59
+ WebkitTapHighlightColor: "transparent",
60
+ transition: "color 0.15s ease, background-color 0.15s ease, transform 0.1s ease",
61
+ boxSizing: "border-box"
62
+ };
63
+
64
+ const combinedStyle = style ? { ...defaultItemStyle, ...style } : defaultItemStyle;
65
+
66
+ return (
67
+ <Tag
68
+ type={Tag === "button" ? "button" : undefined}
69
+ role="tab"
70
+ id={ctx ? `${ctx.baseId}-item-${value}` : undefined}
71
+ aria-selected={() => isSelected()}
72
+ data-state={() => (isSelected() ? "active" : "inactive")}
73
+ data-value={value}
74
+ disabled={disabled}
75
+ class={`visage-bottom-bar-item ${className ?? ""}`.trim()}
76
+ style={combinedStyle}
77
+ onClick={handleClick}
78
+ {...rest}
79
+ >
80
+ {badge !== undefined ? (
81
+ typeof badge === "function" || typeof badge === "string" || typeof badge === "number" ? (
82
+ <BottomBarBadge count={badge as any} />
83
+ ) : (
84
+ badge
85
+ )
86
+ ) : null}
87
+
88
+ {children !== undefined ? (typeof children === "function" ? children() : children) : null}
89
+
90
+ {icon ? (
91
+ <span
92
+ class="visage-bottom-bar-icon"
93
+ style={{
94
+ display: "inline-flex",
95
+ alignItems: "center",
96
+ justifyContent: "center",
97
+ fontSize: "18px"
98
+ }}
99
+ >
100
+ {typeof icon === "function" ? icon() : icon}
101
+ </span>
102
+ ) : null}
103
+ {label ? (
104
+ <span
105
+ class="visage-bottom-bar-label"
106
+ style={{
107
+ fontSize: "10px",
108
+ fontWeight: 500,
109
+ lineHeight: 1.2,
110
+ marginTop: "3px",
111
+ letterSpacing: "0.01em"
112
+ }}
113
+ >
114
+ {typeof label === "function" ? label() : label}
115
+ </span>
116
+ ) : null}
117
+ </Tag>
118
+ );
119
+ }