@oxy.so/bloom 2.12.2 → 2.12.3

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.
Files changed (63) hide show
  1. package/lib/commonjs/button/shared.js +10 -1
  2. package/lib/commonjs/button/shared.js.map +1 -1
  3. package/lib/commonjs/sidebar/Sidebar.js +5 -1
  4. package/lib/commonjs/sidebar/Sidebar.js.map +1 -1
  5. package/lib/commonjs/sidebar/SidebarModeSwitcher.js +14 -20
  6. package/lib/commonjs/sidebar/SidebarModeSwitcher.js.map +1 -1
  7. package/lib/commonjs/sidebar/palette.js +2 -0
  8. package/lib/commonjs/sidebar/palette.js.map +1 -1
  9. package/lib/commonjs/social-button/OxyMark.js +4 -2
  10. package/lib/commonjs/social-button/OxyMark.js.map +1 -1
  11. package/lib/commonjs/social-button/SocialButton.js +2 -1
  12. package/lib/commonjs/social-button/SocialButton.js.map +1 -1
  13. package/lib/commonjs/social-button/use-svg-id.js +16 -0
  14. package/lib/commonjs/social-button/use-svg-id.js.map +1 -0
  15. package/lib/commonjs/theme-toggle/ThemeToggle.js +2 -2
  16. package/lib/commonjs/theme-toggle/ThemeToggle.js.map +1 -1
  17. package/lib/module/button/shared.js +9 -0
  18. package/lib/module/button/shared.js.map +1 -1
  19. package/lib/module/sidebar/Sidebar.js +5 -1
  20. package/lib/module/sidebar/Sidebar.js.map +1 -1
  21. package/lib/module/sidebar/SidebarModeSwitcher.js +16 -22
  22. package/lib/module/sidebar/SidebarModeSwitcher.js.map +1 -1
  23. package/lib/module/sidebar/palette.js +3 -1
  24. package/lib/module/sidebar/palette.js.map +1 -1
  25. package/lib/module/social-button/OxyMark.js +3 -2
  26. package/lib/module/social-button/OxyMark.js.map +1 -1
  27. package/lib/module/social-button/SocialButton.js +3 -2
  28. package/lib/module/social-button/SocialButton.js.map +1 -1
  29. package/lib/module/social-button/use-svg-id.js +13 -0
  30. package/lib/module/social-button/use-svg-id.js.map +1 -0
  31. package/lib/module/theme-toggle/ThemeToggle.js +3 -3
  32. package/lib/module/theme-toggle/ThemeToggle.js.map +1 -1
  33. package/lib/typescript/commonjs/button/shared.d.ts +8 -0
  34. package/lib/typescript/commonjs/button/shared.d.ts.map +1 -1
  35. package/lib/typescript/commonjs/sidebar/Sidebar.d.ts.map +1 -1
  36. package/lib/typescript/commonjs/sidebar/SidebarModeSwitcher.d.ts.map +1 -1
  37. package/lib/typescript/commonjs/sidebar/palette.d.ts +3 -0
  38. package/lib/typescript/commonjs/sidebar/palette.d.ts.map +1 -1
  39. package/lib/typescript/commonjs/social-button/OxyMark.d.ts.map +1 -1
  40. package/lib/typescript/commonjs/social-button/SocialButton.d.ts.map +1 -1
  41. package/lib/typescript/commonjs/social-button/use-svg-id.d.ts +7 -0
  42. package/lib/typescript/commonjs/social-button/use-svg-id.d.ts.map +1 -0
  43. package/lib/typescript/commonjs/theme-toggle/ThemeToggle.d.ts.map +1 -1
  44. package/lib/typescript/module/button/shared.d.ts +8 -0
  45. package/lib/typescript/module/button/shared.d.ts.map +1 -1
  46. package/lib/typescript/module/sidebar/Sidebar.d.ts.map +1 -1
  47. package/lib/typescript/module/sidebar/SidebarModeSwitcher.d.ts.map +1 -1
  48. package/lib/typescript/module/sidebar/palette.d.ts +3 -0
  49. package/lib/typescript/module/sidebar/palette.d.ts.map +1 -1
  50. package/lib/typescript/module/social-button/OxyMark.d.ts.map +1 -1
  51. package/lib/typescript/module/social-button/SocialButton.d.ts.map +1 -1
  52. package/lib/typescript/module/social-button/use-svg-id.d.ts +7 -0
  53. package/lib/typescript/module/social-button/use-svg-id.d.ts.map +1 -0
  54. package/lib/typescript/module/theme-toggle/ThemeToggle.d.ts.map +1 -1
  55. package/package.json +1 -1
  56. package/src/button/shared.ts +9 -0
  57. package/src/sidebar/Sidebar.tsx +5 -1
  58. package/src/sidebar/SidebarModeSwitcher.tsx +13 -14
  59. package/src/sidebar/palette.ts +6 -1
  60. package/src/social-button/OxyMark.tsx +4 -2
  61. package/src/social-button/SocialButton.tsx +3 -2
  62. package/src/social-button/use-svg-id.ts +10 -0
  63. package/src/theme-toggle/ThemeToggle.tsx +3 -9
@@ -0,0 +1,7 @@
1
+ /**
2
+ * A DOM-safe id prefix, per component instance. Two marks of one brand on a
3
+ * page must not resolve each other's `url(#…)`, and React's `useId` carries
4
+ * characters an SVG id cannot.
5
+ */
6
+ export declare function useSvgIdPrefix(prefix: string): string;
7
+ //# sourceMappingURL=use-svg-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-svg-id.d.ts","sourceRoot":"","sources":["../../../../src/social-button/use-svg-id.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD"}
@@ -1 +1 @@
1
- {"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../../../src/theme-toggle/ThemeToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwD,MAAM,OAAO,CAAC;AAgC7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqCvE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,CAuCrE;AAkcD,eAAO,MAAM,WAAW,8CAA6B,CAAC"}
1
+ {"version":3,"file":"ThemeToggle.d.ts","sourceRoot":"","sources":["../../../../src/theme-toggle/ThemeToggle.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwD,MAAM,OAAO,CAAC;AA0B7E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAG5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAqCvE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;IACjE,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,CAAC;IACrD,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAOD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,KAAK,GAAG,aAAa,CAuCrE;AAkcD,eAAO,MAAM,WAAW,8CAA6B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/bloom",
3
- "version": "2.12.2",
3
+ "version": "2.12.3",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
6
6
  "main": "lib/commonjs/index.js",
@@ -178,6 +178,15 @@ export const BUTTON_SIZE_ALIAS: Record<ButtonSize, ButtonResolvedSize> = {
178
178
  /** The button's transition duration. */
179
179
  export const BUTTON_TRANSITION_MS = 150;
180
180
 
181
+ /**
182
+ * The sliding thumb of a segmented control — the sidebar theme toggle and the
183
+ * sidebar's mode switcher — so the two move at one speed on one curve.
184
+ */
185
+ export const SEGMENTED_THUMB_MS = 200;
186
+ /** Control points for `Easing.bezier(...)` — the constant itself stays out of
187
+ * this module, which every family imports and which must not pull Reanimated in. */
188
+ export const SEGMENTED_THUMB_EASE_BEZIER = [0.25, 0.1, 0.25, 1] as const;
189
+
181
190
  /** Tailwind v4 `shadow-xs`, and its dark-mode override. */
182
191
  export const BUTTON_SHADOW = {
183
192
  light: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
@@ -480,12 +480,16 @@ const SidebarPanel: React.FC<SidebarProps> = ({
480
480
  shownItems.length === 0 && shownSecondary.length === 0 && shownFolders.length === 0 && !collapsed;
481
481
  const hasTree = !!tree;
482
482
 
483
+ // A fresh closure here would re-render the memoised switcher on every
484
+ // keystroke in the quick-search field.
485
+ const handleModeChange = useCallback((key: string) => onModeChange?.(key), [onModeChange]);
486
+
483
487
  const modeSwitcher =
484
488
  modes && modes.length > 0 ? (
485
489
  <SidebarModeSwitcher
486
490
  modes={modes}
487
491
  value={mode ?? modes[0]!.key}
488
- onValueChange={(key) => onModeChange?.(key)}
492
+ onValueChange={handleModeChange}
489
493
  collapsed={collapsed}
490
494
  accessibilityLabel={modesLabel}
491
495
  testID={testID ? `${testID}-modes` : 'sidebar-modes'}
@@ -8,14 +8,13 @@ import Animated, {
8
8
  withTiming,
9
9
  } from 'react-native-reanimated';
10
10
 
11
- import { BUTTON_SHADOW, resolveButtonRamps } from '../button/shared';
11
+ import { SEGMENTED_THUMB_EASE_BEZIER, SEGMENTED_THUMB_MS } from '../button/shared';
12
12
  import { useInteractionState } from '../hooks/use-interaction-state';
13
13
  import { borderRadius } from '../styles/tokens';
14
14
  import type { WebCssStyle } from '../styles/web-view-style';
15
- import { useTheme } from '../theme/use-theme';
16
15
  import { Text } from '../typography';
17
16
  import { useSidebarPalette } from './palette';
18
- import { Collapsible, IS_WEB, useSidebarWebCss } from './parts';
17
+ import { Collapsible, IS_WEB, useSidebarWebCss, webHook } from './parts';
19
18
  import type { SidebarMode, SidebarModeSwitcherProps } from './types';
20
19
 
21
20
  /**
@@ -37,8 +36,7 @@ import type { SidebarMode, SidebarModeSwitcherProps } from './types';
37
36
  const ROW_HEIGHT = 32;
38
37
  const ROW_GAP = 4;
39
38
  const PADDING = 4;
40
- const THUMB_MS = 200;
41
- const EASE = Easing.bezier(0.25, 0.1, 0.25, 1);
39
+ const EASE = Easing.bezier(...SEGMENTED_THUMB_EASE_BEZIER);
42
40
 
43
41
  function ModeRow({
44
42
  mode,
@@ -69,7 +67,8 @@ function ModeRow({
69
67
 
70
68
  return (
71
69
  <Pressable
72
- {...(IS_WEB ? { dataSet: { bloomSidebar: 'ring' }, ...(collapsed ? { title: mode.label } : null) } : {})}
70
+ {...webHook('ring')}
71
+ {...(IS_WEB && collapsed ? { title: mode.label } : null)}
73
72
  role="radio"
74
73
  aria-checked={selected}
75
74
  accessibilityState={{ checked: selected }}
@@ -112,22 +111,22 @@ const SidebarModeSwitcherComponent: React.FC<SidebarModeSwitcherProps> = ({
112
111
  style,
113
112
  testID,
114
113
  }) => {
115
- const theme = useTheme();
116
114
  const palette = useSidebarPalette();
117
115
  useSidebarWebCss();
118
116
  const reducedMotion = useReducedMotion();
119
- const { neutral: n } = resolveButtonRamps(theme);
120
117
 
121
- const index = Math.max(0, modes.findIndex((mode) => mode.key === value));
118
+ // One scan answers both questions: which row the thumb sits on, and whether
119
+ // there is a selected row at all.
120
+ const selectedIndex = modes.findIndex((mode) => mode.key === value);
121
+ const selectedKnown = selectedIndex !== -1;
122
+ const index = Math.max(0, selectedIndex);
122
123
  const offset = useSharedValue(index * (ROW_HEIGHT + ROW_GAP));
123
124
  useEffect(() => {
124
125
  const target = index * (ROW_HEIGHT + ROW_GAP);
125
- offset.value = reducedMotion ? target : withTiming(target, { duration: THUMB_MS, easing: EASE });
126
+ offset.value = reducedMotion ? target : withTiming(target, { duration: SEGMENTED_THUMB_MS, easing: EASE });
126
127
  }, [index, reducedMotion, offset]);
127
128
  const thumbStyle = useAnimatedStyle(() => ({ transform: [{ translateY: offset.value }] }), [offset]);
128
129
 
129
- const selectedKnown = modes.some((mode) => mode.key === value);
130
-
131
130
  return (
132
131
  <View
133
132
  role="radiogroup"
@@ -158,8 +157,8 @@ const SidebarModeSwitcherComponent: React.FC<SidebarModeSwitcherProps> = ({
158
157
  right: PADDING,
159
158
  height: ROW_HEIGHT,
160
159
  borderRadius: borderRadius.full,
161
- backgroundColor: theme.isDark ? n[700] : theme.colors.card,
162
- boxShadow: theme.isDark ? BUTTON_SHADOW.dark : BUTTON_SHADOW.light,
160
+ backgroundColor: palette.segmentedThumb,
161
+ boxShadow: palette.segmentedThumbShadow,
163
162
  },
164
163
  thumbStyle,
165
164
  ]}
@@ -1,6 +1,6 @@
1
1
  import { useMemo } from 'react';
2
2
 
3
- import { ACCENT_TABLE, colorRamp, mixColor, resolveButtonRamps } from '../button/shared';
3
+ import { ACCENT_TABLE, BUTTON_SHADOW, colorRamp, mixColor, resolveButtonRamps } from '../button/shared';
4
4
  import { MENU_SHADOW } from '../floating/menu-palette';
5
5
  import { PANEL_SHADOW, resolvePanelChrome } from '../styles/panel-chrome';
6
6
  import { hairlineOn } from '../styles/surface-levels';
@@ -49,6 +49,9 @@ export interface SidebarPalette {
49
49
  flat: string;
50
50
  rowHover: string;
51
51
  tertiary: string;
52
+ /** Fill and shadow of a segmented control's sliding thumb (the mode switcher). */
53
+ segmentedThumb: string;
54
+ segmentedThumbShadow: string;
52
55
  tertiaryHover: string;
53
56
  searchHover: string;
54
57
  searchRing: string;
@@ -113,6 +116,8 @@ export function resolveSidebarPalette(theme: Theme): SidebarPalette {
113
116
  flat: dark ? mixColor(n[900], n[950], 0.4) : card,
114
117
  rowHover: dark ? n[800] : n[200],
115
118
  tertiary: dark ? n[800] : n[200],
119
+ segmentedThumb: dark ? n[700] : card,
120
+ segmentedThumbShadow: dark ? BUTTON_SHADOW.dark : BUTTON_SHADOW.light,
116
121
  tertiaryHover: dark ? n[700] : n[300],
117
122
  // `hover:bg-background-tertiary-hover/55` over the panel.
118
123
  searchHover: mixColor(panel, dark ? n[700] : n[300], 0.55),
@@ -1,6 +1,8 @@
1
- import React, { useId } from 'react';
1
+ import React from 'react';
2
2
  import Svg, { ClipPath, Defs, G, Path } from 'react-native-svg';
3
3
 
4
+ import { useSvgIdPrefix } from './use-svg-id';
5
+
4
6
  /**
5
7
  * The Oxy mark for `SocialButton` — the same drawing the Oxy SDK ships.
6
8
  *
@@ -29,7 +31,7 @@ export interface OxyMarkProps {
29
31
 
30
32
  export function OxyMark({ size, color, letterColor }: OxyMarkProps) {
31
33
  // Two marks on one page must not resolve each other's clip path.
32
- const clipId = `bloom-oxy-mark-${useId().replace(/[^a-zA-Z0-9]/g, '')}`;
34
+ const clipId = useSvgIdPrefix('bloom-oxy-mark');
33
35
  return (
34
36
  <Svg width={size} height={size} viewBox={OXY_MARK_VIEW_BOX} aria-hidden>
35
37
  <Defs>
@@ -1,4 +1,4 @@
1
- import React, { memo, useCallback, useId, useMemo } from 'react';
1
+ import React, { memo, useCallback, useMemo } from 'react';
2
2
  import { Linking, Platform, View, type TextStyle, type ViewStyle } from 'react-native';
3
3
  import Svg, { Circle, Defs, LinearGradient, Path, Stop } from 'react-native-svg';
4
4
 
@@ -20,6 +20,7 @@ import { StyledPressable } from '../styles/styled-primitives';
20
20
  import type { WebCssStyle } from '../styles/web-view-style';
21
21
  import { SOCIAL_COLOR_LOGOS } from './color-logos';
22
22
  import { OxyMark } from './OxyMark';
23
+ import { useSvgIdPrefix } from './use-svg-id';
23
24
  import { SOCIAL_PROVIDERS, type SocialColorLogo, type SocialProvider } from './providers';
24
25
  import type {
25
26
  SocialBrand,
@@ -343,7 +344,7 @@ function ColorLogo({
343
344
  }) {
344
345
  // Gradient ids are per logo upstream; two buttons of one brand on a page must
345
346
  // not resolve each other's `url(#…)`, so every id is prefixed per instance.
346
- const prefix = `bloom-social-${useId().replace(/[^a-zA-Z0-9]/g, '')}-`;
347
+ const prefix = useSvgIdPrefix('bloom-social');
347
348
  const resolveFill = (fill: string) =>
348
349
  fill.startsWith('url(#') ? `url(#${prefix}${fill.slice(5)}` : fill;
349
350
 
@@ -0,0 +1,10 @@
1
+ import { useId } from 'react';
2
+
3
+ /**
4
+ * A DOM-safe id prefix, per component instance. Two marks of one brand on a
5
+ * page must not resolve each other's `url(#…)`, and React's `useId` carries
6
+ * characters an SVG id cannot.
7
+ */
8
+ export function useSvgIdPrefix(prefix: string): string {
9
+ return `${prefix}-${useId().replace(/[^a-zA-Z0-9]/g, '')}-`;
10
+ }
@@ -17,13 +17,7 @@ import Animated, {
17
17
  } from 'react-native-reanimated';
18
18
  import Svg, { Defs, LinearGradient, Rect, Stop } from 'react-native-svg';
19
19
 
20
- import {
21
- ACCENT_TABLE,
22
- BUTTON_SHADOW,
23
- colorRamp,
24
- mixColor,
25
- resolveButtonRamps,
26
- } from '../button/shared';
20
+ import { ACCENT_TABLE, BUTTON_SHADOW, SEGMENTED_THUMB_EASE_BEZIER, SEGMENTED_THUMB_MS, colorRamp, mixColor, resolveButtonRamps } from '../button/shared';
27
21
  import { useInteractionState } from '../hooks/use-interaction-state';
28
22
  import { RiMoonLine, RiSunLine } from '../icons/remix';
29
23
  import { adoptStyleSheet } from '../styles/adopt-style-sheet';
@@ -66,8 +60,8 @@ import { revealTheme, THEME_TRANSITION_DURATION, type RevealOrigin } from './vie
66
60
  */
67
61
 
68
62
  const IS_WEB = Platform.OS === 'web';
69
- const EASE = Easing.bezier(0.25, 0.1, 0.25, 1);
70
- const THUMB_MS = 200;
63
+ const EASE = Easing.bezier(...SEGMENTED_THUMB_EASE_BEZIER);
64
+ const THUMB_MS = SEGMENTED_THUMB_MS;
71
65
  const HOVER_MS = 150;
72
66
 
73
67
  interface TogglePalette {