react-better-html 1.1.181 → 1.1.183

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/dist/index.mjs CHANGED
@@ -9,7 +9,7 @@ var isMobileDevice = /Mobi|Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Op
9
9
 
10
10
  // src/utils/hooks.ts
11
11
  import { useCallback as useCallback4, useEffect as useEffect5, useMemo as useMemo3, useRef as useRef2, useState as useState3 } from "react";
12
- import { createSearchParams, useInRouterContext, useNavigate, useSearchParams } from "react-router-dom";
12
+ import { createSearchParams } from "react-router-dom";
13
13
 
14
14
  // src/constants/css.ts
15
15
  var cssProps = {
@@ -1518,6 +1518,7 @@ var appConfig = {
1518
1518
  contentMaxWidth: 1100
1519
1519
  };
1520
1520
  var defaultAlertDuration = 2.3 * 1e3;
1521
+ var defaultSideMenuWidth = 300;
1521
1522
 
1522
1523
  // src/components/alerts/AlertsHolder.tsx
1523
1524
  import { memo as memo7 } from "react";
@@ -1547,7 +1548,13 @@ var alertsPlugin = (options) => ({
1547
1548
  });
1548
1549
 
1549
1550
  // src/plugins/reactRouterDom.ts
1550
- var defaultReactRouterDomPluginOptions = {};
1551
+ import { useInRouterContext, useLocation, useNavigate, useSearchParams } from "react-router-dom";
1552
+ var defaultReactRouterDomPluginOptions = {
1553
+ useNavigate,
1554
+ useLocation,
1555
+ useInRouterContext,
1556
+ useSearchParams
1557
+ };
1551
1558
  var reactRouterDomPlugin = (options) => ({
1552
1559
  name: "react-router-dom",
1553
1560
  initialize: () => {
@@ -2534,6 +2541,8 @@ function BetterHtmlProvider({ config, plugins, children }) {
2534
2541
  );
2535
2542
  const [loaders, setLoaders] = useState2(config?.loaders ?? {});
2536
2543
  const [alerts, setAlerts] = useState2([]);
2544
+ const [sideMenuIsCollapsed, setSideMenuIsCollapsed] = useBooleanState();
2545
+ const [sideMenuIsOpenMobile, setSideMenuIsOpenMobile] = useBooleanState();
2537
2546
  const [tabGroups, setTabGroups] = useState2([]);
2538
2547
  const [tabsWithDots, setTabsWithDots] = useState2([]);
2539
2548
  const readyConfig = useMemo2(
@@ -2571,6 +2580,10 @@ function BetterHtmlProvider({ config, plugins, children }) {
2571
2580
  setLoaders,
2572
2581
  alerts,
2573
2582
  setAlerts,
2583
+ sideMenuIsCollapsed,
2584
+ setSideMenuIsCollapsed,
2585
+ sideMenuIsOpenMobile,
2586
+ setSideMenuIsOpenMobile,
2574
2587
  components: {
2575
2588
  ...config?.components
2576
2589
  },
@@ -2584,7 +2597,7 @@ function BetterHtmlProvider({ config, plugins, children }) {
2584
2597
  }
2585
2598
  }
2586
2599
  }),
2587
- [config, colorTheme, loaders, alerts, tabGroups, tabsWithDots]
2600
+ [config, colorTheme, loaders, alerts, sideMenuIsCollapsed, sideMenuIsOpenMobile, tabGroups, tabsWithDots]
2588
2601
  );
2589
2602
  useEffect4(() => {
2590
2603
  if (!plugins) return;
@@ -3091,14 +3104,15 @@ function useUrlQuery() {
3091
3104
  "`useUrlQuery` hook requires the `react-router-dom` plugin to be added to the `plugins` prop in `<BetterHtmlProvider>`."
3092
3105
  );
3093
3106
  }
3094
- const isInRouterContext = useInRouterContext();
3107
+ const reactRouterDomPluginConfig = reactRouterDomPlugin2.getConfig();
3108
+ const isInRouterContext = reactRouterDomPluginConfig.useInRouterContext();
3095
3109
  if (!isInRouterContext) {
3096
3110
  throw new Error(
3097
3111
  "`useUrlQuery` hook must be used inside a React Router context. Make sure your component is wrapped in a `<BrowserRouter>`, or another Router component."
3098
3112
  );
3099
3113
  }
3100
- const navigate = useNavigate();
3101
- const [searchParams] = useSearchParams();
3114
+ const navigate = reactRouterDomPluginConfig.useNavigate();
3115
+ const [searchParams] = reactRouterDomPluginConfig.useSearchParams();
3102
3116
  const setQuery = useCallback4(
3103
3117
  (query, keepHistory = true) => {
3104
3118
  const currentSearchParams = {};
@@ -5486,6 +5500,24 @@ var alertControls = {
5486
5500
  externalBetterHtmlContextValue.setAlerts((oldValue) => oldValue.filter((alert) => alert.id !== alertId));
5487
5501
  }
5488
5502
  };
5503
+ var sideMenuControls = {
5504
+ expand: () => {
5505
+ if (!checkBetterHtmlContextValue(externalBetterHtmlContextValue, "sideMenuControls.expand")) return;
5506
+ externalBetterHtmlContextValue.setSideMenuIsCollapsed.setFalse();
5507
+ },
5508
+ collapse: () => {
5509
+ if (!checkBetterHtmlContextValue(externalBetterHtmlContextValue, "sideMenuControls.collapse")) return;
5510
+ externalBetterHtmlContextValue.setSideMenuIsCollapsed.setTrue();
5511
+ },
5512
+ open: () => {
5513
+ if (!checkBetterHtmlContextValue(externalBetterHtmlContextValue, "sideMenuControls.open")) return;
5514
+ externalBetterHtmlContextValue.setSideMenuIsOpenMobile.setTrue();
5515
+ },
5516
+ close: () => {
5517
+ if (!checkBetterHtmlContextValue(externalBetterHtmlContextValue, "sideMenuControls.close")) return;
5518
+ externalBetterHtmlContextValue.setSideMenuIsOpenMobile.setFalse();
5519
+ }
5520
+ };
5489
5521
  var colorThemeControls = {
5490
5522
  toggleTheme: (theme2) => {
5491
5523
  if (!checkBetterHtmlContextValue(externalBetterHtmlContextValue, "colorThemeControls.toggleTheme")) return;
@@ -8504,7 +8536,7 @@ var TableComponent = forwardRef15(function Table({
8504
8536
  value: value.value,
8505
8537
  onClickWithValue: onClickFilterListItem,
8506
8538
  children: /* @__PURE__ */ jsxs18(Div_default.row, { alignItems: "center", gap: theme2.styles.gap / 2, children: [
8507
- /* @__PURE__ */ jsx22(Text_default, { children: value.label ?? value.value }),
8539
+ /* @__PURE__ */ jsx22(Text_default, { children: value.label || value.value }),
8508
8540
  openedFilterColumn.withTotalNumber && /* @__PURE__ */ jsxs18(
8509
8541
  Text_default,
8510
8542
  {
@@ -9247,6 +9279,305 @@ var Foldable2 = memo25(FoldableComponent);
9247
9279
  Foldable2.box = FoldableComponent.box;
9248
9280
  var Foldable_default = Foldable2;
9249
9281
 
9282
+ // src/components/SideMenu.tsx
9283
+ import { memo as memo26, useCallback as useCallback15, useEffect as useEffect14, useMemo as useMemo11 } from "react";
9284
+ import { jsx as jsx26, jsxs as jsxs22 } from "react/jsx-runtime";
9285
+ var MenuItemComponent = memo26(function MenuItemComponent2({ item, onClick }) {
9286
+ const reactRouterDomPlugin2 = usePlugin("react-router-dom");
9287
+ if (!reactRouterDomPlugin2) {
9288
+ throw new Error(
9289
+ "`SideMenu` component requires the `react-router-dom` plugin to be added to the `plugins` prop in `<BetterHtmlProvider>`."
9290
+ );
9291
+ }
9292
+ const reactRouterDomPluginConfig = reactRouterDomPlugin2.getConfig();
9293
+ const theme2 = useTheme();
9294
+ const mediaQuery = useMediaQuery();
9295
+ const location = reactRouterDomPluginConfig.useLocation();
9296
+ const { colorTheme, components, sideMenuIsCollapsed } = useBetterHtmlContextInternal();
9297
+ const [isOpened, setIsOpened] = useBooleanState();
9298
+ const onClickElement = useCallback15(() => {
9299
+ if (item.disabled) return;
9300
+ onClick?.();
9301
+ item.onClick?.(item);
9302
+ }, [onClick, item]);
9303
+ const isCollapsed = sideMenuIsCollapsed && !mediaQuery.size1000;
9304
+ const isActive = item.href ? location.pathname === "/" ? location.pathname === item.href : location.pathname.startsWith(item.href) && item.href !== "/" : false;
9305
+ const iconSize = 16;
9306
+ const paddingBlock = theme2.styles.gap;
9307
+ const paddingLeft = theme2.styles.gap + 2;
9308
+ const iconGap = theme2.styles.gap;
9309
+ const lineHeight = 20;
9310
+ const lineWidth = 2;
9311
+ const lineEndRadius = iconSize / 2 + iconGap * 2;
9312
+ const content = /* @__PURE__ */ jsxs22(
9313
+ Div_default.row,
9314
+ {
9315
+ alignItems: "center",
9316
+ gap: iconGap,
9317
+ whiteSpace: "nowrap",
9318
+ backgroundColor: isActive ? `${theme2.colors.primary}20` : theme2.colors.backgroundBase,
9319
+ borderRadius: theme2.styles.borderRadius,
9320
+ paddingBlock,
9321
+ paddingLeft: isCollapsed ? theme2.styles.space : paddingLeft,
9322
+ paddingRight: theme2.styles.space,
9323
+ filterHover: `brightness(${colorTheme === "dark" ? isActive ? 0.8 : 1.3 : isActive ? 0.8 : 0.95})`,
9324
+ overflow: isCollapsed ? "hidden" : void 0,
9325
+ cursor: item.disabled ? "not-allowed" : "pointer",
9326
+ opacity: item.disabled ? 0.6 : void 0,
9327
+ onClick: item.children ? setIsOpened.toggle : void 0,
9328
+ children: [
9329
+ /* @__PURE__ */ jsx26(Icon_default, { name: item.iconName, color: theme2.colors.primary, size: iconSize, flexShrink: 0 }),
9330
+ /* @__PURE__ */ jsx26(
9331
+ Text_default,
9332
+ {
9333
+ flex: 1,
9334
+ lineHeight: `${lineHeight}px`,
9335
+ color: isActive ? theme2.colors.primary : theme2.colors.textPrimary,
9336
+ opacity: isCollapsed ? 0 : void 0,
9337
+ transition: theme2.styles.transition,
9338
+ children: item.text
9339
+ }
9340
+ ),
9341
+ item.children && /* @__PURE__ */ jsx26(
9342
+ Icon_default,
9343
+ {
9344
+ name: "chevronDown",
9345
+ color: theme2.colors.textSecondary,
9346
+ size: 14,
9347
+ transform: isOpened ? "rotate(180deg)" : void 0,
9348
+ transition: theme2.styles.transition
9349
+ }
9350
+ )
9351
+ ]
9352
+ }
9353
+ );
9354
+ useEffect14(() => {
9355
+ if (!item.children) return;
9356
+ const toBeOpened = item.children.some(
9357
+ (child) => child.href ? location.pathname === "/" ? location.pathname === child.href : location.pathname.startsWith(child.href) && child.href !== "/" : false
9358
+ );
9359
+ setIsOpened.setState(toBeOpened);
9360
+ }, [item]);
9361
+ const LinkComponentTag = components.button?.tagReplacement?.linkComponent ?? "a";
9362
+ return /* @__PURE__ */ jsxs22(Div_default, { width: "100%", children: [
9363
+ item.href ? /* @__PURE__ */ jsx26(LinkComponentTag, { to: item.href, href: item.href, onClick: onClickElement, children: content }) : content,
9364
+ item.children && /* @__PURE__ */ jsxs22(
9365
+ Div_default.column,
9366
+ {
9367
+ position: "relative",
9368
+ maxHeight: isOpened ? 1e3 : 0,
9369
+ gap: theme2.styles.gap / 2,
9370
+ marginTop: isOpened ? theme2.styles.gap / 2 : void 0,
9371
+ paddingLeft: paddingLeft + iconSize + iconGap,
9372
+ overflow: "hidden",
9373
+ transition: `max-height ${theme2.styles.transition}, margin-top ${theme2.styles.transition}`,
9374
+ children: [
9375
+ item.children.map((child) => /* @__PURE__ */ jsx26(MenuItemComponent2, { item: child, onClick }, child.text)),
9376
+ /* @__PURE__ */ jsxs22(
9377
+ Div_default,
9378
+ {
9379
+ position: "absolute",
9380
+ height: `calc(100% - ${paddingBlock + lineHeight / 2 + lineEndRadius / 2}px)`,
9381
+ top: 0,
9382
+ left: paddingLeft + iconSize / 2,
9383
+ zIndex: -1,
9384
+ children: [
9385
+ /* @__PURE__ */ jsx26(
9386
+ Div_default,
9387
+ {
9388
+ position: "relative",
9389
+ width: lineWidth,
9390
+ height: "100%",
9391
+ backgroundColor: theme2.colors.border,
9392
+ zIndex: 1
9393
+ }
9394
+ ),
9395
+ /* @__PURE__ */ jsx26(
9396
+ Div_default,
9397
+ {
9398
+ position: "absolute",
9399
+ width: lineEndRadius,
9400
+ height: lineEndRadius,
9401
+ top: `calc(100% - ${lineEndRadius / 2}px)`,
9402
+ left: 0,
9403
+ border: `${lineWidth}px solid ${theme2.colors.border}`,
9404
+ borderRadius: 999,
9405
+ borderTopColor: theme2.colors.backgroundBase,
9406
+ borderLeftColor: theme2.colors.backgroundBase,
9407
+ borderRightColor: theme2.colors.backgroundBase,
9408
+ transform: "rotate(45deg)"
9409
+ }
9410
+ )
9411
+ ]
9412
+ }
9413
+ )
9414
+ ]
9415
+ }
9416
+ )
9417
+ ] });
9418
+ });
9419
+ var SideMenuComponent = function SideMenu({
9420
+ items,
9421
+ logoAssetName,
9422
+ logoUrl,
9423
+ logoText,
9424
+ logoFontFamily,
9425
+ collapsable,
9426
+ widthMobileHandle
9427
+ }) {
9428
+ const theme2 = useTheme();
9429
+ const mediaQuery = useMediaQuery();
9430
+ const { components, sideMenuIsCollapsed, setSideMenuIsCollapsed, sideMenuIsOpenMobile, setSideMenuIsOpenMobile } = useBetterHtmlContextInternal();
9431
+ const onClickXButton = useCallback15(() => {
9432
+ setSideMenuIsOpenMobile.setFalse();
9433
+ }, []);
9434
+ const readyItems = useMemo11(() => items.filter((item) => !item.hidden), [items]);
9435
+ const isCollapsed = sideMenuIsCollapsed && !mediaQuery.size1000;
9436
+ const LinkComponentTag = components.button?.tagReplacement?.linkComponent ?? "a";
9437
+ const sideMenuWidth = components.sideMenu?.width ?? defaultSideMenuWidth;
9438
+ const sideMenuCollapsedWidth = theme2.styles.space + theme2.styles.space * 2 + 16 + 1 + theme2.styles.space;
9439
+ const logoSize = sideMenuCollapsedWidth - theme2.styles.space * 2;
9440
+ return /* @__PURE__ */ jsxs22(
9441
+ Div_default.column,
9442
+ {
9443
+ position: "fixed",
9444
+ width: mediaQuery.size1000 ? "100%" : isCollapsed ? sideMenuCollapsedWidth : sideMenuWidth,
9445
+ height: "100svh",
9446
+ backgroundColor: theme2.colors.backgroundBase,
9447
+ borderRight: `solid 1px ${theme2.colors.border}`,
9448
+ transform: !mediaQuery.size1000 || sideMenuIsOpenMobile ? "translateX(0)" : "translateX(-100%)",
9449
+ paddingInline: theme2.styles.space,
9450
+ paddingTop: logoAssetName || logoUrl ? theme2.styles.gap : theme2.styles.space,
9451
+ paddingBottom: theme2.styles.space,
9452
+ transition: mediaQuery.size1000 ? !isCollapsed ? `transform ${theme2.styles.transition}` : "none" : theme2.styles.transition,
9453
+ userSelect: "none",
9454
+ zIndex: 11,
9455
+ children: [
9456
+ /* @__PURE__ */ jsxs22(Div_default.column, { width: "100%", height: "100%", gap: theme2.styles.space, children: [
9457
+ (logoAssetName || logoUrl || mediaQuery.size1000) && /* @__PURE__ */ jsxs22(Div_default.row, { alignItems: "center", children: [
9458
+ (logoAssetName || logoUrl) && /* @__PURE__ */ jsx26(LinkComponentTag, { to: "/", href: "/", onClick: onClickXButton, children: /* @__PURE__ */ jsxs22(
9459
+ Div_default.row,
9460
+ {
9461
+ alignItems: "center",
9462
+ width: sideMenuCollapsedWidth ? logoSize : void 0,
9463
+ height: logoSize,
9464
+ whiteSpace: "nowrap",
9465
+ gap: theme2.styles.gap,
9466
+ children: [
9467
+ /* @__PURE__ */ jsx26(
9468
+ Image_default,
9469
+ {
9470
+ name: logoAssetName,
9471
+ src: logoUrl,
9472
+ width: logoSize,
9473
+ height: logoSize,
9474
+ objectFit: "contain"
9475
+ }
9476
+ ),
9477
+ logoText && /* @__PURE__ */ jsx26(
9478
+ Text_default,
9479
+ {
9480
+ fontFamily: logoFontFamily,
9481
+ fontSize: 22,
9482
+ fontWeight: 800,
9483
+ opacity: !isCollapsed ? 1 : 0,
9484
+ transition: theme2.styles.transition,
9485
+ userSelect: "none",
9486
+ children: logoText
9487
+ }
9488
+ )
9489
+ ]
9490
+ }
9491
+ ) }),
9492
+ mediaQuery.size1000 && /* @__PURE__ */ jsx26(Button_default.icon, { icon: "XMark", marginLeft: "auto", onClick: onClickXButton })
9493
+ ] }),
9494
+ /* @__PURE__ */ jsx26(Div_default.column, { gap: theme2.styles.gap / 2, children: readyItems.map((item) => /* @__PURE__ */ jsx26(MenuItemComponent, { item, onClick: onClickXButton }, item.text)) }),
9495
+ collapsable && !mediaQuery.size1000 && /* @__PURE__ */ jsx26(
9496
+ Div_default.row,
9497
+ {
9498
+ alignItems: "center",
9499
+ justifyContent: "center",
9500
+ backgroundColor: theme2.colors.backgroundBase,
9501
+ borderRadius: theme2.styles.borderRadius,
9502
+ marginTop: "auto",
9503
+ cursor: "pointer",
9504
+ filterHover: filterHover().z1,
9505
+ paddingBlock: theme2.styles.gap,
9506
+ isTabAccessed: true,
9507
+ onClick: setSideMenuIsCollapsed.toggle,
9508
+ children: /* @__PURE__ */ jsx26(
9509
+ Icon_default,
9510
+ {
9511
+ name: "chevronRight",
9512
+ size: 20,
9513
+ color: theme2.colors.textSecondary,
9514
+ transform: `rotate(${isCollapsed ? 0 : 180}deg)`,
9515
+ transition: theme2.styles.transition
9516
+ }
9517
+ )
9518
+ }
9519
+ )
9520
+ ] }),
9521
+ widthMobileHandle && /* @__PURE__ */ jsx26(
9522
+ Div_default.row,
9523
+ {
9524
+ position: "absolute",
9525
+ top: theme2.styles.space,
9526
+ left: "100%",
9527
+ backgroundColor: theme2.colors.backgroundBase,
9528
+ border: `solid 1px ${theme2.colors.border}`,
9529
+ borderLeft: "none",
9530
+ borderTopRightRadius: theme2.styles.borderRadius,
9531
+ borderBottomRightRadius: theme2.styles.borderRadius,
9532
+ alignItems: "center",
9533
+ cursor: "pointer",
9534
+ opacity: !mediaQuery.size1000 ? 0 : void 0,
9535
+ pointerEvents: !mediaQuery.size1000 ? "none" : void 0,
9536
+ padding: theme2.styles.gap,
9537
+ paddingRight: (theme2.styles.space + theme2.styles.gap) / 2,
9538
+ transform: !mediaQuery.size1000 ? "translateX(-100%)" : void 0,
9539
+ transition: theme2.styles.transition,
9540
+ onClick: setSideMenuIsOpenMobile.toggle,
9541
+ children: /* @__PURE__ */ jsx26(
9542
+ Icon_default,
9543
+ {
9544
+ name: "chevronRight",
9545
+ size: 20,
9546
+ color: theme2.colors.textSecondary,
9547
+ transform: sideMenuIsOpenMobile ? "rotate(180deg)" : void 0,
9548
+ transition: theme2.styles.transition
9549
+ }
9550
+ )
9551
+ }
9552
+ )
9553
+ ]
9554
+ }
9555
+ );
9556
+ };
9557
+ SideMenuComponent.pageHolder = function SideMenuPageHolder({ outsideComponent, ...props }) {
9558
+ const theme2 = useTheme();
9559
+ const mediaQuery = useMediaQuery();
9560
+ const { components, sideMenuIsCollapsed } = useBetterHtmlContextInternal();
9561
+ const sideMenuWidth = components.sideMenu?.width ?? defaultSideMenuWidth;
9562
+ const sideMenuCollapsedWidth = theme2.styles.space + theme2.styles.space * 2 + 16 + 1 + theme2.styles.space;
9563
+ return /* @__PURE__ */ jsxs22(
9564
+ Div_default,
9565
+ {
9566
+ position: "relative",
9567
+ width: "100%",
9568
+ paddingLeft: !mediaQuery.size1000 ? !sideMenuIsCollapsed ? sideMenuWidth : sideMenuCollapsedWidth : void 0,
9569
+ transition: theme2.styles.transition,
9570
+ children: [
9571
+ outsideComponent,
9572
+ /* @__PURE__ */ jsx26(PageHolder_default, { ...props })
9573
+ ]
9574
+ }
9575
+ );
9576
+ };
9577
+ var SideMenu2 = memo26(SideMenuComponent);
9578
+ SideMenu2.pageHolder = SideMenuComponent.pageHolder;
9579
+ var SideMenu_default = SideMenu2;
9580
+
9250
9581
  // src/utils/localStorage.ts
9251
9582
  function generateLocalStorage() {
9252
9583
  return {
@@ -9330,6 +9661,7 @@ export {
9330
9661
  Modal_default as Modal,
9331
9662
  PageHeader_default as PageHeader,
9332
9663
  PageHolder_default as PageHolder,
9664
+ SideMenu_default as SideMenu,
9333
9665
  Table_default as Table,
9334
9666
  Tabs_default as Tabs,
9335
9667
  Text_default as Text,
@@ -9359,6 +9691,7 @@ export {
9359
9691
  localStoragePlugin,
9360
9692
  reactRouterDomPlugin,
9361
9693
  saturateColor,
9694
+ sideMenuControls,
9362
9695
  useAlertControls,
9363
9696
  useBetterHtmlContext,
9364
9697
  useBooleanState,