@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.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/theme/provider.tsx","../src/theme/helpers.ts","../src/discover/filter.ts","../src/discover/DiscoverRow.tsx","../src/discover/DiscoverList.tsx","../src/discover/DiscoverScreen.tsx"],"sourcesContent":["/**\n * Theme injection plumbing — provider + hook.\n *\n * The package carries ZERO theme values. The host app builds a concrete {@link Theme}\n * and wraps its tree in `<OctoSpacesThemeProvider theme={resolvedTheme}>`.\n * All primitives then call `useOctoSpacesTheme()` to read the active theme.\n */\nimport React, { createContext, useContext } from 'react';\nimport type { Theme } from './types.js';\n\nconst ThemeContext = createContext<Theme | null>(null);\n\nexport interface OctoSpacesThemeProviderProps {\n theme: Theme;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your root component with this provider to inject the resolved Theme into\n * every primitive from `@drakkar.software/octospaces-ui`.\n *\n * @example\n * ```tsx\n * import { OctoSpacesThemeProvider } from '@drakkar.software/octospaces-ui';\n * import { resolvedTheme } from '@/theme'; // your app's theme\n *\n * export default function App() {\n * return (\n * <OctoSpacesThemeProvider theme={resolvedTheme}>\n * <RootNavigator />\n * </OctoSpacesThemeProvider>\n * );\n * }\n * ```\n */\nexport function OctoSpacesThemeProvider({ theme, children }: OctoSpacesThemeProviderProps) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read the active theme. Throws if called outside an `<OctoSpacesThemeProvider>` —\n * this is intentional: a missing provider means primitives have no colors/spacing,\n * so a hard failure with a clear message is better than a silent rendering bug.\n */\nexport function useOctoSpacesTheme(): Theme {\n const theme = useContext(ThemeContext);\n if (!theme) {\n throw new Error(\n '[octospaces-ui] useOctoSpacesTheme() called outside of <OctoSpacesThemeProvider>. ' +\n 'Wrap your root component with <OctoSpacesThemeProvider theme={…}>.',\n );\n }\n return theme;\n}\n","/**\n * Pure palette-helper functions over a {@link Palette}. No theme values live here —\n * only functions that DERIVE from the injected palette (or from passed-in colors).\n *\n * Import these from `@drakkar.software/octospaces-ui` (re-exported by `src/index.ts`).\n */\nimport type { Palette, ShadowToken, Theme } from './types.js';\n\n// ── Presence ──────────────────────────────────────────────────────────────────\n\n/** Map a presence status string to the corresponding palette color. */\nexport function presenceColor(\n palette: Palette,\n status: 'online' | 'away' | 'busy' | 'offline' | string,\n): string {\n switch (status) {\n case 'online': return palette.presenceOnline;\n case 'away': return palette.presenceAway;\n case 'busy': return palette.presenceBusy;\n default: return palette.presenceOffline;\n }\n}\n\n// ── Verification ──────────────────────────────────────────────────────────────\n\n/** Map a verification level to the corresponding palette color. */\nexport function verificationColor(\n palette: Palette,\n level: 'verified' | 'partial' | 'none' | string,\n): string {\n switch (level) {\n case 'verified': return palette.verificationVerified;\n case 'partial': return palette.verificationPartial;\n default: return palette.verificationNone;\n }\n}\n\n// ── Avatar ────────────────────────────────────────────────────────────────────\n\nconst AVATAR_TINT_KEYS = [\n 'primary', 'success', 'warning', 'danger', 'info',\n] as const;\n\n/** Stable avatar background tint derived from a userId string. */\nexport function avatarTint(palette: Palette, userId: string): string {\n let hash = 0;\n for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) | 0;\n const key = AVATAR_TINT_KEYS[Math.abs(hash) % AVATAR_TINT_KEYS.length];\n return (palette as unknown as Record<string, string>)[key] ?? palette.primary;\n}\n\n// ── Swatch ────────────────────────────────────────────────────────────────────\n\n/** Look up a named swatch; falls back to `palette.primary` if absent. */\nexport function swatch(theme: Theme, name: string): string {\n return theme.swatches[name] ?? theme.colors.primary;\n}\n\n// ── Borders ───────────────────────────────────────────────────────────────────\n\n/** Derive a `borderColor` value for a \"paper\" (elevated surface) border. */\nexport function paperBorder(palette: Palette): string {\n return palette.borderSubtle;\n}\n\n// ── Shadows ───────────────────────────────────────────────────────────────────\n\n/** Build a glow shadow token from a base color (used for focus rings, highlights). */\nexport function glowShadow(color: string, radius = 8, opacity = 0.4): ShadowToken {\n return {\n shadowColor: color,\n shadowOffset: { width: 0, height: 0 },\n shadowOpacity: opacity,\n shadowRadius: radius,\n elevation: 4,\n };\n}\n\n// ── Focus ring ────────────────────────────────────────────────────────────────\n\n/** Style object for a keyboard-focus indicator (web + React Native). */\nexport function focusRingStyle(\n palette: Palette,\n width = 2,\n): {\n borderWidth: number;\n borderColor: string;\n borderStyle: 'solid';\n} {\n return { borderWidth: width, borderColor: palette.focus, borderStyle: 'solid' };\n}\n\n// ── Status color ──────────────────────────────────────────────────────────────\n\n/** Map a semantic status name to its palette color. */\nexport function statusColor(\n palette: Palette,\n status: 'success' | 'warning' | 'danger' | 'info' | string,\n muted = false,\n): string {\n if (muted) {\n switch (status) {\n case 'success': return palette.successMuted;\n case 'warning': return palette.warningMuted;\n case 'danger': return palette.dangerMuted;\n default: return palette.infoMuted;\n }\n }\n switch (status) {\n case 'success': return palette.success;\n case 'warning': return palette.warning;\n case 'danger': return palette.danger;\n default: return palette.info;\n }\n}\n","import type { DiscoverEntry } from './types.js';\n\n/**\n * Case-insensitive substring filter over a `DiscoverEntry[]`.\n *\n * Returns the original array reference unchanged when `query` is blank so the\n * caller can skip a re-render. Pure function — no side effects.\n */\nexport function filterDiscoverEntries(\n entries: DiscoverEntry[],\n query: string,\n): DiscoverEntry[] {\n const q = query.trim().toLowerCase();\n if (!q) return entries;\n return entries.filter((e) => e.title.toLowerCase().includes(q));\n}\n\n/**\n * Sort discover entries by updatedAt descending (most recent first).\n * Entries without an `updatedAt` field sort last. Pure function.\n */\nexport function sortDiscoverEntries(entries: DiscoverEntry[]): DiscoverEntry[] {\n return [...entries].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));\n}\n","/**\n * A single row in the Discover list — shows the object's emoji/icon, title,\n * and type. All app-specific behaviour is injected via props:\n * - `renderIcon` — render a type/emoji icon; receives the entry and must\n * return a ReactNode (null for no icon).\n * - `onOpen` — called when the row is pressed.\n *\n * Styled entirely from the injected {@link Theme} via `useOctoSpacesTheme()`.\n */\nimport React, { useCallback } from 'react';\nimport { Pressable, Text, View } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverRowProps {\n entry: DiscoverEntry;\n /** Render a leading icon for the entry. Return `null` to show nothing. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when the user taps the row. */\n onOpen: (entry: DiscoverEntry) => void;\n}\n\nexport function DiscoverRow({ entry, renderIcon, onOpen }: DiscoverRowProps) {\n const theme = useOctoSpacesTheme();\n\n const handlePress = useCallback(() => {\n onOpen(entry);\n }, [entry, onOpen]);\n\n const icon = renderIcon ? renderIcon(entry) : null;\n const displayEmoji = !icon && entry.emoji ? entry.emoji : null;\n\n return (\n <Pressable\n onPress={handlePress}\n style={({ pressed }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: (theme.spacing['3'] as number) ?? 12,\n paddingHorizontal: (theme.spacing['4'] as number) ?? 16,\n backgroundColor: pressed\n ? (theme.colors.surface ?? '#f5f5f5')\n : 'transparent',\n borderRadius: (theme.radii['sm'] as number) ?? 6,\n })}\n accessibilityRole=\"button\"\n accessibilityLabel={entry.title || 'Untitled'}\n >\n {/* Leading icon / emoji */}\n {(icon || displayEmoji) && (\n <View\n style={{\n width: 28,\n height: 28,\n marginRight: (theme.spacing['2'] as number) ?? 8,\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n }}\n >\n {icon ?? (\n <Text style={{ fontSize: 18, lineHeight: 24 }}>{displayEmoji}</Text>\n )}\n </View>\n )}\n\n {/* Title + type subtitle */}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text\n numberOfLines={1}\n style={{\n fontSize: (theme.type['body']?.size ?? 15),\n lineHeight: (theme.type['body']?.lineHeight ?? 22),\n color: entry.title ? theme.colors.text : theme.colors.textTertiary,\n fontFamily: theme.fonts['body'] ?? undefined,\n }}\n >\n {entry.title || 'Untitled'}\n </Text>\n <Text\n numberOfLines={1}\n style={{\n fontSize: (theme.type['caption']?.size ?? 12),\n lineHeight: (theme.type['caption']?.lineHeight ?? 18),\n color: theme.colors.textSecondary,\n marginTop: 1,\n textTransform: 'capitalize',\n }}\n >\n {entry.type}\n </Text>\n </View>\n </Pressable>\n );\n}\n","/**\n * A themed FlatList wrapper that renders a list of {@link DiscoverEntry} rows.\n *\n * All app-specific behaviour is injected via props so this component has zero\n * imports from any specific OctoSpaces app.\n */\nimport React, { useCallback } from 'react';\nimport { FlatList, RefreshControl, Text, View } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport { DiscoverRow } from './DiscoverRow.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverListProps {\n entries: DiscoverEntry[];\n /** Render a leading icon for each row — see {@link DiscoverRowProps.renderIcon}. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when a row is tapped. */\n onOpen: (entry: DiscoverEntry) => void;\n /** Text shown when `entries` is empty (default: \"No public objects found\"). */\n emptyMessage?: string;\n /** Whether a pull-to-refresh is currently in progress. */\n refreshing?: boolean;\n /** Called when the user pulls to refresh. */\n onRefresh?: () => void;\n}\n\nexport function DiscoverList({\n entries,\n renderIcon,\n onOpen,\n emptyMessage = 'No public objects found',\n refreshing,\n onRefresh,\n}: DiscoverListProps) {\n const theme = useOctoSpacesTheme();\n\n const renderItem = useCallback(\n ({ item }: { item: DiscoverEntry }) => (\n <DiscoverRow entry={item} renderIcon={renderIcon} onOpen={onOpen} />\n ),\n [renderIcon, onOpen],\n );\n\n const keyExtractor = useCallback(\n (item: DiscoverEntry) => `${item.spaceId}:${item.id}`,\n [],\n );\n\n if (entries.length === 0) {\n return (\n <View\n style={{\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: (theme.spacing['6'] as number) ?? 24,\n }}\n >\n <Text\n style={{\n fontSize: theme.type['body']?.size ?? 15,\n color: theme.colors.textSecondary,\n textAlign: 'center',\n }}\n >\n {emptyMessage}\n </Text>\n </View>\n );\n }\n\n return (\n <FlatList\n data={entries}\n renderItem={renderItem}\n keyExtractor={keyExtractor}\n contentContainerStyle={{ paddingVertical: (theme.spacing['1'] as number) ?? 4 }}\n showsVerticalScrollIndicator={false}\n removeClippedSubviews\n refreshControl={\n onRefresh ? (\n <RefreshControl\n refreshing={refreshing ?? false}\n onRefresh={onRefresh}\n tintColor={theme.colors.primary}\n />\n ) : undefined\n }\n />\n );\n}\n","/**\n * Generic public-object discovery screen.\n *\n * Loads the world-readable public-object directory via `loadEntries`, renders a\n * search bar, and delegates row rendering + tap behaviour to the injected props.\n * No app-specific logic lives here — all customisation is via props:\n *\n * ```tsx\n * <DiscoverScreen\n * loadEntries={readObjectDirectory}\n * renderIcon={(e) => <TypeIcon entry={e} />}\n * onOpen={(e) => router.push({ pathname: routeForNode(e), params: { id: e.id, spaceId: e.spaceId } })}\n * />\n * ```\n *\n * State machine:\n * idle → loading → (ready | error)\n * Any pull of `loadEntries` updates the entries; errors show a retry button.\n */\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport { ActivityIndicator, Pressable, Text, TextInput, View, type TextStyle } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport { DiscoverList } from './DiscoverList.js';\nimport { filterDiscoverEntries, sortDiscoverEntries } from './filter.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverScreenProps {\n /**\n * Async function that resolves to the current public-object directory.\n * Typically `readObjectDirectory` from `@drakkar.software/octospaces-sdk`.\n * Called on mount and when `refresh()` is triggered.\n */\n loadEntries: () => Promise<DiscoverEntry[]>;\n /** Render a leading icon for each row. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when the user taps a row — navigate to the object. */\n onOpen: (entry: DiscoverEntry) => void;\n /**\n * Optional heading text shown above the search bar.\n * @default \"Discover\"\n */\n title?: string;\n /**\n * Text shown when the directory is empty after loading.\n * @default \"No public objects yet\"\n */\n emptyMessage?: string;\n /**\n * Text shown when the directory is empty due to an active search query.\n * @default \"No results for «query»\"\n */\n emptySearchMessage?: string;\n /**\n * Whether to show the inline search bar.\n * @default true\n */\n searchEnabled?: boolean;\n}\n\ntype State =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'ready'; entries: DiscoverEntry[] }\n | { status: 'error'; message: string };\n\nexport function DiscoverScreen({\n loadEntries,\n renderIcon,\n onOpen,\n title = 'Discover',\n emptyMessage = 'No public objects yet',\n emptySearchMessage,\n searchEnabled = true,\n}: DiscoverScreenProps) {\n const theme = useOctoSpacesTheme();\n const [state, setState] = useState<State>({ status: 'idle' });\n const [query, setQuery] = useState('');\n const [refreshing, setRefreshing] = useState(false);\n const cancelledRef = useRef(false);\n\n const load = useCallback(async () => {\n setState({ status: 'loading' });\n try {\n const raw = await loadEntries();\n if (cancelledRef.current) return;\n setState({ status: 'ready', entries: sortDiscoverEntries(raw) });\n } catch (err) {\n if (cancelledRef.current) return;\n setState({\n status: 'error',\n message: err instanceof Error ? err.message : 'Failed to load directory',\n });\n }\n }, [loadEntries]);\n\n /** Pull-to-refresh: re-fetches without blanking the existing list. */\n const handleRefresh = useCallback(async () => {\n setRefreshing(true);\n try {\n const raw = await loadEntries();\n if (cancelledRef.current) return;\n setState({ status: 'ready', entries: sortDiscoverEntries(raw) });\n } catch {\n // keep the existing list on refresh failure; the retry button remains for error state\n } finally {\n if (!cancelledRef.current) setRefreshing(false);\n }\n }, [loadEntries]);\n\n useEffect(() => {\n cancelledRef.current = false;\n void load();\n return () => {\n cancelledRef.current = true;\n };\n }, [load]);\n\n // ── Derived list ─────────────────────────────────────────────────────────\n const allEntries = state.status === 'ready' ? state.entries : [];\n const visibleEntries = filterDiscoverEntries(allEntries, query);\n const noSearchResults = !!query.trim() && visibleEntries.length === 0 && allEntries.length > 0;\n const resolvedEmptyMessage = noSearchResults\n ? (emptySearchMessage ?? `No results for \"${query.trim()}\"`)\n : emptyMessage;\n\n // ── Palette shortcuts ─────────────────────────────────────────────────────\n const sp2 = (theme.spacing['2'] as number) ?? 8;\n const sp3 = (theme.spacing['3'] as number) ?? 12;\n const sp4 = (theme.spacing['4'] as number) ?? 16;\n const radMd = (theme.radii['md'] as number) ?? 8;\n\n return (\n <View style={{ flex: 1, backgroundColor: theme.colors.background }}>\n {/* Header */}\n <View\n style={{\n paddingHorizontal: sp4,\n paddingTop: sp4,\n paddingBottom: sp2,\n }}\n >\n <Text\n style={{\n fontSize: theme.type['title2']?.size ?? 22,\n fontWeight: (theme.type['title2']?.weight as TextStyle['fontWeight']) ?? '700',\n lineHeight: theme.type['title2']?.lineHeight ?? 28,\n color: theme.colors.text,\n fontFamily: theme.fonts['heading'] ?? undefined,\n marginBottom: sp3,\n }}\n >\n {title}\n </Text>\n\n {/* Search bar */}\n {searchEnabled && (\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n backgroundColor: theme.colors.surfaceInput ?? theme.colors.surface,\n borderRadius: radMd,\n borderWidth: 1,\n borderColor: theme.colors.borderSubtle,\n paddingHorizontal: sp3,\n height: 40,\n }}\n >\n <TextInput\n placeholder=\"Search…\"\n placeholderTextColor={theme.colors.textTertiary}\n value={query}\n onChangeText={setQuery}\n style={{\n flex: 1,\n fontSize: theme.type['body']?.size ?? 15,\n color: theme.colors.text,\n fontFamily: theme.fonts['body'] ?? undefined,\n }}\n returnKeyType=\"search\"\n clearButtonMode=\"while-editing\"\n accessibilityLabel=\"Search discover\"\n />\n </View>\n )}\n </View>\n\n {/* Body */}\n {state.status === 'loading' ? (\n <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>\n <ActivityIndicator color={theme.colors.primary} />\n </View>\n ) : state.status === 'error' ? (\n <View\n style={{\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: sp4,\n }}\n >\n <Text\n style={{\n color: theme.colors.textSecondary,\n fontSize: theme.type['body']?.size ?? 15,\n textAlign: 'center',\n marginBottom: sp3,\n }}\n >\n {state.message}\n </Text>\n <Pressable\n onPress={load}\n style={{\n paddingHorizontal: sp4,\n paddingVertical: sp2,\n backgroundColor: theme.colors.primary,\n borderRadius: radMd,\n }}\n >\n <Text style={{ color: theme.colors.textOnPrimary, fontWeight: '600' }}>\n Retry\n </Text>\n </Pressable>\n </View>\n ) : (\n <DiscoverList\n entries={visibleEntries}\n renderIcon={renderIcon}\n onOpen={onOpen}\n emptyMessage={resolvedEmptyMessage}\n refreshing={refreshing}\n onRefresh={handleRefresh}\n />\n )}\n </View>\n );\n}\n"],"mappings":"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","names":["React","React","React","useCallback","Text","View","useCallback","React","View","Text","React","useCallback","Pressable","Text","View","useCallback","React","View","Text","Pressable"]}
|
|
1
|
+
{"version":3,"sources":["../src/theme/provider.tsx","../src/theme/helpers.ts","../src/discover/filter.ts","../src/discover/DiscoverRow.tsx","../src/discover/DiscoverList.tsx","../src/discover/DiscoverScreen.tsx","../src/sidebar/SpacesRail.tsx","../src/sidebar/tile-state.ts"],"sourcesContent":["/**\n * Theme injection plumbing — provider + hook.\n *\n * The package carries ZERO theme values. The host app builds a concrete {@link Theme}\n * and wraps its tree in `<OctoSpacesThemeProvider theme={resolvedTheme}>`.\n * All primitives then call `useOctoSpacesTheme()` to read the active theme.\n */\nimport React, { createContext, useContext } from 'react';\nimport type { Theme } from './types.js';\n\nconst ThemeContext = createContext<Theme | null>(null);\n\nexport interface OctoSpacesThemeProviderProps {\n theme: Theme;\n children: React.ReactNode;\n}\n\n/**\n * Wrap your root component with this provider to inject the resolved Theme into\n * every primitive from `@drakkar.software/octospaces-ui`.\n *\n * @example\n * ```tsx\n * import { OctoSpacesThemeProvider } from '@drakkar.software/octospaces-ui';\n * import { resolvedTheme } from '@/theme'; // your app's theme\n *\n * export default function App() {\n * return (\n * <OctoSpacesThemeProvider theme={resolvedTheme}>\n * <RootNavigator />\n * </OctoSpacesThemeProvider>\n * );\n * }\n * ```\n */\nexport function OctoSpacesThemeProvider({ theme, children }: OctoSpacesThemeProviderProps) {\n return <ThemeContext.Provider value={theme}>{children}</ThemeContext.Provider>;\n}\n\n/**\n * Read the active theme. Throws if called outside an `<OctoSpacesThemeProvider>` —\n * this is intentional: a missing provider means primitives have no colors/spacing,\n * so a hard failure with a clear message is better than a silent rendering bug.\n */\nexport function useOctoSpacesTheme(): Theme {\n const theme = useContext(ThemeContext);\n if (!theme) {\n throw new Error(\n '[octospaces-ui] useOctoSpacesTheme() called outside of <OctoSpacesThemeProvider>. ' +\n 'Wrap your root component with <OctoSpacesThemeProvider theme={…}>.',\n );\n }\n return theme;\n}\n","/**\n * Pure palette-helper functions over a {@link Palette}. No theme values live here —\n * only functions that DERIVE from the injected palette (or from passed-in colors).\n *\n * Import these from `@drakkar.software/octospaces-ui` (re-exported by `src/index.ts`).\n */\nimport type { Palette, ShadowToken, Theme } from './types.js';\n\n// ── Presence ──────────────────────────────────────────────────────────────────\n\n/** Map a presence status string to the corresponding palette color. */\nexport function presenceColor(\n palette: Palette,\n status: 'online' | 'away' | 'busy' | 'offline' | string,\n): string {\n switch (status) {\n case 'online': return palette.presenceOnline;\n case 'away': return palette.presenceAway;\n case 'busy': return palette.presenceBusy;\n default: return palette.presenceOffline;\n }\n}\n\n// ── Verification ──────────────────────────────────────────────────────────────\n\n/** Map a verification level to the corresponding palette color. */\nexport function verificationColor(\n palette: Palette,\n level: 'verified' | 'partial' | 'none' | string,\n): string {\n switch (level) {\n case 'verified': return palette.verificationVerified;\n case 'partial': return palette.verificationPartial;\n default: return palette.verificationNone;\n }\n}\n\n// ── Avatar ────────────────────────────────────────────────────────────────────\n\nconst AVATAR_TINT_KEYS = [\n 'primary', 'success', 'warning', 'danger', 'info',\n] as const;\n\n/** Stable avatar background tint derived from a userId string. */\nexport function avatarTint(palette: Palette, userId: string): string {\n let hash = 0;\n for (let i = 0; i < userId.length; i++) hash = (hash * 31 + userId.charCodeAt(i)) | 0;\n const key = AVATAR_TINT_KEYS[Math.abs(hash) % AVATAR_TINT_KEYS.length];\n return (palette as unknown as Record<string, string>)[key] ?? palette.primary;\n}\n\n// ── Swatch ────────────────────────────────────────────────────────────────────\n\n/** Look up a named swatch; falls back to `palette.primary` if absent. */\nexport function swatch(theme: Theme, name: string): string {\n return theme.swatches[name] ?? theme.colors.primary;\n}\n\n// ── Borders ───────────────────────────────────────────────────────────────────\n\n/** Derive a `borderColor` value for a \"paper\" (elevated surface) border. */\nexport function paperBorder(palette: Palette): string {\n return palette.borderSubtle;\n}\n\n// ── Shadows ───────────────────────────────────────────────────────────────────\n\n/** Build a glow shadow token from a base color (used for focus rings, highlights). */\nexport function glowShadow(color: string, radius = 8, opacity = 0.4): ShadowToken {\n return {\n shadowColor: color,\n shadowOffset: { width: 0, height: 0 },\n shadowOpacity: opacity,\n shadowRadius: radius,\n elevation: 4,\n };\n}\n\n// ── Focus ring ────────────────────────────────────────────────────────────────\n\n/** Style object for a keyboard-focus indicator (web + React Native). */\nexport function focusRingStyle(\n palette: Palette,\n width = 2,\n): {\n borderWidth: number;\n borderColor: string;\n borderStyle: 'solid';\n} {\n return { borderWidth: width, borderColor: palette.focus, borderStyle: 'solid' };\n}\n\n// ── Status color ──────────────────────────────────────────────────────────────\n\n/** Map a semantic status name to its palette color. */\nexport function statusColor(\n palette: Palette,\n status: 'success' | 'warning' | 'danger' | 'info' | string,\n muted = false,\n): string {\n if (muted) {\n switch (status) {\n case 'success': return palette.successMuted;\n case 'warning': return palette.warningMuted;\n case 'danger': return palette.dangerMuted;\n default: return palette.infoMuted;\n }\n }\n switch (status) {\n case 'success': return palette.success;\n case 'warning': return palette.warning;\n case 'danger': return palette.danger;\n default: return palette.info;\n }\n}\n","import type { DiscoverEntry } from './types.js';\n\n/**\n * Case-insensitive substring filter over a `DiscoverEntry[]`.\n *\n * Returns the original array reference unchanged when `query` is blank so the\n * caller can skip a re-render. Pure function — no side effects.\n */\nexport function filterDiscoverEntries(\n entries: DiscoverEntry[],\n query: string,\n): DiscoverEntry[] {\n const q = query.trim().toLowerCase();\n if (!q) return entries;\n return entries.filter((e) => e.title.toLowerCase().includes(q));\n}\n\n/**\n * Sort discover entries by updatedAt descending (most recent first).\n * Entries without an `updatedAt` field sort last. Pure function.\n */\nexport function sortDiscoverEntries(entries: DiscoverEntry[]): DiscoverEntry[] {\n return [...entries].sort((a, b) => (b.updatedAt ?? 0) - (a.updatedAt ?? 0));\n}\n","/**\n * A single row in the Discover list — shows the object's emoji/icon, title,\n * and type. All app-specific behaviour is injected via props:\n * - `renderIcon` — render a type/emoji icon; receives the entry and must\n * return a ReactNode (null for no icon).\n * - `onOpen` — called when the row is pressed.\n *\n * Styled entirely from the injected {@link Theme} via `useOctoSpacesTheme()`.\n */\nimport React, { useCallback } from 'react';\nimport { Pressable, Text, View } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverRowProps {\n entry: DiscoverEntry;\n /** Render a leading icon for the entry. Return `null` to show nothing. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when the user taps the row. */\n onOpen: (entry: DiscoverEntry) => void;\n}\n\nexport function DiscoverRow({ entry, renderIcon, onOpen }: DiscoverRowProps) {\n const theme = useOctoSpacesTheme();\n\n const handlePress = useCallback(() => {\n onOpen(entry);\n }, [entry, onOpen]);\n\n const icon = renderIcon ? renderIcon(entry) : null;\n const displayEmoji = !icon && entry.emoji ? entry.emoji : null;\n\n return (\n <Pressable\n onPress={handlePress}\n style={({ pressed }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: (theme.spacing['3'] as number) ?? 12,\n paddingHorizontal: (theme.spacing['4'] as number) ?? 16,\n backgroundColor: pressed\n ? (theme.colors.surface ?? '#f5f5f5')\n : 'transparent',\n borderRadius: (theme.radii['sm'] as number) ?? 6,\n })}\n accessibilityRole=\"button\"\n accessibilityLabel={entry.title || 'Untitled'}\n >\n {/* Leading icon / emoji */}\n {(icon || displayEmoji) && (\n <View\n style={{\n width: 28,\n height: 28,\n marginRight: (theme.spacing['2'] as number) ?? 8,\n alignItems: 'center',\n justifyContent: 'center',\n flexShrink: 0,\n }}\n >\n {icon ?? (\n <Text style={{ fontSize: 18, lineHeight: 24 }}>{displayEmoji}</Text>\n )}\n </View>\n )}\n\n {/* Title + type subtitle */}\n <View style={{ flex: 1, minWidth: 0 }}>\n <Text\n numberOfLines={1}\n style={{\n fontSize: (theme.type['body']?.size ?? 15),\n lineHeight: (theme.type['body']?.lineHeight ?? 22),\n color: entry.title ? theme.colors.text : theme.colors.textTertiary,\n fontFamily: theme.fonts['body'] ?? undefined,\n }}\n >\n {entry.title || 'Untitled'}\n </Text>\n <Text\n numberOfLines={1}\n style={{\n fontSize: (theme.type['caption']?.size ?? 12),\n lineHeight: (theme.type['caption']?.lineHeight ?? 18),\n color: theme.colors.textSecondary,\n marginTop: 1,\n textTransform: 'capitalize',\n }}\n >\n {entry.type}\n </Text>\n </View>\n </Pressable>\n );\n}\n","/**\n * A themed FlatList wrapper that renders a list of {@link DiscoverEntry} rows.\n *\n * All app-specific behaviour is injected via props so this component has zero\n * imports from any specific OctoSpaces app.\n */\nimport React, { useCallback } from 'react';\nimport { FlatList, RefreshControl, Text, View } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport { DiscoverRow } from './DiscoverRow.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverListProps {\n entries: DiscoverEntry[];\n /** Render a leading icon for each row — see {@link DiscoverRowProps.renderIcon}. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when a row is tapped. */\n onOpen: (entry: DiscoverEntry) => void;\n /** Text shown when `entries` is empty (default: \"No public objects found\"). */\n emptyMessage?: string;\n /** Whether a pull-to-refresh is currently in progress. */\n refreshing?: boolean;\n /** Called when the user pulls to refresh. */\n onRefresh?: () => void;\n}\n\nexport function DiscoverList({\n entries,\n renderIcon,\n onOpen,\n emptyMessage = 'No public objects found',\n refreshing,\n onRefresh,\n}: DiscoverListProps) {\n const theme = useOctoSpacesTheme();\n\n const renderItem = useCallback(\n ({ item }: { item: DiscoverEntry }) => (\n <DiscoverRow entry={item} renderIcon={renderIcon} onOpen={onOpen} />\n ),\n [renderIcon, onOpen],\n );\n\n const keyExtractor = useCallback(\n (item: DiscoverEntry) => `${item.spaceId}:${item.id}`,\n [],\n );\n\n if (entries.length === 0) {\n return (\n <View\n style={{\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: (theme.spacing['6'] as number) ?? 24,\n }}\n >\n <Text\n style={{\n fontSize: theme.type['body']?.size ?? 15,\n color: theme.colors.textSecondary,\n textAlign: 'center',\n }}\n >\n {emptyMessage}\n </Text>\n </View>\n );\n }\n\n return (\n <FlatList\n data={entries}\n renderItem={renderItem}\n keyExtractor={keyExtractor}\n contentContainerStyle={{ paddingVertical: (theme.spacing['1'] as number) ?? 4 }}\n showsVerticalScrollIndicator={false}\n removeClippedSubviews\n refreshControl={\n onRefresh ? (\n <RefreshControl\n refreshing={refreshing ?? false}\n onRefresh={onRefresh}\n tintColor={theme.colors.primary}\n />\n ) : undefined\n }\n />\n );\n}\n","/**\n * Generic public-object discovery screen.\n *\n * Loads the world-readable public-object directory via `loadEntries`, renders a\n * search bar, and delegates row rendering + tap behaviour to the injected props.\n * No app-specific logic lives here — all customisation is via props:\n *\n * ```tsx\n * <DiscoverScreen\n * loadEntries={readObjectDirectory}\n * renderIcon={(e) => <TypeIcon entry={e} />}\n * onOpen={(e) => router.push({ pathname: routeForNode(e), params: { id: e.id, spaceId: e.spaceId } })}\n * />\n * ```\n *\n * State machine:\n * idle → loading → (ready | error)\n * Any pull of `loadEntries` updates the entries; errors show a retry button.\n */\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport { ActivityIndicator, Pressable, Text, TextInput, View, type TextStyle } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport { DiscoverList } from './DiscoverList.js';\nimport { filterDiscoverEntries, sortDiscoverEntries } from './filter.js';\nimport type { DiscoverEntry } from './types.js';\n\nexport interface DiscoverScreenProps {\n /**\n * Async function that resolves to the current public-object directory.\n * Typically `readObjectDirectory` from `@drakkar.software/octospaces-sdk`.\n * Called on mount and when `refresh()` is triggered.\n */\n loadEntries: () => Promise<DiscoverEntry[]>;\n /** Render a leading icon for each row. */\n renderIcon?: (entry: DiscoverEntry) => React.ReactNode;\n /** Called when the user taps a row — navigate to the object. */\n onOpen: (entry: DiscoverEntry) => void;\n /**\n * Optional heading text shown above the search bar.\n * @default \"Discover\"\n */\n title?: string;\n /**\n * Text shown when the directory is empty after loading.\n * @default \"No public objects yet\"\n */\n emptyMessage?: string;\n /**\n * Text shown when the directory is empty due to an active search query.\n * @default \"No results for «query»\"\n */\n emptySearchMessage?: string;\n /**\n * Whether to show the inline search bar.\n * @default true\n */\n searchEnabled?: boolean;\n /**\n * Optional ref whose `.current` is set to a `reload()` function once mounted.\n * Lets a host (e.g. a tab screen) trigger a soft-refresh on focus without\n * blanking the existing list — identical to pull-to-refresh behaviour.\n *\n * ```tsx\n * const reloadRef = useRef<() => void>(null);\n * useFocusEffect(useCallback(() => { reloadRef.current?.(); }, []));\n * <DiscoverScreen reloadRef={reloadRef} ... />\n * ```\n */\n reloadRef?: React.RefObject<(() => void) | null>;\n}\n\ntype State =\n | { status: 'idle' }\n | { status: 'loading' }\n | { status: 'ready'; entries: DiscoverEntry[] }\n | { status: 'error'; message: string };\n\nexport function DiscoverScreen({\n loadEntries,\n renderIcon,\n onOpen,\n title = 'Discover',\n emptyMessage = 'No public objects yet',\n emptySearchMessage,\n searchEnabled = true,\n reloadRef,\n}: DiscoverScreenProps) {\n const theme = useOctoSpacesTheme();\n const [state, setState] = useState<State>({ status: 'idle' });\n const [query, setQuery] = useState('');\n const [refreshing, setRefreshing] = useState(false);\n const cancelledRef = useRef(false);\n\n const load = useCallback(async () => {\n setState({ status: 'loading' });\n try {\n const raw = await loadEntries();\n if (cancelledRef.current) return;\n setState({ status: 'ready', entries: sortDiscoverEntries(raw) });\n } catch (err) {\n if (cancelledRef.current) return;\n setState({\n status: 'error',\n message: err instanceof Error ? err.message : 'Failed to load directory',\n });\n }\n }, [loadEntries]);\n\n /** Pull-to-refresh: re-fetches without blanking the existing list. */\n const handleRefresh = useCallback(async () => {\n setRefreshing(true);\n try {\n const raw = await loadEntries();\n if (cancelledRef.current) return;\n setState({ status: 'ready', entries: sortDiscoverEntries(raw) });\n } catch {\n // keep the existing list on refresh failure; the retry button remains for error state\n } finally {\n if (!cancelledRef.current) setRefreshing(false);\n }\n }, [loadEntries]);\n\n // Expose handleRefresh via reloadRef so a host can trigger a soft reload on focus.\n useImperativeHandle(reloadRef, () => handleRefresh, [handleRefresh]);\n\n useEffect(() => {\n cancelledRef.current = false;\n void load();\n return () => {\n cancelledRef.current = true;\n };\n }, [load]);\n\n // ── Derived list ─────────────────────────────────────────────────────────\n const allEntries = state.status === 'ready' ? state.entries : [];\n const visibleEntries = filterDiscoverEntries(allEntries, query);\n const noSearchResults = !!query.trim() && visibleEntries.length === 0 && allEntries.length > 0;\n const resolvedEmptyMessage = noSearchResults\n ? (emptySearchMessage ?? `No results for \"${query.trim()}\"`)\n : emptyMessage;\n\n // ── Palette shortcuts ─────────────────────────────────────────────────────\n const sp2 = (theme.spacing['2'] as number) ?? 8;\n const sp3 = (theme.spacing['3'] as number) ?? 12;\n const sp4 = (theme.spacing['4'] as number) ?? 16;\n const radMd = (theme.radii['md'] as number) ?? 8;\n\n return (\n <View style={{ flex: 1, backgroundColor: theme.colors.background }}>\n {/* Header */}\n <View\n style={{\n paddingHorizontal: sp4,\n paddingTop: sp4,\n paddingBottom: sp2,\n }}\n >\n <Text\n style={{\n fontSize: theme.type['title2']?.size ?? 22,\n fontWeight: (theme.type['title2']?.weight as TextStyle['fontWeight']) ?? '700',\n lineHeight: theme.type['title2']?.lineHeight ?? 28,\n color: theme.colors.text,\n fontFamily: theme.fonts['heading'] ?? undefined,\n marginBottom: sp3,\n }}\n >\n {title}\n </Text>\n\n {/* Search bar */}\n {searchEnabled && (\n <View\n style={{\n flexDirection: 'row',\n alignItems: 'center',\n backgroundColor: theme.colors.surfaceInput ?? theme.colors.surface,\n borderRadius: radMd,\n borderWidth: 1,\n borderColor: theme.colors.borderSubtle,\n paddingHorizontal: sp3,\n height: 40,\n }}\n >\n <TextInput\n placeholder=\"Search…\"\n placeholderTextColor={theme.colors.textTertiary}\n value={query}\n onChangeText={setQuery}\n style={{\n flex: 1,\n fontSize: theme.type['body']?.size ?? 15,\n color: theme.colors.text,\n fontFamily: theme.fonts['body'] ?? undefined,\n }}\n returnKeyType=\"search\"\n clearButtonMode=\"while-editing\"\n accessibilityLabel=\"Search discover\"\n />\n </View>\n )}\n </View>\n\n {/* Body */}\n {state.status === 'loading' ? (\n <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>\n <ActivityIndicator color={theme.colors.primary} />\n </View>\n ) : state.status === 'error' ? (\n <View\n style={{\n flex: 1,\n alignItems: 'center',\n justifyContent: 'center',\n paddingHorizontal: sp4,\n }}\n >\n <Text\n style={{\n color: theme.colors.textSecondary,\n fontSize: theme.type['body']?.size ?? 15,\n textAlign: 'center',\n marginBottom: sp3,\n }}\n >\n {state.message}\n </Text>\n <Pressable\n onPress={load}\n style={{\n paddingHorizontal: sp4,\n paddingVertical: sp2,\n backgroundColor: theme.colors.primary,\n borderRadius: radMd,\n }}\n >\n <Text style={{ color: theme.colors.textOnPrimary, fontWeight: '600' }}>\n Retry\n </Text>\n </Pressable>\n </View>\n ) : (\n <DiscoverList\n entries={visibleEntries}\n renderIcon={renderIcon}\n onOpen={onOpen}\n emptyMessage={resolvedEmptyMessage}\n refreshing={refreshing}\n onRefresh={handleRefresh}\n />\n )}\n </View>\n );\n}\n","/**\n * Headless, abstractly-themed vertical spaces rail.\n *\n * The component reads the injected {@link Theme} via `useOctoSpacesTheme()` and\n * delegates all app-specific concerns to props:\n *\n * - Icons are rendered by `renderIcon` (keeps `@expo/vector-icons` out of this package).\n * - Space tile images are rendered by `renderTileImage` (keeps `expo-image` out too).\n * - Unread badges are rendered by `renderBadge`.\n * - The rail foot (account avatar + menu) is rendered by `renderFoot`.\n * - Web drag-reorder is wired via the `useTileDnd` hook prop (see below).\n *\n * All React Native primitives used here ship with the `react-native` peer dep.\n */\nimport React, { useState } from 'react';\nimport {\n Pressable as RNPressable,\n ScrollView,\n Text,\n View,\n} from 'react-native';\nimport type { PressableProps, View as RNView } from 'react-native';\n\nimport { useOctoSpacesTheme } from '../theme/provider.js';\nimport { railTileState } from './tile-state.js';\nimport type { RailTileTokens } from './tile-state.js';\nimport type { RailIconName, RailSpace } from './types.js';\n\n// ── Pressable with web hover events ───────────────────────────────────────────\n\n// React Native Web supports onMouseEnter/onMouseLeave for hover detection.\n// The peer dep is >=0.75 which includes these events in the ViewProps contract.\n// Cast to ForwardRefExoticComponent so `ref` is a valid JSX prop (Pressable\n// already uses forwardRef internally; this just makes TypeScript aware of it).\ntype HoverProps = { onMouseEnter?: () => void; onMouseLeave?: () => void };\nconst Pressable = RNPressable as React.ForwardRefExoticComponent<\n PressableProps & HoverProps & React.RefAttributes<RNView>\n>;\n\n// ── Props ─────────────────────────────────────────────────────────────────────\n\nexport interface SpacesRailProps {\n /** The spaces to show in the scrollable column. */\n spaces: RailSpace[];\n /** The currently-active space id (or null / undefined for none). */\n activeId?: string | null;\n /** Called when the user selects a space tile. */\n onSelect?: (id: string) => void;\n /** Called when the user taps the \"add\" tile. */\n onAdd?: () => void;\n /** When provided, renders a leading DM-home tile. */\n onSelectDms?: () => void;\n /** Whether the DM-home tile is the active selection. */\n dmsActive?: boolean;\n /** Unread count for the DM-home tile badge. */\n dmUnread?: number;\n /** Accessibility label for the DM-home tile (default: \"Direct messages\"). */\n dmLabel?: string;\n /** Accessibility label for the add-space tile (default: \"Create or join a space\"). */\n addLabel?: string;\n /**\n * Render a named icon at the given size and color. Used for the DM tile icon\n * (`'dm'`), the E2EE lock corner (`'lock'`), the mute corner (`'mute'`),\n * and the add tile icon (`'add'`). Return `null` to suppress the icon slot.\n * If omitted, all icon slots render nothing.\n */\n renderIcon?: (name: RailIconName, size: number, color: string) => React.ReactNode;\n /**\n * Render an image filling the tile background. Only called when `space.image`\n * is set. The component must fill its parent (`StyleSheet.absoluteFill` or\n * equivalent). If omitted, the short-name monogram is shown instead.\n */\n renderTileImage?: (space: RailSpace) => React.ReactNode;\n /**\n * Render an unread badge. Only called when `space.unread > 0`.\n * If omitted, badges are not shown.\n */\n renderBadge?: (count: number) => React.ReactNode;\n /**\n * When `true`, each tile shows a small E2EE-lock corner badge (bottom-right).\n * Requires `renderIcon` to be provided (otherwise the corner renders nothing).\n * Default: `false`.\n */\n showLockCorner?: boolean;\n /**\n * Render the pinned rail foot (e.g. the account avatar and popover).\n * The host app owns this entirely — identity state stays out of the package.\n */\n renderFoot?: () => React.ReactNode;\n /**\n * **Hook injection for web drag-reorder.** When provided, each space tile is\n * wrapped in a `DndTile` that calls `useTileDnd(spaceId)` unconditionally at\n * the top of its render — treat this prop as a React hook and keep it stable\n * for the lifetime of a `SpacesRail` mount (always provided or always absent).\n * Omit on native / in apps that don't need DnD.\n */\n useTileDnd?: (spaceId: string) => { ref?: React.Ref<RNView>; over?: boolean };\n}\n\n// ── Token resolver ─────────────────────────────────────────────────────────────\n\nfunction resolveRailTokens(theme: ReturnType<typeof useOctoSpacesTheme>): RailTileTokens {\n const { colors, swatches } = theme;\n return {\n primary: colors.primary,\n primaryMuted: colors.primaryMuted,\n primarySubtle: colors.primarySubtle,\n surfaceInput: colors.surfaceInput,\n borderSubtle: colors.borderSubtle,\n textOnPrimary: colors.textOnPrimary,\n textSecondary: colors.textSecondary,\n textTertiary: colors.textTertiary,\n railTile: swatches['railTile'] ?? colors.surfaceInput,\n railTileHoverBorder: swatches['railTileHoverBorder'] ?? colors.primarySubtle,\n railGlow: swatches['railGlow'] ?? colors.primary,\n railTileHoverInk: swatches['railTileHoverInk'] ?? colors.primary,\n };\n}\n\n// ── Shared tile dimensions (constant) ────────────────────────────────────────\n\nconst TILE_SIZE = 40;\nconst CORNER_SIZE = 16;\nconst BADGE_OFFSET = -5;\nconst CORNER_OFFSET = -3;\n\n// ── Tile content (non-hook render helper) ─────────────────────────────────────\n\ninterface TileContentProps {\n space: RailSpace;\n labelColor: string;\n fontFamily?: string;\n fontSize: number;\n lineHeight: number;\n cornerBg: string;\n cornerBorder: string;\n cornerIconColor: string;\n renderIcon?: SpacesRailProps['renderIcon'];\n renderTileImage?: SpacesRailProps['renderTileImage'];\n renderBadge?: SpacesRailProps['renderBadge'];\n showLockCorner?: boolean;\n}\n\nfunction TileContent({\n space,\n labelColor,\n fontFamily,\n fontSize,\n lineHeight,\n cornerBg,\n cornerBorder,\n cornerIconColor,\n renderIcon,\n renderTileImage,\n renderBadge,\n showLockCorner,\n}: TileContentProps) {\n return (\n <>\n {/* Image or monogram */}\n {space.image && renderTileImage ? (\n renderTileImage(space)\n ) : (\n <Text\n style={{\n fontSize,\n lineHeight,\n fontWeight: '700',\n fontFamily: fontFamily ?? undefined,\n color: labelColor,\n }}\n numberOfLines={1}\n >\n {space.short}\n </Text>\n )}\n {/* E2EE lock corner (bottom-right) */}\n {showLockCorner && renderIcon ? (\n <View\n style={{\n position: 'absolute',\n bottom: CORNER_OFFSET,\n right: CORNER_OFFSET,\n width: CORNER_SIZE,\n height: CORNER_SIZE,\n borderRadius: CORNER_SIZE / 2,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: cornerBg,\n borderColor: cornerBorder,\n }}\n >\n {renderIcon('lock', 9, cornerIconColor)}\n </View>\n ) : null}\n {/* Mute corner (bottom-left) */}\n {space.muted && renderIcon ? (\n <View\n style={{\n position: 'absolute',\n bottom: CORNER_OFFSET,\n left: CORNER_OFFSET,\n width: CORNER_SIZE,\n height: CORNER_SIZE,\n borderRadius: CORNER_SIZE / 2,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: cornerBg,\n borderColor: cornerBorder,\n }}\n >\n {renderIcon('mute', 9, cornerIconColor)}\n </View>\n ) : null}\n {/* Unread badge (top-right) */}\n {space.unread ? (\n <View\n style={{\n position: 'absolute',\n top: BADGE_OFFSET,\n right: BADGE_OFFSET,\n }}\n >\n {renderBadge ? renderBadge(space.unread) : null}\n </View>\n ) : null}\n </>\n );\n}\n\n// ── PlainTile — space tile without DnD ────────────────────────────────────────\n\ninterface TileSharedProps {\n space: RailSpace;\n active: boolean;\n onPress?: () => void;\n tokens: RailTileTokens;\n radiusActive: number;\n radiusDefault: number;\n renderIcon?: SpacesRailProps['renderIcon'];\n renderTileImage?: SpacesRailProps['renderTileImage'];\n renderBadge?: SpacesRailProps['renderBadge'];\n showLockCorner?: boolean;\n cornerBg: string;\n cornerBorder: string;\n fontFamily?: string;\n fontSize: number;\n lineHeight: number;\n}\n\nfunction PlainTile({\n space,\n active,\n onPress,\n tokens,\n radiusActive,\n radiusDefault,\n renderIcon,\n renderTileImage,\n renderBadge,\n showLockCorner,\n cornerBg,\n cornerBorder,\n fontFamily,\n fontSize,\n lineHeight,\n}: TileSharedProps) {\n const [hovered, setHovered] = useState(false);\n const s = railTileState({ active, hovered, over: false }, tokens, radiusActive, radiusDefault);\n\n return (\n <Pressable\n onPress={onPress}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n accessibilityRole=\"button\"\n accessibilityLabel={space.short}\n style={{\n position: 'relative',\n width: TILE_SIZE,\n height: TILE_SIZE,\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n borderRadius: s.radius,\n backgroundColor: s.bg,\n borderWidth: s.borderWidth,\n borderColor: s.borderColor,\n ...(s.shadow ?? {}),\n }}\n >\n <TileContent\n space={space}\n labelColor={s.labelColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n lineHeight={lineHeight}\n cornerBg={cornerBg}\n cornerBorder={cornerBorder}\n cornerIconColor={tokens.textTertiary}\n renderIcon={renderIcon}\n renderTileImage={renderTileImage}\n renderBadge={renderBadge}\n showLockCorner={showLockCorner}\n />\n </Pressable>\n );\n}\n\n// ── DndTile — space tile with hook-injected DnD ref + over state ──────────────\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\ninterface DndTileProps extends TileSharedProps {\n /** Hook injection: called unconditionally at the top of this component.\n * Treat it as a React hook — always provided for every DndTile. */\n dnd: NonNullable<SpacesRailProps['useTileDnd']>;\n}\n\nfunction DndTile({\n space,\n active,\n onPress,\n tokens,\n radiusActive,\n radiusDefault,\n renderIcon,\n renderTileImage,\n renderBadge,\n showLockCorner,\n cornerBg,\n cornerBorder,\n fontFamily,\n fontSize,\n lineHeight,\n dnd,\n}: DndTileProps) {\n const [hovered, setHovered] = useState(false);\n // Hook injection: useTileDnd is called unconditionally here (it IS a hook).\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const { ref, over = false } = dnd(space.id);\n const s = railTileState({ active, hovered, over }, tokens, radiusActive, radiusDefault);\n\n return (\n <Pressable\n ref={ref as React.Ref<RNView>}\n onPress={onPress}\n onMouseEnter={() => setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n accessibilityRole=\"button\"\n accessibilityLabel={space.short}\n style={{\n position: 'relative',\n width: TILE_SIZE,\n height: TILE_SIZE,\n alignItems: 'center',\n justifyContent: 'center',\n overflow: 'hidden',\n borderRadius: s.radius,\n backgroundColor: s.bg,\n borderWidth: s.borderWidth,\n borderColor: s.borderColor,\n ...(s.shadow ?? {}),\n }}\n >\n <TileContent\n space={space}\n labelColor={s.labelColor}\n fontFamily={fontFamily}\n fontSize={fontSize}\n lineHeight={lineHeight}\n cornerBg={cornerBg}\n cornerBorder={cornerBorder}\n cornerIconColor={tokens.textTertiary}\n renderIcon={renderIcon}\n renderTileImage={renderTileImage}\n renderBadge={renderBadge}\n showLockCorner={showLockCorner}\n />\n </Pressable>\n );\n}\n\n// ── SpacesRail ─────────────────────────────────────────────────────────────────\n\n/**\n * Vertical spaces rail — a 64px-wide column of square space tiles, a DM-home tile,\n * an add-space tile, and a pinned foot for the account widget.\n *\n * Styled entirely from the injected {@link Theme} via `useOctoSpacesTheme()`.\n * All icons, images, badges, and the account foot are provided by the host app.\n */\nexport function SpacesRail({\n spaces,\n activeId,\n onSelect,\n onAdd,\n onSelectDms,\n dmsActive = false,\n dmUnread,\n dmLabel = 'Direct messages',\n addLabel = 'Create or join a space',\n renderIcon,\n renderTileImage,\n renderBadge,\n showLockCorner = false,\n renderFoot,\n useTileDnd,\n}: SpacesRailProps) {\n const theme = useOctoSpacesTheme();\n const { colors, spacing, radii, type: typeScale, fonts, layout } = theme;\n\n const tokens = resolveRailTokens(theme);\n\n // Layout constants with fallbacks for hosts that haven't set them.\n const railWidth = (layout['railWidth'] as number | undefined) ?? 64;\n const spaceV = (spacing['2'] as number | undefined) ?? 8;\n const spaceXs = (spacing['1'] as number | undefined) ?? 4;\n const spaceS = (spacing['2'] as number | undefined) ?? 8;\n const spaceMd = (spacing['3'] as number | undefined) ?? 12;\n\n const radiusActive = (radii['lg'] as number | undefined) ?? 12;\n const radiusDefault = (radii['xl'] as number | undefined) ?? 16;\n\n const footnoteSize = typeScale['footnote']?.size ?? 12;\n const footnoteLineH = typeScale['footnote']?.lineHeight ?? 18;\n const monoFont = fonts['mono'] ?? undefined;\n\n // Corner-badge tokens (background = rail surface, border = rail border).\n const cornerBg = colors.sidebar;\n const cornerBorder = colors.border;\n\n // Shared tile props (passed to every tile variant).\n const tileShared = {\n tokens,\n radiusActive,\n radiusDefault,\n renderIcon,\n renderTileImage,\n renderBadge,\n showLockCorner,\n cornerBg,\n cornerBorder,\n fontFamily: monoFont,\n fontSize: footnoteSize,\n lineHeight: footnoteLineH,\n };\n\n // DM tile hover state (managed here since DM tile is inline, not a separate component).\n const [dmHovered, setDmHovered] = useState(false);\n\n const dmTileStyle = railTileState(\n { active: dmsActive, hovered: dmHovered, over: false },\n tokens,\n radiusActive,\n radiusDefault,\n );\n\n const dmIconColor = dmsActive ? colors.textOnPrimary : dmHovered ? tokens.railTileHoverInk : colors.textSecondary;\n\n // Add-tile hover state.\n const [addHovered, setAddHovered] = useState(false);\n\n // Whether DnD is active (determines which tile variant to render).\n const hasDnd = !!useTileDnd;\n\n return (\n <View\n style={{\n width: railWidth,\n paddingVertical: spaceMd,\n borderRightWidth: 1,\n borderRightColor: colors.border,\n backgroundColor: colors.sidebar,\n alignItems: 'center',\n gap: spaceS,\n }}\n >\n {/* Scrollable tile column */}\n <ScrollView\n style={{ alignSelf: 'stretch', flex: 1 }}\n contentContainerStyle={{\n alignItems: 'center',\n gap: spaceV,\n paddingVertical: spaceXs,\n }}\n showsVerticalScrollIndicator={false}\n >\n {/* DM-home tile (pinned first when provided) */}\n {onSelectDms ? (\n <View style={{ position: 'relative' }}>\n <Pressable\n onPress={onSelectDms}\n onMouseEnter={() => setDmHovered(true)}\n onMouseLeave={() => setDmHovered(false)}\n accessibilityRole=\"button\"\n accessibilityLabel={dmLabel}\n style={{\n width: TILE_SIZE,\n height: TILE_SIZE,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: dmTileStyle.radius,\n backgroundColor: dmTileStyle.bg,\n borderWidth: dmTileStyle.borderWidth,\n borderColor: dmTileStyle.borderColor,\n ...(dmTileStyle.shadow ?? {}),\n }}\n >\n {renderIcon ? renderIcon('dm', 20, dmIconColor) : null}\n </Pressable>\n {/* Lock corner */}\n {showLockCorner && renderIcon ? (\n <View\n style={{\n position: 'absolute',\n bottom: CORNER_OFFSET,\n right: CORNER_OFFSET,\n width: CORNER_SIZE,\n height: CORNER_SIZE,\n borderRadius: CORNER_SIZE / 2,\n borderWidth: 1,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: cornerBg,\n borderColor: cornerBorder,\n }}\n >\n {renderIcon('lock', 9, tokens.textTertiary)}\n </View>\n ) : null}\n {/* DM unread badge */}\n {dmUnread ? (\n <View style={{ position: 'absolute', top: BADGE_OFFSET, right: BADGE_OFFSET }}>\n {renderBadge ? renderBadge(dmUnread) : null}\n </View>\n ) : null}\n </View>\n ) : null}\n\n {/* Space tiles */}\n {spaces.map((s) =>\n hasDnd ? (\n <DndTile\n key={s.id}\n space={s}\n active={s.id === activeId}\n onPress={() => onSelect?.(s.id)}\n dnd={useTileDnd!}\n {...tileShared}\n />\n ) : (\n <PlainTile\n key={s.id}\n space={s}\n active={s.id === activeId}\n onPress={() => onSelect?.(s.id)}\n {...tileShared}\n />\n ),\n )}\n\n {/* Add-space tile */}\n <Pressable\n onPress={onAdd}\n onMouseEnter={() => setAddHovered(true)}\n onMouseLeave={() => setAddHovered(false)}\n accessibilityRole=\"button\"\n accessibilityLabel={addLabel}\n style={{\n width: TILE_SIZE,\n height: TILE_SIZE,\n alignItems: 'center',\n justifyContent: 'center',\n borderRadius: radiusDefault,\n borderWidth: 1,\n borderStyle: 'dashed',\n borderColor: addHovered ? colors.border : colors.borderSubtle,\n }}\n >\n {renderIcon ? renderIcon('add', 16, addHovered ? tokens.railTileHoverInk : colors.textTertiary) : null}\n </Pressable>\n </ScrollView>\n\n {/* Pinned foot — account avatar, popover, etc. */}\n {renderFoot ? renderFoot() : null}\n </View>\n );\n}\n","/**\n * Pure helper: maps tile interaction state + resolved theme tokens → visual style.\n * No React or React Native imports — fully testable in isolation.\n */\nimport type { ShadowToken } from '../theme/types.js';\nimport { glowShadow } from '../theme/helpers.js';\n\n// ── Token contract ─────────────────────────────────────────────────────────────\n\n/**\n * Resolved color tokens consumed by {@link railTileState}. Built from a `Theme`\n * object in the component layer (see `resolveRailTokens` in `SpacesRail.tsx`).\n * Swatch entries fall back to palette tokens when the host app hasn't set them.\n */\nexport interface RailTileTokens {\n // Core palette tokens\n primary: string;\n primaryMuted: string;\n primarySubtle: string;\n surfaceInput: string;\n borderSubtle: string;\n textOnPrimary: string;\n textSecondary: string;\n textTertiary: string;\n // Optional swatch overrides (host app can tune rail-specific colors)\n railTile: string; // swatch 'railTile' ?? surfaceInput\n railTileHoverBorder: string; // swatch 'railTileHoverBorder' ?? primarySubtle\n railGlow: string; // swatch 'railGlow' ?? primary\n railTileHoverInk: string; // swatch 'railTileHoverInk' ?? primary\n}\n\n// ── Output ─────────────────────────────────────────────────────────────────────\n\n/** Resolved visual properties for a single rail tile. */\nexport interface RailTileStyle {\n /** Tile background color. */\n bg: string;\n /** Tile border color. */\n borderColor: string;\n /** Tile border width (0 when active, 1 otherwise). */\n borderWidth: number;\n /** Tile border-radius in pixels (squared-off when active/hovered, rounded otherwise). */\n radius: number;\n /** Monogram label color. */\n labelColor: string;\n /** Active glow shadow, or `null` when not active. */\n shadow: ShadowToken | null;\n}\n\n// ── Mapping ────────────────────────────────────────────────────────────────────\n\n/**\n * Map tile interaction state to visual style tokens.\n *\n * @param state Current interaction state.\n * @param tokens Resolved color tokens (see {@link RailTileTokens}).\n * @param radiusActive Border-radius when the tile is active or hovered (squarer look).\n * @param radiusDefault Border-radius for the resting state (rounder look).\n */\nexport function railTileState(\n state: { active: boolean; hovered: boolean; over: boolean },\n tokens: RailTileTokens,\n radiusActive: number,\n radiusDefault: number,\n): RailTileStyle {\n const { active, hovered, over } = state;\n\n let bg: string;\n let borderColor: string;\n let borderWidth: number;\n let labelColor: string;\n\n if (active) {\n bg = tokens.primary;\n borderColor = 'transparent';\n borderWidth = 0;\n labelColor = tokens.textOnPrimary;\n } else if (hovered) {\n bg = tokens.primaryMuted;\n borderColor = tokens.railTileHoverBorder;\n borderWidth = 1;\n labelColor = tokens.railTileHoverInk;\n } else {\n bg = tokens.railTile;\n borderColor = tokens.borderSubtle;\n borderWidth = 1;\n labelColor = tokens.textSecondary;\n }\n\n // Drop-target overlay: accent border when a dragged tile is over this one.\n if (over && !active) {\n borderColor = tokens.primary;\n borderWidth = 1;\n }\n\n const radius = active || hovered || over ? radiusActive : radiusDefault;\n const shadow: ShadowToken | null = active ? glowShadow(tokens.railGlow, 8, 0.3) : null;\n\n return { bg, borderColor, borderWidth, radius, labelColor, shadow };\n}\n"],"mappings":"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","names":["React","React","React","useCallback","Text","View","useCallback","React","View","Text","React","useCallback","Pressable","Text","View","useCallback","React","View","Text","Pressable","React","useState","Text","View","Pressable","React","Text","View","useState"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@drakkar.software/octospaces-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Shared headless-themed UI primitives for OctoSpaces apps. Theme values are injected by the host app; this package ships only types + plumbing + primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
package/src/index.ts
CHANGED
|
@@ -49,3 +49,10 @@ export type { DiscoverListProps } from './discover/DiscoverList.js';
|
|
|
49
49
|
export { DiscoverList } from './discover/DiscoverList.js';
|
|
50
50
|
export type { DiscoverScreenProps } from './discover/DiscoverScreen.js';
|
|
51
51
|
export { DiscoverScreen } from './discover/DiscoverScreen.js';
|
|
52
|
+
|
|
53
|
+
// Sidebar surface — the vertical spaces rail (icon tiles + DM home + add + foot).
|
|
54
|
+
// Headless: icons, images, badges and the account foot are injected via props so
|
|
55
|
+
// the package stays free of expo-image / @expo/vector-icons / reanimated.
|
|
56
|
+
export type { RailSpace, RailIconName } from './sidebar/types.js';
|
|
57
|
+
export type { SpacesRailProps } from './sidebar/SpacesRail.js';
|
|
58
|
+
export { SpacesRail } from './sidebar/SpacesRail.js';
|