@drakkar.software/octospaces-ui 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import React from 'react';
2
- import { View } from 'react-native';
1
+ import React, { ReactNode } from 'react';
2
+ import { View, StyleProp, ViewStyle } from 'react-native';
3
3
 
4
4
  /**
5
5
  * Theme contract for `@drakkar.software/octospaces-ui`.
@@ -27,6 +27,7 @@ interface Palette {
27
27
  surfaceModal: string;
28
28
  surfaceInput: string;
29
29
  sidebar: string;
30
+ sidebarPanel: string;
30
31
  sidebarActive: string;
31
32
  border: string;
32
33
  borderSubtle: string;
@@ -458,4 +459,245 @@ interface SpacesRailProps {
458
459
  */
459
460
  declare function SpacesRail({ spaces, activeId, onSelect, onAdd, onSelectDms, dmsActive, dmUnread, dmLabel, addLabel, renderIcon, renderTileImage, renderBadge, showLockCorner, renderFoot, useTileDnd, }: SpacesRailProps): React.JSX.Element;
460
461
 
461
- export { type ColorScheme, type DiscoverEntry, DiscoverList, type DiscoverListProps, DiscoverRow, type DiscoverRowProps, DiscoverScreen, type DiscoverScreenProps, type Easing, type Fonts, type LabelTracking, type Layers, type Layout, type Motion, type MotionToken, OctoSpacesThemeProvider, type OctoSpacesThemeProviderProps, type Opacity, type Palette, type Radii, type RailIconName, type RailSpace, type ShadowToken, type Shadows, SpacesRail, type SpacesRailProps, type Spacing, type Swatches, type Theme, type TypeScale, type Typography, avatarTint, filterDiscoverEntries, focusRingStyle, glowShadow, paperBorder, presenceColor, sortDiscoverEntries, statusColor, swatch, useOctoSpacesTheme, verificationColor };
462
+ /**
463
+ * Headless themed sidebar panel shell.
464
+ *
465
+ * Renders the 240–248px wide panel: a background surface (colors.sidebarPanel),
466
+ * a right border (colors.border), an optional header slot, the item list
467
+ * (scrollable by default), and an optional footer slot.
468
+ *
469
+ * All content is delegated to the host via slots — only the chrome (bg, border,
470
+ * width) and the ScrollView wrapper are shared.
471
+ *
472
+ * ```tsx
473
+ * <Sidebar
474
+ * header={
475
+ * <SidebarHeader
476
+ * leading={<SpaceSwitcher variant="sidebar" />}
477
+ * actions={<>
478
+ * <IconButton name="search" ... />
479
+ * <IconButton name="plus" ... />
480
+ * </>}
481
+ * />
482
+ * }
483
+ * contentContainerStyle={{ paddingHorizontal: 8 }}
484
+ * >
485
+ * <WorkObjects ... />
486
+ * </Sidebar>
487
+ * ```
488
+ */
489
+
490
+ interface SidebarProps {
491
+ /** Header slot — render a {@link SidebarHeader} or custom content above the list. */
492
+ header?: React.ReactNode;
493
+ /** Footer slot — pinned below the scroll area. */
494
+ footer?: React.ReactNode;
495
+ /** The item list. Wrapped in a ScrollView unless `scrollable` is `false`. */
496
+ children: React.ReactNode;
497
+ /** Panel width in pixels. Defaults to `theme.layout.sidebarWidth ?? 248`. */
498
+ width?: number;
499
+ /**
500
+ * When `false`, children are rendered in a plain `View` instead of a `ScrollView`.
501
+ * Use when the host manages its own scroll (e.g. multiple independent lists).
502
+ * @default true
503
+ */
504
+ scrollable?: boolean;
505
+ /**
506
+ * Passed to the `ScrollView`'s `contentContainerStyle` (or to the body `View`
507
+ * style when `scrollable` is `false`).
508
+ */
509
+ contentContainerStyle?: StyleProp<ViewStyle>;
510
+ /**
511
+ * Override the panel background color.
512
+ * Defaults to `colors.sidebarPanel` from the injected theme.
513
+ */
514
+ background?: string;
515
+ }
516
+ declare function Sidebar({ header, footer, children, width, scrollable, contentContainerStyle, background, }: SidebarProps): React.JSX.Element;
517
+
518
+ /**
519
+ * Headless themed sidebar header strip.
520
+ *
521
+ * Row 1: a `leading` slot (flex:1, typically a space selector or name) + an
522
+ * optional `actions` row (right-aligned command buttons). An optional `extra`
523
+ * slot below row 1 accepts additional controls (OctoChat uses this for its
524
+ * `ModeSwitcher` + jump-to search bar). An optional bottom hairline divider.
525
+ *
526
+ * ```tsx
527
+ * // OctoVault — space switcher + command icons
528
+ * <SidebarHeader
529
+ * leading={<SpaceSwitcher variant="sidebar" />}
530
+ * actions={<>
531
+ * <IconButton name="search" onPress={openSearch} tooltip="Search" shortcut="⌘K" />
532
+ * <IconButton name="plus" onPress={newPage} tooltip="New page" shortcut="⌘N" />
533
+ * <IconButton name="sidebar" onPress={collapse} tooltip="Hide sidebar" shortcut="⌘\\" />
534
+ * </>}
535
+ * />
536
+ *
537
+ * // OctoChat — space menu + mode switcher + jump-to bar
538
+ * <SidebarHeader
539
+ * leading={<Pressable onPress={onOpenSpaceMenu}>…</Pressable>}
540
+ * extra={<><ModeSwitcher /><JumpToBar /></>}
541
+ * divider
542
+ * />
543
+ * ```
544
+ */
545
+
546
+ interface SidebarHeaderProps {
547
+ /** Leading content (flex:1) — typically the space selector / space name. */
548
+ leading: React.ReactNode;
549
+ /**
550
+ * Right-aligned action buttons row.
551
+ * OctoVault passes its own `IconButton` components here (with tooltips +
552
+ * keyboard shortcuts). For simpler headless consumers use `SidebarActionButton`.
553
+ */
554
+ actions?: React.ReactNode;
555
+ /**
556
+ * Extra slot rendered below the leading+actions row.
557
+ * Use for secondary controls like a mode switcher or a search bar.
558
+ */
559
+ extra?: React.ReactNode;
560
+ /**
561
+ * Render a hairline bottom divider in `colors.borderSubtle`.
562
+ * @default false
563
+ */
564
+ divider?: boolean;
565
+ /**
566
+ * Style applied to the outer container. Use to set host-specific padding,
567
+ * gap, background override, etc.
568
+ */
569
+ style?: StyleProp<ViewStyle>;
570
+ }
571
+ declare function SidebarHeader({ leading, actions, extra, divider, style, }: SidebarHeaderProps): React.JSX.Element;
572
+
573
+ /**
574
+ * Headless themed icon-button primitive for sidebar action slots.
575
+ *
576
+ * Renders a square `Pressable` with hover/press wash from the injected theme.
577
+ * The icon is provided by the host app as a `ReactNode` — this keeps the package
578
+ * free of `@expo/vector-icons`, `Tooltip`, and keyboard-shortcut concerns.
579
+ *
580
+ * Host apps with richer icon buttons (OctoVault's `IconButton` has tooltips,
581
+ * keyboard-shortcut labels, and haptics) can slot those directly into
582
+ * `SidebarHeader.actions` instead — this primitive is for simpler headless
583
+ * consumers.
584
+ *
585
+ * ```tsx
586
+ * <SidebarActionButton
587
+ * icon={<Icon name="search" size={15} color={theme.colors.textSecondary} />}
588
+ * onPress={openSearch}
589
+ * accessibilityLabel="Search"
590
+ * />
591
+ * ```
592
+ */
593
+
594
+ interface SidebarActionButtonProps {
595
+ /** Icon element to render — the host provides the icon component, size, and color. */
596
+ icon: React.ReactNode;
597
+ onPress: () => void;
598
+ accessibilityLabel: string;
599
+ /**
600
+ * Width and height of the pressable target in pixels.
601
+ * @default 32
602
+ */
603
+ size?: number;
604
+ }
605
+ declare function SidebarActionButton({ icon, onPress, accessibilityLabel, size, }: SidebarActionButtonProps): React.JSX.Element;
606
+
607
+ /**
608
+ * Generic themed sidebar navigation row.
609
+ *
610
+ * The headless analog of `DiscoverRow` for the sidebar panel. Suitable for
611
+ * simple link-style rows (explore, threads, pinned, …). Complex item lists
612
+ * (OctoVault's `ObjectTree`, OctoChat's `RoomCategoryList`) use their own row
613
+ * components — this primitive is for straightforward nav items.
614
+ *
615
+ * Active rows are highlighted with `colors.sidebarActive`. Hovered rows receive
616
+ * a subtle `colors.primarySubtle` wash.
617
+ *
618
+ * ```tsx
619
+ * <SidebarItem
620
+ * label="Threads"
621
+ * icon={<Icon name="thread" size={15} color={colors.textSecondary} />}
622
+ * active={threadsActive}
623
+ * onPress={onOpenThreads}
624
+ * />
625
+ * ```
626
+ */
627
+
628
+ interface SidebarItemProps {
629
+ label: string;
630
+ /** Leading icon element — the host provides the icon component. */
631
+ icon?: React.ReactNode;
632
+ /** Highlight the row as the current destination. */
633
+ active?: boolean;
634
+ /** Badge shown at the trailing edge — a number or short string. */
635
+ badge?: number | string;
636
+ onPress: () => void;
637
+ onLongPress?: () => void;
638
+ /** Additional trailing element (e.g. an action button). */
639
+ trailing?: React.ReactNode;
640
+ /**
641
+ * Left indentation level for nested items. Each level adds 16 px.
642
+ * @default 0
643
+ */
644
+ indent?: number;
645
+ }
646
+ declare function SidebarItem({ label, icon, active, badge, onPress, onLongPress, trailing, indent, }: SidebarItemProps): React.JSX.Element;
647
+
648
+ /**
649
+ * Full-screen scrim overlay that centers its content. Tapping the backdrop, the
650
+ * close button, the Escape key (web) or the hardware back (Android) dismisses it.
651
+ *
652
+ * All interactive chrome (close button, action buttons) is injected via render
653
+ * props so this package remains free of @expo/vector-icons, expo-image, and
654
+ * reanimated. The host app renders its own icon buttons and images.
655
+ *
656
+ * @example
657
+ * ```tsx
658
+ * import { Lightbox } from '@drakkar.software/octospaces-ui';
659
+ *
660
+ * <Lightbox
661
+ * visible={zoomed}
662
+ * onClose={() => setZoomed(false)}
663
+ * renderCloseButton={(onClose) => (
664
+ * <IconButton name="x" color={colors.onScrim} onPress={onClose} />
665
+ * )}
666
+ * renderActions={() => (
667
+ * <IconButton name="share" color={colors.onScrim} onPress={handleShare} />
668
+ * )}
669
+ * >
670
+ * <Image source={{ uri }} style={{ width: w * 0.92, height: h * 0.82 }} />
671
+ * </Lightbox>
672
+ * ```
673
+ */
674
+
675
+ interface LightboxProps {
676
+ visible: boolean;
677
+ onClose: () => void;
678
+ /** Centered content — the host renders the full-size image here. */
679
+ children: ReactNode;
680
+ /** Accessible label for the backdrop tap-to-close. Default: "Close preview". */
681
+ closeLabel?: string;
682
+ /**
683
+ * Render the close affordance pinned to the top-right corner.
684
+ * Receives `onClose` so the button can dismiss the overlay.
685
+ * If omitted, tapping the backdrop or hardware back still closes it.
686
+ */
687
+ renderCloseButton?: (onClose: () => void) => ReactNode;
688
+ /**
689
+ * Render additional action(s) pinned to the bottom-right corner,
690
+ * e.g. a save/share button. Return `null` to show nothing.
691
+ */
692
+ renderActions?: () => ReactNode;
693
+ }
694
+ /**
695
+ * Full-screen scrim overlay that centers its children. Headless: all buttons
696
+ * are injected via render props; the package has no icon or image dependencies.
697
+ *
698
+ * Dismissal: backdrop tap · `renderCloseButton` · hardware back (Android) ·
699
+ * Escape key (web).
700
+ */
701
+ declare function Lightbox({ visible, onClose, children, closeLabel, renderCloseButton, renderActions, }: LightboxProps): React.JSX.Element;
702
+
703
+ export { type ColorScheme, type DiscoverEntry, DiscoverList, type DiscoverListProps, DiscoverRow, type DiscoverRowProps, DiscoverScreen, type DiscoverScreenProps, type Easing, type Fonts, type LabelTracking, type Layers, type Layout, Lightbox, type LightboxProps, type Motion, type MotionToken, OctoSpacesThemeProvider, type OctoSpacesThemeProviderProps, type Opacity, type Palette, type Radii, type RailIconName, type RailSpace, type ShadowToken, type Shadows, Sidebar, SidebarActionButton, type SidebarActionButtonProps, SidebarHeader, type SidebarHeaderProps, SidebarItem, type SidebarItemProps, type SidebarProps, SpacesRail, type SpacesRailProps, type Spacing, type Swatches, type Theme, type TypeScale, type Typography, avatarTint, filterDiscoverEntries, focusRingStyle, glowShadow, paperBorder, presenceColor, sortDiscoverEntries, statusColor, swatch, useOctoSpacesTheme, verificationColor };
package/dist/index.js CHANGED
@@ -828,11 +828,295 @@ function SpacesRail({
828
828
  renderFoot ? renderFoot() : null
829
829
  );
830
830
  }
831
+
832
+ // src/sidebar/Sidebar.tsx
833
+ import React6 from "react";
834
+ import { ScrollView as ScrollView2, View as View5 } from "react-native";
835
+ function Sidebar({
836
+ header,
837
+ footer,
838
+ children,
839
+ width,
840
+ scrollable = true,
841
+ contentContainerStyle,
842
+ background
843
+ }) {
844
+ const theme = useOctoSpacesTheme();
845
+ const { colors, layout } = theme;
846
+ const panelWidth = width ?? layout["sidebarWidth"] ?? 248;
847
+ const bg = background ?? colors.sidebarPanel;
848
+ return /* @__PURE__ */ React6.createElement(
849
+ View5,
850
+ {
851
+ style: {
852
+ width: panelWidth,
853
+ backgroundColor: bg,
854
+ borderRightWidth: 1,
855
+ borderRightColor: colors.border,
856
+ flexDirection: "column"
857
+ }
858
+ },
859
+ header ?? null,
860
+ scrollable ? /* @__PURE__ */ React6.createElement(
861
+ ScrollView2,
862
+ {
863
+ style: { flex: 1 },
864
+ contentContainerStyle: contentContainerStyle ?? void 0,
865
+ showsVerticalScrollIndicator: false
866
+ },
867
+ children
868
+ ) : /* @__PURE__ */ React6.createElement(View5, { style: [{ flex: 1 }, contentContainerStyle] }, children),
869
+ footer ?? null
870
+ );
871
+ }
872
+
873
+ // src/sidebar/SidebarHeader.tsx
874
+ import React7 from "react";
875
+ import { StyleSheet, View as View6 } from "react-native";
876
+ function SidebarHeader({
877
+ leading,
878
+ actions,
879
+ extra,
880
+ divider = false,
881
+ style
882
+ }) {
883
+ const theme = useOctoSpacesTheme();
884
+ const { colors } = theme;
885
+ return /* @__PURE__ */ React7.createElement(
886
+ View6,
887
+ {
888
+ style: [
889
+ styles.root,
890
+ style,
891
+ divider ? {
892
+ borderBottomWidth: StyleSheet.hairlineWidth,
893
+ borderBottomColor: colors.borderSubtle
894
+ } : void 0
895
+ ]
896
+ },
897
+ /* @__PURE__ */ React7.createElement(View6, { style: styles.row }, /* @__PURE__ */ React7.createElement(View6, { style: styles.leading }, leading), actions != null ? /* @__PURE__ */ React7.createElement(View6, { style: styles.actions }, actions) : null),
898
+ extra != null ? /* @__PURE__ */ React7.createElement(View6, null, extra) : null
899
+ );
900
+ }
901
+ var styles = StyleSheet.create({
902
+ root: {
903
+ flexDirection: "column"
904
+ },
905
+ row: {
906
+ flexDirection: "row",
907
+ alignItems: "center"
908
+ },
909
+ leading: {
910
+ flex: 1,
911
+ minWidth: 0
912
+ },
913
+ actions: {
914
+ flexDirection: "row",
915
+ alignItems: "center",
916
+ flexShrink: 0
917
+ }
918
+ });
919
+
920
+ // src/sidebar/SidebarActionButton.tsx
921
+ import React8, { useState as useState3 } from "react";
922
+ import { Pressable as RNPressable2 } from "react-native";
923
+ var Pressable4 = RNPressable2;
924
+ function SidebarActionButton({
925
+ icon,
926
+ onPress,
927
+ accessibilityLabel,
928
+ size = 32
929
+ }) {
930
+ const theme = useOctoSpacesTheme();
931
+ const { colors, radii } = theme;
932
+ const [hovered, setHovered] = useState3(false);
933
+ const [pressed, setPressed] = useState3(false);
934
+ const bg = pressed ? colors.primaryMuted ?? "rgba(0,0,0,0.10)" : hovered ? colors.primarySubtle ?? "rgba(0,0,0,0.05)" : "transparent";
935
+ const radius = radii["sm"] ?? 4;
936
+ return /* @__PURE__ */ React8.createElement(
937
+ Pressable4,
938
+ {
939
+ onPress,
940
+ onPressIn: () => setPressed(true),
941
+ onPressOut: () => setPressed(false),
942
+ onMouseEnter: () => setHovered(true),
943
+ onMouseLeave: () => setHovered(false),
944
+ accessibilityRole: "button",
945
+ accessibilityLabel,
946
+ style: {
947
+ width: size,
948
+ height: size,
949
+ alignItems: "center",
950
+ justifyContent: "center",
951
+ borderRadius: radius,
952
+ backgroundColor: bg
953
+ }
954
+ },
955
+ icon
956
+ );
957
+ }
958
+
959
+ // src/sidebar/SidebarItem.tsx
960
+ import React9, { useState as useState4 } from "react";
961
+ import { Pressable as RNPressable3, StyleSheet as StyleSheet2, Text as Text5, View as View7 } from "react-native";
962
+ var Pressable5 = RNPressable3;
963
+ function SidebarItem({
964
+ label,
965
+ icon,
966
+ active = false,
967
+ badge,
968
+ onPress,
969
+ onLongPress,
970
+ trailing,
971
+ indent = 0
972
+ }) {
973
+ const theme = useOctoSpacesTheme();
974
+ const { colors, type: typeScale, fonts, spacing, radii } = theme;
975
+ const [hovered, setHovered] = useState4(false);
976
+ const sp1 = spacing["1"] ?? 4;
977
+ const sp2 = spacing["2"] ?? 8;
978
+ const sp3 = spacing["3"] ?? 12;
979
+ const radSm = radii["sm"] ?? 4;
980
+ const indentPx = indent * 16;
981
+ const bg = active ? colors.sidebarActive : hovered ? colors.primarySubtle ?? "rgba(0,0,0,0.04)" : "transparent";
982
+ const textColor = active ? colors.primary : colors.text;
983
+ return /* @__PURE__ */ React9.createElement(
984
+ Pressable5,
985
+ {
986
+ onPress,
987
+ onLongPress,
988
+ onMouseEnter: () => setHovered(true),
989
+ onMouseLeave: () => setHovered(false),
990
+ accessibilityRole: "button",
991
+ accessibilityLabel: label,
992
+ style: {
993
+ flexDirection: "row",
994
+ alignItems: "center",
995
+ gap: sp2,
996
+ paddingVertical: sp1 + 2,
997
+ paddingLeft: sp3 + indentPx,
998
+ paddingRight: sp3,
999
+ borderRadius: radSm,
1000
+ backgroundColor: bg
1001
+ }
1002
+ },
1003
+ icon != null ? /* @__PURE__ */ React9.createElement(View7, { style: styles2.iconSlot }, icon) : null,
1004
+ /* @__PURE__ */ React9.createElement(
1005
+ Text5,
1006
+ {
1007
+ style: {
1008
+ flex: 1,
1009
+ fontSize: typeScale["callout"]?.size ?? 13,
1010
+ lineHeight: typeScale["callout"]?.lineHeight ?? 18,
1011
+ fontWeight: active ? "600" : "400",
1012
+ color: textColor,
1013
+ fontFamily: fonts["body"] ?? void 0
1014
+ },
1015
+ numberOfLines: 1
1016
+ },
1017
+ label
1018
+ ),
1019
+ badge != null ? /* @__PURE__ */ React9.createElement(
1020
+ View7,
1021
+ {
1022
+ style: {
1023
+ minWidth: 16,
1024
+ height: 16,
1025
+ borderRadius: 8,
1026
+ backgroundColor: active ? colors.primary : colors.textTertiary,
1027
+ alignItems: "center",
1028
+ justifyContent: "center",
1029
+ paddingHorizontal: sp1
1030
+ }
1031
+ },
1032
+ /* @__PURE__ */ React9.createElement(
1033
+ Text5,
1034
+ {
1035
+ style: {
1036
+ fontSize: typeScale["micro"]?.size ?? 10,
1037
+ lineHeight: typeScale["micro"]?.lineHeight ?? 14,
1038
+ fontWeight: "700",
1039
+ color: active ? colors.textOnPrimary : colors.textInverse
1040
+ }
1041
+ },
1042
+ String(badge)
1043
+ )
1044
+ ) : null,
1045
+ trailing != null ? trailing : null
1046
+ );
1047
+ }
1048
+ var styles2 = StyleSheet2.create({
1049
+ iconSlot: { width: 18, alignItems: "center", justifyContent: "center" }
1050
+ });
1051
+
1052
+ // src/lightbox/Lightbox.tsx
1053
+ import React10, { useEffect as useEffect2 } from "react";
1054
+ import { Modal, Platform, Pressable as Pressable6, View as View8 } from "react-native";
1055
+ function Lightbox({
1056
+ visible,
1057
+ onClose,
1058
+ children,
1059
+ closeLabel = "Close preview",
1060
+ renderCloseButton,
1061
+ renderActions
1062
+ }) {
1063
+ const theme = useOctoSpacesTheme();
1064
+ useEffect2(() => {
1065
+ if (!visible || Platform.OS !== "web") return;
1066
+ const onKey = (e) => {
1067
+ if (e.key === "Escape") onClose();
1068
+ };
1069
+ window.addEventListener("keydown", onKey);
1070
+ return () => window.removeEventListener("keydown", onKey);
1071
+ }, [visible, onClose]);
1072
+ const pad = theme.spacing["6"] ?? 24;
1073
+ const insetV = theme.spacing["8"] ?? 32;
1074
+ const insetH = theme.spacing["4"] ?? 16;
1075
+ return /* @__PURE__ */ React10.createElement(
1076
+ Modal,
1077
+ {
1078
+ visible,
1079
+ transparent: true,
1080
+ animationType: "fade",
1081
+ onRequestClose: onClose,
1082
+ statusBarTranslucent: true
1083
+ },
1084
+ /* @__PURE__ */ React10.createElement(
1085
+ Pressable6,
1086
+ {
1087
+ style: {
1088
+ flex: 1,
1089
+ alignItems: "center",
1090
+ justifyContent: "center",
1091
+ padding: pad,
1092
+ backgroundColor: theme.colors.overlay ?? "rgba(0,0,0,0.85)"
1093
+ },
1094
+ onPress: onClose,
1095
+ accessibilityLabel: closeLabel
1096
+ },
1097
+ /* @__PURE__ */ React10.createElement(
1098
+ View8,
1099
+ {
1100
+ style: { alignItems: "center", justifyContent: "center" },
1101
+ pointerEvents: "box-none"
1102
+ },
1103
+ children
1104
+ ),
1105
+ renderCloseButton ? /* @__PURE__ */ React10.createElement(View8, { style: { position: "absolute", top: insetV, right: insetH } }, renderCloseButton(onClose)) : null,
1106
+ renderActions ? /* @__PURE__ */ React10.createElement(View8, { style: { position: "absolute", bottom: insetV, right: insetH } }, renderActions()) : null
1107
+ )
1108
+ );
1109
+ }
831
1110
  export {
832
1111
  DiscoverList,
833
1112
  DiscoverRow,
834
1113
  DiscoverScreen,
1114
+ Lightbox,
835
1115
  OctoSpacesThemeProvider,
1116
+ Sidebar,
1117
+ SidebarActionButton,
1118
+ SidebarHeader,
1119
+ SidebarItem,
836
1120
  SpacesRail,
837
1121
  avatarTint,
838
1122
  filterDiscoverEntries,