@drakkar.software/octospaces-ui 0.3.0 → 0.4.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/dist/index.js CHANGED
@@ -828,11 +828,599 @@ 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/sidebar/SpaceSwitcher.tsx
1053
+ import React10, { useRef as useRef2, useState as useState5 } from "react";
1054
+ import { Pressable as RNPressable4, StyleSheet as StyleSheet3, Text as Text6, View as View8 } from "react-native";
1055
+ var Pressable6 = RNPressable4;
1056
+ function SpaceSwitcher({
1057
+ spaces,
1058
+ activeId,
1059
+ onSelect,
1060
+ onAdd,
1061
+ addLabel = "Join or create a space",
1062
+ onSettings,
1063
+ settingsLabel = "Space settings",
1064
+ variant,
1065
+ renderContainer,
1066
+ renderTriggerAvatar,
1067
+ renderSpaceAvatar,
1068
+ renderIcon,
1069
+ footerSlot
1070
+ }) {
1071
+ const theme = useOctoSpacesTheme();
1072
+ const { colors, type: typeScale, fonts, spacing: sp, radii } = theme;
1073
+ const [open, setOpen] = useState5(false);
1074
+ const [triggerHovered, setTriggerHovered] = useState5(false);
1075
+ const anchorRef = useRef2(null);
1076
+ const active = spaces.find((s) => s.id === activeId) ?? spaces[0] ?? null;
1077
+ const close = () => setOpen(false);
1078
+ const handleSelect = (id) => {
1079
+ close();
1080
+ onSelect(id);
1081
+ };
1082
+ const handleAdd = () => {
1083
+ close();
1084
+ onAdd?.();
1085
+ };
1086
+ const handleSettings = () => {
1087
+ close();
1088
+ onSettings?.();
1089
+ };
1090
+ const sp1 = sp["1"] ?? 4;
1091
+ const sp2 = sp["2"] ?? 8;
1092
+ const sp3 = sp["3"] ?? 12;
1093
+ const sp4 = sp["4"] ?? 16;
1094
+ const radMd = radii["md"] ?? 6;
1095
+ const bodyFont = fonts["body"] ?? void 0;
1096
+ const bodySize = typeScale["callout"]?.size ?? 13;
1097
+ const bodyLine = typeScale["callout"]?.lineHeight ?? 18;
1098
+ const labelSize = typeScale["caption"]?.size ?? 11;
1099
+ const labelLine = typeScale["caption"]?.lineHeight ?? 16;
1100
+ const triggerStyle = variant === "sidebar" ? {
1101
+ flex: 1,
1102
+ flexDirection: "row",
1103
+ alignItems: "center",
1104
+ gap: sp2,
1105
+ paddingHorizontal: sp2,
1106
+ paddingVertical: sp1 + 2,
1107
+ borderRadius: radMd,
1108
+ minWidth: 0,
1109
+ backgroundColor: triggerHovered ? colors.primarySubtle ?? "rgba(0,0,0,0.05)" : "transparent"
1110
+ } : {
1111
+ flexDirection: "row",
1112
+ alignItems: "center",
1113
+ justifyContent: "center",
1114
+ gap: sp2,
1115
+ paddingHorizontal: sp2,
1116
+ paddingVertical: sp1,
1117
+ borderRadius: radMd,
1118
+ backgroundColor: triggerHovered ? colors.primarySubtle ?? "rgba(0,0,0,0.05)" : "transparent"
1119
+ };
1120
+ const dropdownContent = /* @__PURE__ */ React10.createElement(View8, { style: { paddingVertical: sp1 } }, spaces.length > 0 ? /* @__PURE__ */ React10.createElement(
1121
+ SectionLabel,
1122
+ {
1123
+ label: "Spaces",
1124
+ color: colors.textTertiary,
1125
+ size: labelSize,
1126
+ lineHeight: labelLine,
1127
+ font: bodyFont,
1128
+ paddingH: sp4,
1129
+ paddingV: sp1
1130
+ }
1131
+ ) : null, spaces.map((s) => /* @__PURE__ */ React10.createElement(
1132
+ SpaceRow,
1133
+ {
1134
+ key: s.id,
1135
+ space: s,
1136
+ active: s.id === (active?.id ?? null),
1137
+ onPress: () => handleSelect(s.id),
1138
+ renderAvatar: renderSpaceAvatar,
1139
+ renderIcon,
1140
+ colors,
1141
+ bodyFont,
1142
+ bodySize,
1143
+ bodyLine,
1144
+ sp2,
1145
+ sp3,
1146
+ sp4,
1147
+ radMd
1148
+ }
1149
+ )), onAdd ? /* @__PURE__ */ React10.createElement(
1150
+ ActionRow,
1151
+ {
1152
+ label: spaces.length > 0 ? addLabel : "Create your first space",
1153
+ iconName: "plus",
1154
+ onPress: handleAdd,
1155
+ renderIcon,
1156
+ colors,
1157
+ bodyFont,
1158
+ bodySize,
1159
+ bodyLine,
1160
+ sp2,
1161
+ sp3,
1162
+ sp4,
1163
+ radMd
1164
+ }
1165
+ ) : null, onSettings && active ? /* @__PURE__ */ React10.createElement(
1166
+ ActionRow,
1167
+ {
1168
+ label: settingsLabel,
1169
+ iconName: "gear",
1170
+ onPress: handleSettings,
1171
+ renderIcon,
1172
+ colors,
1173
+ bodyFont,
1174
+ bodySize,
1175
+ bodyLine,
1176
+ sp2,
1177
+ sp3,
1178
+ sp4,
1179
+ radMd
1180
+ }
1181
+ ) : null, footerSlot != null ? /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
1182
+ View8,
1183
+ {
1184
+ style: {
1185
+ height: StyleSheet3.hairlineWidth,
1186
+ backgroundColor: colors.borderSubtle,
1187
+ marginVertical: sp1,
1188
+ marginHorizontal: sp2
1189
+ }
1190
+ }
1191
+ ), footerSlot) : null);
1192
+ return /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement(
1193
+ Pressable6,
1194
+ {
1195
+ ref: anchorRef,
1196
+ accessibilityRole: "button",
1197
+ accessibilityLabel: active ? `${active.name} \u2014 switch space` : "Switch space",
1198
+ accessibilityState: { expanded: open },
1199
+ hitSlop: 6,
1200
+ onPress: () => setOpen(true),
1201
+ onMouseEnter: () => setTriggerHovered(true),
1202
+ onMouseLeave: () => setTriggerHovered(false),
1203
+ style: triggerStyle
1204
+ },
1205
+ renderTriggerAvatar ? renderTriggerAvatar(active, 22) : null,
1206
+ /* @__PURE__ */ React10.createElement(
1207
+ Text6,
1208
+ {
1209
+ numberOfLines: 1,
1210
+ style: {
1211
+ flex: variant === "sidebar" ? 1 : void 0,
1212
+ minWidth: 0,
1213
+ flexShrink: 1,
1214
+ fontSize: typeScale["heading"]?.size ?? 15,
1215
+ lineHeight: typeScale["heading"]?.lineHeight ?? 20,
1216
+ fontWeight: "600",
1217
+ color: colors.text,
1218
+ fontFamily: bodyFont
1219
+ }
1220
+ },
1221
+ active?.name ?? "Spaces"
1222
+ ),
1223
+ renderIcon ? renderIcon("chevron-down", 14, colors.textTertiary) : null
1224
+ ), renderContainer({ isOpen: open, onClose: close, anchorRef, children: dropdownContent }));
1225
+ }
1226
+ function SectionLabel({ label, color, size, lineHeight, font, paddingH, paddingV }) {
1227
+ return /* @__PURE__ */ React10.createElement(
1228
+ Text6,
1229
+ {
1230
+ style: {
1231
+ fontSize: size,
1232
+ lineHeight,
1233
+ fontWeight: "600",
1234
+ color,
1235
+ fontFamily: font,
1236
+ textTransform: "uppercase",
1237
+ letterSpacing: 0.5,
1238
+ paddingHorizontal: paddingH,
1239
+ paddingVertical: paddingV
1240
+ }
1241
+ },
1242
+ label
1243
+ );
1244
+ }
1245
+ function SpaceRow({
1246
+ space,
1247
+ active,
1248
+ onPress,
1249
+ renderAvatar,
1250
+ renderIcon,
1251
+ colors,
1252
+ bodyFont,
1253
+ bodySize,
1254
+ bodyLine,
1255
+ sp2,
1256
+ sp3,
1257
+ sp4,
1258
+ radMd
1259
+ }) {
1260
+ const [hovered, setHovered] = useState5(false);
1261
+ const bg = hovered ? colors.primarySubtle ?? "rgba(0,0,0,0.04)" : "transparent";
1262
+ return /* @__PURE__ */ React10.createElement(
1263
+ Pressable6,
1264
+ {
1265
+ accessibilityRole: "menuitem",
1266
+ accessibilityLabel: active ? `${space.name} (current)` : `Switch to ${space.name}`,
1267
+ accessibilityState: { selected: active },
1268
+ onPress,
1269
+ onMouseEnter: () => setHovered(true),
1270
+ onMouseLeave: () => setHovered(false),
1271
+ style: {
1272
+ flexDirection: "row",
1273
+ alignItems: "center",
1274
+ gap: sp3,
1275
+ paddingHorizontal: sp4,
1276
+ paddingVertical: sp2,
1277
+ borderRadius: radMd,
1278
+ backgroundColor: bg
1279
+ }
1280
+ },
1281
+ renderAvatar ? renderAvatar(space, 24) : null,
1282
+ /* @__PURE__ */ React10.createElement(
1283
+ Text6,
1284
+ {
1285
+ numberOfLines: 1,
1286
+ style: {
1287
+ flex: 1,
1288
+ minWidth: 0,
1289
+ fontSize: bodySize,
1290
+ lineHeight: bodyLine,
1291
+ fontWeight: active ? "600" : "400",
1292
+ color: active ? colors.primary : colors.text,
1293
+ fontFamily: bodyFont
1294
+ }
1295
+ },
1296
+ space.name
1297
+ ),
1298
+ active && renderIcon ? renderIcon("check", 15, colors.primary) : null
1299
+ );
1300
+ }
1301
+ function ActionRow({
1302
+ label,
1303
+ iconName,
1304
+ onPress,
1305
+ renderIcon,
1306
+ colors,
1307
+ bodyFont,
1308
+ bodySize,
1309
+ bodyLine,
1310
+ sp2,
1311
+ sp3,
1312
+ sp4,
1313
+ radMd
1314
+ }) {
1315
+ const [hovered, setHovered] = useState5(false);
1316
+ const bg = hovered ? colors.primarySubtle ?? "rgba(0,0,0,0.04)" : "transparent";
1317
+ return /* @__PURE__ */ React10.createElement(
1318
+ Pressable6,
1319
+ {
1320
+ accessibilityRole: "menuitem",
1321
+ accessibilityLabel: label,
1322
+ onPress,
1323
+ onMouseEnter: () => setHovered(true),
1324
+ onMouseLeave: () => setHovered(false),
1325
+ style: {
1326
+ flexDirection: "row",
1327
+ alignItems: "center",
1328
+ gap: sp3,
1329
+ paddingHorizontal: sp4,
1330
+ paddingVertical: sp2,
1331
+ borderRadius: radMd,
1332
+ backgroundColor: bg
1333
+ }
1334
+ },
1335
+ renderIcon ? /* @__PURE__ */ React10.createElement(View8, { style: { width: 24, alignItems: "center", justifyContent: "center" } }, renderIcon(iconName, 15, colors.textSecondary)) : null,
1336
+ /* @__PURE__ */ React10.createElement(
1337
+ Text6,
1338
+ {
1339
+ numberOfLines: 1,
1340
+ style: {
1341
+ flex: 1,
1342
+ minWidth: 0,
1343
+ fontSize: bodySize,
1344
+ lineHeight: bodyLine,
1345
+ fontWeight: "400",
1346
+ color: colors.text,
1347
+ fontFamily: bodyFont
1348
+ }
1349
+ },
1350
+ label
1351
+ )
1352
+ );
1353
+ }
1354
+
1355
+ // src/lightbox/Lightbox.tsx
1356
+ import React11, { useEffect as useEffect2 } from "react";
1357
+ import { Modal, Platform, Pressable as Pressable7, View as View9 } from "react-native";
1358
+ function Lightbox({
1359
+ visible,
1360
+ onClose,
1361
+ children,
1362
+ closeLabel = "Close preview",
1363
+ renderCloseButton,
1364
+ renderActions
1365
+ }) {
1366
+ const theme = useOctoSpacesTheme();
1367
+ useEffect2(() => {
1368
+ if (!visible || Platform.OS !== "web") return;
1369
+ const onKey = (e) => {
1370
+ if (e.key === "Escape") onClose();
1371
+ };
1372
+ window.addEventListener("keydown", onKey);
1373
+ return () => window.removeEventListener("keydown", onKey);
1374
+ }, [visible, onClose]);
1375
+ const pad = theme.spacing["6"] ?? 24;
1376
+ const insetV = theme.spacing["8"] ?? 32;
1377
+ const insetH = theme.spacing["4"] ?? 16;
1378
+ return /* @__PURE__ */ React11.createElement(
1379
+ Modal,
1380
+ {
1381
+ visible,
1382
+ transparent: true,
1383
+ animationType: "fade",
1384
+ onRequestClose: onClose,
1385
+ statusBarTranslucent: true
1386
+ },
1387
+ /* @__PURE__ */ React11.createElement(
1388
+ Pressable7,
1389
+ {
1390
+ style: {
1391
+ flex: 1,
1392
+ alignItems: "center",
1393
+ justifyContent: "center",
1394
+ padding: pad,
1395
+ backgroundColor: theme.colors.overlay ?? "rgba(0,0,0,0.85)"
1396
+ },
1397
+ onPress: onClose,
1398
+ accessibilityLabel: closeLabel
1399
+ },
1400
+ /* @__PURE__ */ React11.createElement(
1401
+ View9,
1402
+ {
1403
+ style: { alignItems: "center", justifyContent: "center" },
1404
+ pointerEvents: "box-none"
1405
+ },
1406
+ children
1407
+ ),
1408
+ renderCloseButton ? /* @__PURE__ */ React11.createElement(View9, { style: { position: "absolute", top: insetV, right: insetH } }, renderCloseButton(onClose)) : null,
1409
+ renderActions ? /* @__PURE__ */ React11.createElement(View9, { style: { position: "absolute", bottom: insetV, right: insetH } }, renderActions()) : null
1410
+ )
1411
+ );
1412
+ }
831
1413
  export {
832
1414
  DiscoverList,
833
1415
  DiscoverRow,
834
1416
  DiscoverScreen,
1417
+ Lightbox,
835
1418
  OctoSpacesThemeProvider,
1419
+ Sidebar,
1420
+ SidebarActionButton,
1421
+ SidebarHeader,
1422
+ SidebarItem,
1423
+ SpaceSwitcher,
836
1424
  SpacesRail,
837
1425
  avatarTint,
838
1426
  filterDiscoverEntries,