@drakkar.software/octospaces-ui 0.2.0 → 0.3.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 +123 -2
- package/dist/index.js +435 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +7 -0
- package/src/sidebar/SpacesRail.tsx +590 -0
- package/src/sidebar/tile-state.test.ts +126 -0
- package/src/sidebar/tile-state.ts +100 -0
- package/src/sidebar/types.ts +25 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Theme contract for `@drakkar.software/octospaces-ui`.
|
|
@@ -334,7 +335,127 @@ interface DiscoverScreenProps {
|
|
|
334
335
|
* @default true
|
|
335
336
|
*/
|
|
336
337
|
searchEnabled?: boolean;
|
|
338
|
+
/**
|
|
339
|
+
* Optional ref whose `.current` is set to a `reload()` function once mounted.
|
|
340
|
+
* Lets a host (e.g. a tab screen) trigger a soft-refresh on focus without
|
|
341
|
+
* blanking the existing list — identical to pull-to-refresh behaviour.
|
|
342
|
+
*
|
|
343
|
+
* ```tsx
|
|
344
|
+
* const reloadRef = useRef<() => void>(null);
|
|
345
|
+
* useFocusEffect(useCallback(() => { reloadRef.current?.(); }, []));
|
|
346
|
+
* <DiscoverScreen reloadRef={reloadRef} ... />
|
|
347
|
+
* ```
|
|
348
|
+
*/
|
|
349
|
+
reloadRef?: React.RefObject<(() => void) | null>;
|
|
337
350
|
}
|
|
338
|
-
declare function DiscoverScreen({ loadEntries, renderIcon, onOpen, title, emptyMessage, emptySearchMessage, searchEnabled, }: DiscoverScreenProps): React.JSX.Element;
|
|
351
|
+
declare function DiscoverScreen({ loadEntries, renderIcon, onOpen, title, emptyMessage, emptySearchMessage, searchEnabled, reloadRef, }: DiscoverScreenProps): React.JSX.Element;
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Shared types for the SpacesRail component.
|
|
355
|
+
*
|
|
356
|
+
* Structurally compatible with `Space` from `@drakkar.software/octochat-sdk` /
|
|
357
|
+
* `@drakkar.software/octospaces-sdk` — apps can pass their domain objects directly
|
|
358
|
+
* without a runtime conversion step.
|
|
359
|
+
*/
|
|
360
|
+
/** A minimal space descriptor for the rail: id + display info + badge state. */
|
|
361
|
+
interface RailSpace {
|
|
362
|
+
/** Unique space identifier. */
|
|
363
|
+
id: string;
|
|
364
|
+
/** Short display name or initials shown as a monogram when no image is available. */
|
|
365
|
+
short: string;
|
|
366
|
+
/** Optional image URI (data URI or URL) rendered as the tile background via
|
|
367
|
+
* `SpacesRailProps.renderTileImage`. */
|
|
368
|
+
image?: string;
|
|
369
|
+
/** Unread-message count shown as a badge overlay. */
|
|
370
|
+
unread?: number;
|
|
371
|
+
/** Whether the space is muted (shows a mute-corner icon when `renderIcon` is provided). */
|
|
372
|
+
muted?: boolean;
|
|
373
|
+
}
|
|
374
|
+
/** Named icon slots injected into the rail via `SpacesRailProps.renderIcon`. */
|
|
375
|
+
type RailIconName = 'dm' | 'lock' | 'mute' | 'add';
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Headless, abstractly-themed vertical spaces rail.
|
|
379
|
+
*
|
|
380
|
+
* The component reads the injected {@link Theme} via `useOctoSpacesTheme()` and
|
|
381
|
+
* delegates all app-specific concerns to props:
|
|
382
|
+
*
|
|
383
|
+
* - Icons are rendered by `renderIcon` (keeps `@expo/vector-icons` out of this package).
|
|
384
|
+
* - Space tile images are rendered by `renderTileImage` (keeps `expo-image` out too).
|
|
385
|
+
* - Unread badges are rendered by `renderBadge`.
|
|
386
|
+
* - The rail foot (account avatar + menu) is rendered by `renderFoot`.
|
|
387
|
+
* - Web drag-reorder is wired via the `useTileDnd` hook prop (see below).
|
|
388
|
+
*
|
|
389
|
+
* All React Native primitives used here ship with the `react-native` peer dep.
|
|
390
|
+
*/
|
|
391
|
+
|
|
392
|
+
interface SpacesRailProps {
|
|
393
|
+
/** The spaces to show in the scrollable column. */
|
|
394
|
+
spaces: RailSpace[];
|
|
395
|
+
/** The currently-active space id (or null / undefined for none). */
|
|
396
|
+
activeId?: string | null;
|
|
397
|
+
/** Called when the user selects a space tile. */
|
|
398
|
+
onSelect?: (id: string) => void;
|
|
399
|
+
/** Called when the user taps the "add" tile. */
|
|
400
|
+
onAdd?: () => void;
|
|
401
|
+
/** When provided, renders a leading DM-home tile. */
|
|
402
|
+
onSelectDms?: () => void;
|
|
403
|
+
/** Whether the DM-home tile is the active selection. */
|
|
404
|
+
dmsActive?: boolean;
|
|
405
|
+
/** Unread count for the DM-home tile badge. */
|
|
406
|
+
dmUnread?: number;
|
|
407
|
+
/** Accessibility label for the DM-home tile (default: "Direct messages"). */
|
|
408
|
+
dmLabel?: string;
|
|
409
|
+
/** Accessibility label for the add-space tile (default: "Create or join a space"). */
|
|
410
|
+
addLabel?: string;
|
|
411
|
+
/**
|
|
412
|
+
* Render a named icon at the given size and color. Used for the DM tile icon
|
|
413
|
+
* (`'dm'`), the E2EE lock corner (`'lock'`), the mute corner (`'mute'`),
|
|
414
|
+
* and the add tile icon (`'add'`). Return `null` to suppress the icon slot.
|
|
415
|
+
* If omitted, all icon slots render nothing.
|
|
416
|
+
*/
|
|
417
|
+
renderIcon?: (name: RailIconName, size: number, color: string) => React.ReactNode;
|
|
418
|
+
/**
|
|
419
|
+
* Render an image filling the tile background. Only called when `space.image`
|
|
420
|
+
* is set. The component must fill its parent (`StyleSheet.absoluteFill` or
|
|
421
|
+
* equivalent). If omitted, the short-name monogram is shown instead.
|
|
422
|
+
*/
|
|
423
|
+
renderTileImage?: (space: RailSpace) => React.ReactNode;
|
|
424
|
+
/**
|
|
425
|
+
* Render an unread badge. Only called when `space.unread > 0`.
|
|
426
|
+
* If omitted, badges are not shown.
|
|
427
|
+
*/
|
|
428
|
+
renderBadge?: (count: number) => React.ReactNode;
|
|
429
|
+
/**
|
|
430
|
+
* When `true`, each tile shows a small E2EE-lock corner badge (bottom-right).
|
|
431
|
+
* Requires `renderIcon` to be provided (otherwise the corner renders nothing).
|
|
432
|
+
* Default: `false`.
|
|
433
|
+
*/
|
|
434
|
+
showLockCorner?: boolean;
|
|
435
|
+
/**
|
|
436
|
+
* Render the pinned rail foot (e.g. the account avatar and popover).
|
|
437
|
+
* The host app owns this entirely — identity state stays out of the package.
|
|
438
|
+
*/
|
|
439
|
+
renderFoot?: () => React.ReactNode;
|
|
440
|
+
/**
|
|
441
|
+
* **Hook injection for web drag-reorder.** When provided, each space tile is
|
|
442
|
+
* wrapped in a `DndTile` that calls `useTileDnd(spaceId)` unconditionally at
|
|
443
|
+
* the top of its render — treat this prop as a React hook and keep it stable
|
|
444
|
+
* for the lifetime of a `SpacesRail` mount (always provided or always absent).
|
|
445
|
+
* Omit on native / in apps that don't need DnD.
|
|
446
|
+
*/
|
|
447
|
+
useTileDnd?: (spaceId: string) => {
|
|
448
|
+
ref?: React.Ref<View>;
|
|
449
|
+
over?: boolean;
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Vertical spaces rail — a 64px-wide column of square space tiles, a DM-home tile,
|
|
454
|
+
* an add-space tile, and a pinned foot for the account widget.
|
|
455
|
+
*
|
|
456
|
+
* Styled entirely from the injected {@link Theme} via `useOctoSpacesTheme()`.
|
|
457
|
+
* All icons, images, badges, and the account foot are provided by the host app.
|
|
458
|
+
*/
|
|
459
|
+
declare function SpacesRail({ spaces, activeId, onSelect, onAdd, onSelectDms, dmsActive, dmUnread, dmLabel, addLabel, renderIcon, renderTileImage, renderBadge, showLockCorner, renderFoot, useTileDnd, }: SpacesRailProps): React.JSX.Element;
|
|
339
460
|
|
|
340
|
-
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 ShadowToken, type Shadows, type Spacing, type Swatches, type Theme, type TypeScale, type Typography, avatarTint, filterDiscoverEntries, focusRingStyle, glowShadow, paperBorder, presenceColor, sortDiscoverEntries, statusColor, swatch, useOctoSpacesTheme, verificationColor };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -237,7 +237,7 @@ function DiscoverList({
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
// src/discover/DiscoverScreen.tsx
|
|
240
|
-
import React4, { useCallback as useCallback3, useEffect, useRef, useState } from "react";
|
|
240
|
+
import React4, { useCallback as useCallback3, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
241
241
|
import { ActivityIndicator, Pressable as Pressable2, Text as Text3, TextInput, View as View3 } from "react-native";
|
|
242
242
|
function DiscoverScreen({
|
|
243
243
|
loadEntries,
|
|
@@ -246,7 +246,8 @@ function DiscoverScreen({
|
|
|
246
246
|
title = "Discover",
|
|
247
247
|
emptyMessage = "No public objects yet",
|
|
248
248
|
emptySearchMessage,
|
|
249
|
-
searchEnabled = true
|
|
249
|
+
searchEnabled = true,
|
|
250
|
+
reloadRef
|
|
250
251
|
}) {
|
|
251
252
|
const theme = useOctoSpacesTheme();
|
|
252
253
|
const [state, setState] = useState({ status: "idle" });
|
|
@@ -278,6 +279,7 @@ function DiscoverScreen({
|
|
|
278
279
|
if (!cancelledRef.current) setRefreshing(false);
|
|
279
280
|
}
|
|
280
281
|
}, [loadEntries]);
|
|
282
|
+
useImperativeHandle(reloadRef, () => handleRefresh, [handleRefresh]);
|
|
281
283
|
useEffect(() => {
|
|
282
284
|
cancelledRef.current = false;
|
|
283
285
|
void load();
|
|
@@ -396,11 +398,442 @@ function DiscoverScreen({
|
|
|
396
398
|
}
|
|
397
399
|
));
|
|
398
400
|
}
|
|
401
|
+
|
|
402
|
+
// src/sidebar/SpacesRail.tsx
|
|
403
|
+
import React5, { useState as useState2 } from "react";
|
|
404
|
+
import {
|
|
405
|
+
Pressable as RNPressable,
|
|
406
|
+
ScrollView,
|
|
407
|
+
Text as Text4,
|
|
408
|
+
View as View4
|
|
409
|
+
} from "react-native";
|
|
410
|
+
|
|
411
|
+
// src/sidebar/tile-state.ts
|
|
412
|
+
function railTileState(state, tokens, radiusActive, radiusDefault) {
|
|
413
|
+
const { active, hovered, over } = state;
|
|
414
|
+
let bg;
|
|
415
|
+
let borderColor;
|
|
416
|
+
let borderWidth;
|
|
417
|
+
let labelColor;
|
|
418
|
+
if (active) {
|
|
419
|
+
bg = tokens.primary;
|
|
420
|
+
borderColor = "transparent";
|
|
421
|
+
borderWidth = 0;
|
|
422
|
+
labelColor = tokens.textOnPrimary;
|
|
423
|
+
} else if (hovered) {
|
|
424
|
+
bg = tokens.primaryMuted;
|
|
425
|
+
borderColor = tokens.railTileHoverBorder;
|
|
426
|
+
borderWidth = 1;
|
|
427
|
+
labelColor = tokens.railTileHoverInk;
|
|
428
|
+
} else {
|
|
429
|
+
bg = tokens.railTile;
|
|
430
|
+
borderColor = tokens.borderSubtle;
|
|
431
|
+
borderWidth = 1;
|
|
432
|
+
labelColor = tokens.textSecondary;
|
|
433
|
+
}
|
|
434
|
+
if (over && !active) {
|
|
435
|
+
borderColor = tokens.primary;
|
|
436
|
+
borderWidth = 1;
|
|
437
|
+
}
|
|
438
|
+
const radius = active || hovered || over ? radiusActive : radiusDefault;
|
|
439
|
+
const shadow = active ? glowShadow(tokens.railGlow, 8, 0.3) : null;
|
|
440
|
+
return { bg, borderColor, borderWidth, radius, labelColor, shadow };
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/sidebar/SpacesRail.tsx
|
|
444
|
+
var Pressable3 = RNPressable;
|
|
445
|
+
function resolveRailTokens(theme) {
|
|
446
|
+
const { colors, swatches } = theme;
|
|
447
|
+
return {
|
|
448
|
+
primary: colors.primary,
|
|
449
|
+
primaryMuted: colors.primaryMuted,
|
|
450
|
+
primarySubtle: colors.primarySubtle,
|
|
451
|
+
surfaceInput: colors.surfaceInput,
|
|
452
|
+
borderSubtle: colors.borderSubtle,
|
|
453
|
+
textOnPrimary: colors.textOnPrimary,
|
|
454
|
+
textSecondary: colors.textSecondary,
|
|
455
|
+
textTertiary: colors.textTertiary,
|
|
456
|
+
railTile: swatches["railTile"] ?? colors.surfaceInput,
|
|
457
|
+
railTileHoverBorder: swatches["railTileHoverBorder"] ?? colors.primarySubtle,
|
|
458
|
+
railGlow: swatches["railGlow"] ?? colors.primary,
|
|
459
|
+
railTileHoverInk: swatches["railTileHoverInk"] ?? colors.primary
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
var TILE_SIZE = 40;
|
|
463
|
+
var CORNER_SIZE = 16;
|
|
464
|
+
var BADGE_OFFSET = -5;
|
|
465
|
+
var CORNER_OFFSET = -3;
|
|
466
|
+
function TileContent({
|
|
467
|
+
space,
|
|
468
|
+
labelColor,
|
|
469
|
+
fontFamily,
|
|
470
|
+
fontSize,
|
|
471
|
+
lineHeight,
|
|
472
|
+
cornerBg,
|
|
473
|
+
cornerBorder,
|
|
474
|
+
cornerIconColor,
|
|
475
|
+
renderIcon,
|
|
476
|
+
renderTileImage,
|
|
477
|
+
renderBadge,
|
|
478
|
+
showLockCorner
|
|
479
|
+
}) {
|
|
480
|
+
return /* @__PURE__ */ React5.createElement(React5.Fragment, null, space.image && renderTileImage ? renderTileImage(space) : /* @__PURE__ */ React5.createElement(
|
|
481
|
+
Text4,
|
|
482
|
+
{
|
|
483
|
+
style: {
|
|
484
|
+
fontSize,
|
|
485
|
+
lineHeight,
|
|
486
|
+
fontWeight: "700",
|
|
487
|
+
fontFamily: fontFamily ?? void 0,
|
|
488
|
+
color: labelColor
|
|
489
|
+
},
|
|
490
|
+
numberOfLines: 1
|
|
491
|
+
},
|
|
492
|
+
space.short
|
|
493
|
+
), showLockCorner && renderIcon ? /* @__PURE__ */ React5.createElement(
|
|
494
|
+
View4,
|
|
495
|
+
{
|
|
496
|
+
style: {
|
|
497
|
+
position: "absolute",
|
|
498
|
+
bottom: CORNER_OFFSET,
|
|
499
|
+
right: CORNER_OFFSET,
|
|
500
|
+
width: CORNER_SIZE,
|
|
501
|
+
height: CORNER_SIZE,
|
|
502
|
+
borderRadius: CORNER_SIZE / 2,
|
|
503
|
+
borderWidth: 1,
|
|
504
|
+
alignItems: "center",
|
|
505
|
+
justifyContent: "center",
|
|
506
|
+
backgroundColor: cornerBg,
|
|
507
|
+
borderColor: cornerBorder
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
renderIcon("lock", 9, cornerIconColor)
|
|
511
|
+
) : null, space.muted && renderIcon ? /* @__PURE__ */ React5.createElement(
|
|
512
|
+
View4,
|
|
513
|
+
{
|
|
514
|
+
style: {
|
|
515
|
+
position: "absolute",
|
|
516
|
+
bottom: CORNER_OFFSET,
|
|
517
|
+
left: CORNER_OFFSET,
|
|
518
|
+
width: CORNER_SIZE,
|
|
519
|
+
height: CORNER_SIZE,
|
|
520
|
+
borderRadius: CORNER_SIZE / 2,
|
|
521
|
+
borderWidth: 1,
|
|
522
|
+
alignItems: "center",
|
|
523
|
+
justifyContent: "center",
|
|
524
|
+
backgroundColor: cornerBg,
|
|
525
|
+
borderColor: cornerBorder
|
|
526
|
+
}
|
|
527
|
+
},
|
|
528
|
+
renderIcon("mute", 9, cornerIconColor)
|
|
529
|
+
) : null, space.unread ? /* @__PURE__ */ React5.createElement(
|
|
530
|
+
View4,
|
|
531
|
+
{
|
|
532
|
+
style: {
|
|
533
|
+
position: "absolute",
|
|
534
|
+
top: BADGE_OFFSET,
|
|
535
|
+
right: BADGE_OFFSET
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
|
+
renderBadge ? renderBadge(space.unread) : null
|
|
539
|
+
) : null);
|
|
540
|
+
}
|
|
541
|
+
function PlainTile({
|
|
542
|
+
space,
|
|
543
|
+
active,
|
|
544
|
+
onPress,
|
|
545
|
+
tokens,
|
|
546
|
+
radiusActive,
|
|
547
|
+
radiusDefault,
|
|
548
|
+
renderIcon,
|
|
549
|
+
renderTileImage,
|
|
550
|
+
renderBadge,
|
|
551
|
+
showLockCorner,
|
|
552
|
+
cornerBg,
|
|
553
|
+
cornerBorder,
|
|
554
|
+
fontFamily,
|
|
555
|
+
fontSize,
|
|
556
|
+
lineHeight
|
|
557
|
+
}) {
|
|
558
|
+
const [hovered, setHovered] = useState2(false);
|
|
559
|
+
const s = railTileState({ active, hovered, over: false }, tokens, radiusActive, radiusDefault);
|
|
560
|
+
return /* @__PURE__ */ React5.createElement(
|
|
561
|
+
Pressable3,
|
|
562
|
+
{
|
|
563
|
+
onPress,
|
|
564
|
+
onMouseEnter: () => setHovered(true),
|
|
565
|
+
onMouseLeave: () => setHovered(false),
|
|
566
|
+
accessibilityRole: "button",
|
|
567
|
+
accessibilityLabel: space.short,
|
|
568
|
+
style: {
|
|
569
|
+
position: "relative",
|
|
570
|
+
width: TILE_SIZE,
|
|
571
|
+
height: TILE_SIZE,
|
|
572
|
+
alignItems: "center",
|
|
573
|
+
justifyContent: "center",
|
|
574
|
+
overflow: "hidden",
|
|
575
|
+
borderRadius: s.radius,
|
|
576
|
+
backgroundColor: s.bg,
|
|
577
|
+
borderWidth: s.borderWidth,
|
|
578
|
+
borderColor: s.borderColor,
|
|
579
|
+
...s.shadow ?? {}
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
/* @__PURE__ */ React5.createElement(
|
|
583
|
+
TileContent,
|
|
584
|
+
{
|
|
585
|
+
space,
|
|
586
|
+
labelColor: s.labelColor,
|
|
587
|
+
fontFamily,
|
|
588
|
+
fontSize,
|
|
589
|
+
lineHeight,
|
|
590
|
+
cornerBg,
|
|
591
|
+
cornerBorder,
|
|
592
|
+
cornerIconColor: tokens.textTertiary,
|
|
593
|
+
renderIcon,
|
|
594
|
+
renderTileImage,
|
|
595
|
+
renderBadge,
|
|
596
|
+
showLockCorner
|
|
597
|
+
}
|
|
598
|
+
)
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
function DndTile({
|
|
602
|
+
space,
|
|
603
|
+
active,
|
|
604
|
+
onPress,
|
|
605
|
+
tokens,
|
|
606
|
+
radiusActive,
|
|
607
|
+
radiusDefault,
|
|
608
|
+
renderIcon,
|
|
609
|
+
renderTileImage,
|
|
610
|
+
renderBadge,
|
|
611
|
+
showLockCorner,
|
|
612
|
+
cornerBg,
|
|
613
|
+
cornerBorder,
|
|
614
|
+
fontFamily,
|
|
615
|
+
fontSize,
|
|
616
|
+
lineHeight,
|
|
617
|
+
dnd
|
|
618
|
+
}) {
|
|
619
|
+
const [hovered, setHovered] = useState2(false);
|
|
620
|
+
const { ref, over = false } = dnd(space.id);
|
|
621
|
+
const s = railTileState({ active, hovered, over }, tokens, radiusActive, radiusDefault);
|
|
622
|
+
return /* @__PURE__ */ React5.createElement(
|
|
623
|
+
Pressable3,
|
|
624
|
+
{
|
|
625
|
+
ref,
|
|
626
|
+
onPress,
|
|
627
|
+
onMouseEnter: () => setHovered(true),
|
|
628
|
+
onMouseLeave: () => setHovered(false),
|
|
629
|
+
accessibilityRole: "button",
|
|
630
|
+
accessibilityLabel: space.short,
|
|
631
|
+
style: {
|
|
632
|
+
position: "relative",
|
|
633
|
+
width: TILE_SIZE,
|
|
634
|
+
height: TILE_SIZE,
|
|
635
|
+
alignItems: "center",
|
|
636
|
+
justifyContent: "center",
|
|
637
|
+
overflow: "hidden",
|
|
638
|
+
borderRadius: s.radius,
|
|
639
|
+
backgroundColor: s.bg,
|
|
640
|
+
borderWidth: s.borderWidth,
|
|
641
|
+
borderColor: s.borderColor,
|
|
642
|
+
...s.shadow ?? {}
|
|
643
|
+
}
|
|
644
|
+
},
|
|
645
|
+
/* @__PURE__ */ React5.createElement(
|
|
646
|
+
TileContent,
|
|
647
|
+
{
|
|
648
|
+
space,
|
|
649
|
+
labelColor: s.labelColor,
|
|
650
|
+
fontFamily,
|
|
651
|
+
fontSize,
|
|
652
|
+
lineHeight,
|
|
653
|
+
cornerBg,
|
|
654
|
+
cornerBorder,
|
|
655
|
+
cornerIconColor: tokens.textTertiary,
|
|
656
|
+
renderIcon,
|
|
657
|
+
renderTileImage,
|
|
658
|
+
renderBadge,
|
|
659
|
+
showLockCorner
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
function SpacesRail({
|
|
665
|
+
spaces,
|
|
666
|
+
activeId,
|
|
667
|
+
onSelect,
|
|
668
|
+
onAdd,
|
|
669
|
+
onSelectDms,
|
|
670
|
+
dmsActive = false,
|
|
671
|
+
dmUnread,
|
|
672
|
+
dmLabel = "Direct messages",
|
|
673
|
+
addLabel = "Create or join a space",
|
|
674
|
+
renderIcon,
|
|
675
|
+
renderTileImage,
|
|
676
|
+
renderBadge,
|
|
677
|
+
showLockCorner = false,
|
|
678
|
+
renderFoot,
|
|
679
|
+
useTileDnd
|
|
680
|
+
}) {
|
|
681
|
+
const theme = useOctoSpacesTheme();
|
|
682
|
+
const { colors, spacing, radii, type: typeScale, fonts, layout } = theme;
|
|
683
|
+
const tokens = resolveRailTokens(theme);
|
|
684
|
+
const railWidth = layout["railWidth"] ?? 64;
|
|
685
|
+
const spaceV = spacing["2"] ?? 8;
|
|
686
|
+
const spaceXs = spacing["1"] ?? 4;
|
|
687
|
+
const spaceS = spacing["2"] ?? 8;
|
|
688
|
+
const spaceMd = spacing["3"] ?? 12;
|
|
689
|
+
const radiusActive = radii["lg"] ?? 12;
|
|
690
|
+
const radiusDefault = radii["xl"] ?? 16;
|
|
691
|
+
const footnoteSize = typeScale["footnote"]?.size ?? 12;
|
|
692
|
+
const footnoteLineH = typeScale["footnote"]?.lineHeight ?? 18;
|
|
693
|
+
const monoFont = fonts["mono"] ?? void 0;
|
|
694
|
+
const cornerBg = colors.sidebar;
|
|
695
|
+
const cornerBorder = colors.border;
|
|
696
|
+
const tileShared = {
|
|
697
|
+
tokens,
|
|
698
|
+
radiusActive,
|
|
699
|
+
radiusDefault,
|
|
700
|
+
renderIcon,
|
|
701
|
+
renderTileImage,
|
|
702
|
+
renderBadge,
|
|
703
|
+
showLockCorner,
|
|
704
|
+
cornerBg,
|
|
705
|
+
cornerBorder,
|
|
706
|
+
fontFamily: monoFont,
|
|
707
|
+
fontSize: footnoteSize,
|
|
708
|
+
lineHeight: footnoteLineH
|
|
709
|
+
};
|
|
710
|
+
const [dmHovered, setDmHovered] = useState2(false);
|
|
711
|
+
const dmTileStyle = railTileState(
|
|
712
|
+
{ active: dmsActive, hovered: dmHovered, over: false },
|
|
713
|
+
tokens,
|
|
714
|
+
radiusActive,
|
|
715
|
+
radiusDefault
|
|
716
|
+
);
|
|
717
|
+
const dmIconColor = dmsActive ? colors.textOnPrimary : dmHovered ? tokens.railTileHoverInk : colors.textSecondary;
|
|
718
|
+
const [addHovered, setAddHovered] = useState2(false);
|
|
719
|
+
const hasDnd = !!useTileDnd;
|
|
720
|
+
return /* @__PURE__ */ React5.createElement(
|
|
721
|
+
View4,
|
|
722
|
+
{
|
|
723
|
+
style: {
|
|
724
|
+
width: railWidth,
|
|
725
|
+
paddingVertical: spaceMd,
|
|
726
|
+
borderRightWidth: 1,
|
|
727
|
+
borderRightColor: colors.border,
|
|
728
|
+
backgroundColor: colors.sidebar,
|
|
729
|
+
alignItems: "center",
|
|
730
|
+
gap: spaceS
|
|
731
|
+
}
|
|
732
|
+
},
|
|
733
|
+
/* @__PURE__ */ React5.createElement(
|
|
734
|
+
ScrollView,
|
|
735
|
+
{
|
|
736
|
+
style: { alignSelf: "stretch", flex: 1 },
|
|
737
|
+
contentContainerStyle: {
|
|
738
|
+
alignItems: "center",
|
|
739
|
+
gap: spaceV,
|
|
740
|
+
paddingVertical: spaceXs
|
|
741
|
+
},
|
|
742
|
+
showsVerticalScrollIndicator: false
|
|
743
|
+
},
|
|
744
|
+
onSelectDms ? /* @__PURE__ */ React5.createElement(View4, { style: { position: "relative" } }, /* @__PURE__ */ React5.createElement(
|
|
745
|
+
Pressable3,
|
|
746
|
+
{
|
|
747
|
+
onPress: onSelectDms,
|
|
748
|
+
onMouseEnter: () => setDmHovered(true),
|
|
749
|
+
onMouseLeave: () => setDmHovered(false),
|
|
750
|
+
accessibilityRole: "button",
|
|
751
|
+
accessibilityLabel: dmLabel,
|
|
752
|
+
style: {
|
|
753
|
+
width: TILE_SIZE,
|
|
754
|
+
height: TILE_SIZE,
|
|
755
|
+
alignItems: "center",
|
|
756
|
+
justifyContent: "center",
|
|
757
|
+
borderRadius: dmTileStyle.radius,
|
|
758
|
+
backgroundColor: dmTileStyle.bg,
|
|
759
|
+
borderWidth: dmTileStyle.borderWidth,
|
|
760
|
+
borderColor: dmTileStyle.borderColor,
|
|
761
|
+
...dmTileStyle.shadow ?? {}
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
renderIcon ? renderIcon("dm", 20, dmIconColor) : null
|
|
765
|
+
), showLockCorner && renderIcon ? /* @__PURE__ */ React5.createElement(
|
|
766
|
+
View4,
|
|
767
|
+
{
|
|
768
|
+
style: {
|
|
769
|
+
position: "absolute",
|
|
770
|
+
bottom: CORNER_OFFSET,
|
|
771
|
+
right: CORNER_OFFSET,
|
|
772
|
+
width: CORNER_SIZE,
|
|
773
|
+
height: CORNER_SIZE,
|
|
774
|
+
borderRadius: CORNER_SIZE / 2,
|
|
775
|
+
borderWidth: 1,
|
|
776
|
+
alignItems: "center",
|
|
777
|
+
justifyContent: "center",
|
|
778
|
+
backgroundColor: cornerBg,
|
|
779
|
+
borderColor: cornerBorder
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
renderIcon("lock", 9, tokens.textTertiary)
|
|
783
|
+
) : null, dmUnread ? /* @__PURE__ */ React5.createElement(View4, { style: { position: "absolute", top: BADGE_OFFSET, right: BADGE_OFFSET } }, renderBadge ? renderBadge(dmUnread) : null) : null) : null,
|
|
784
|
+
spaces.map(
|
|
785
|
+
(s) => hasDnd ? /* @__PURE__ */ React5.createElement(
|
|
786
|
+
DndTile,
|
|
787
|
+
{
|
|
788
|
+
key: s.id,
|
|
789
|
+
space: s,
|
|
790
|
+
active: s.id === activeId,
|
|
791
|
+
onPress: () => onSelect?.(s.id),
|
|
792
|
+
dnd: useTileDnd,
|
|
793
|
+
...tileShared
|
|
794
|
+
}
|
|
795
|
+
) : /* @__PURE__ */ React5.createElement(
|
|
796
|
+
PlainTile,
|
|
797
|
+
{
|
|
798
|
+
key: s.id,
|
|
799
|
+
space: s,
|
|
800
|
+
active: s.id === activeId,
|
|
801
|
+
onPress: () => onSelect?.(s.id),
|
|
802
|
+
...tileShared
|
|
803
|
+
}
|
|
804
|
+
)
|
|
805
|
+
),
|
|
806
|
+
/* @__PURE__ */ React5.createElement(
|
|
807
|
+
Pressable3,
|
|
808
|
+
{
|
|
809
|
+
onPress: onAdd,
|
|
810
|
+
onMouseEnter: () => setAddHovered(true),
|
|
811
|
+
onMouseLeave: () => setAddHovered(false),
|
|
812
|
+
accessibilityRole: "button",
|
|
813
|
+
accessibilityLabel: addLabel,
|
|
814
|
+
style: {
|
|
815
|
+
width: TILE_SIZE,
|
|
816
|
+
height: TILE_SIZE,
|
|
817
|
+
alignItems: "center",
|
|
818
|
+
justifyContent: "center",
|
|
819
|
+
borderRadius: radiusDefault,
|
|
820
|
+
borderWidth: 1,
|
|
821
|
+
borderStyle: "dashed",
|
|
822
|
+
borderColor: addHovered ? colors.border : colors.borderSubtle
|
|
823
|
+
}
|
|
824
|
+
},
|
|
825
|
+
renderIcon ? renderIcon("add", 16, addHovered ? tokens.railTileHoverInk : colors.textTertiary) : null
|
|
826
|
+
)
|
|
827
|
+
),
|
|
828
|
+
renderFoot ? renderFoot() : null
|
|
829
|
+
);
|
|
830
|
+
}
|
|
399
831
|
export {
|
|
400
832
|
DiscoverList,
|
|
401
833
|
DiscoverRow,
|
|
402
834
|
DiscoverScreen,
|
|
403
835
|
OctoSpacesThemeProvider,
|
|
836
|
+
SpacesRail,
|
|
404
837
|
avatarTint,
|
|
405
838
|
filterDiscoverEntries,
|
|
406
839
|
focusRingStyle,
|