@entropy-softworks/ui 2026.910.1 → 2026.910.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.
@@ -31,7 +31,7 @@ export { BottomNav, type BottomNavItem, type BottomNavProps } from "./bottom-nav
31
31
  export { ItemIcon, type ItemIconProps } from "./item-icon";
32
32
  export { TotpDisplay, type TotpDisplayProps } from "./totp-display";
33
33
  export { UserAvatar, type UserAvatarProps } from "./user-avatar";
34
- export { SocialConnections, type SocialConnectionsProps } from "./social-connections";
34
+ export { SocialConnections, visibleProviders, customIcon, type SocialConnectionsProps, type Provider, type ProviderKey, type ProviderVisibility, type CustomProvider, } from "./social-connections";
35
35
  export { ProfileDialog, ProfileTab, SecurityTab, type ProfileDialogProps, type ProfileTabProps, type SecurityTabProps, } from "./profile";
36
36
  export { ServerSettings, type ServerSettingsProps } from "./server-settings";
37
37
  export { ConnectionSettings, type ConnectionSettingsProps, type RunMode, } from "./connection-settings";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
@@ -33,7 +33,7 @@ export { BottomNav } from "./bottom-nav";
33
33
  export { ItemIcon } from "./item-icon";
34
34
  export { TotpDisplay } from "./totp-display";
35
35
  export { UserAvatar } from "./user-avatar";
36
- export { SocialConnections } from "./social-connections";
36
+ export { SocialConnections, visibleProviders, customIcon, } from "./social-connections";
37
37
  export { ProfileDialog, ProfileTab, SecurityTab, } from "./profile";
38
38
  export { ServerSettings } from "./server-settings";
39
39
  export { ConnectionSettings, } from "./connection-settings";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"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"}
@@ -27,7 +27,112 @@ export interface SocialConnectionsProps {
27
27
  * button away from consumers that want the placeholder.
28
28
  */
29
29
  showEntropy?: boolean;
30
+ /**
31
+ * Whether to offer "Continue with Apple". Defaults to `true`.
32
+ *
33
+ * ## Why these three needed flags of their own
34
+ *
35
+ * Apple, Google and Microsoft used to render unconditionally, so a consumer
36
+ * could suppress the whole row or none of it. That is the wrong granularity
37
+ * for an identity provider whose upstream connectors are configured per
38
+ * tenant: a tenant with only a Google connector still drew Apple and
39
+ * Microsoft, as two squares that respond to a press by doing nothing.
40
+ *
41
+ * `undefined` handlers do not hide a button, and that is deliberate and
42
+ * unchanged — a handler-less provider is the "coming soon" placeholder the
43
+ * consumer apps use during a staged rollout, and simple-finance renders the
44
+ * whole row with no handlers at all. So intent cannot be inferred from a
45
+ * missing handler; it has to be said. Same shape and same default as
46
+ * {@link SocialConnectionsProps.showEntropy}, so existing callers are
47
+ * unaffected.
48
+ */
49
+ showApple?: boolean;
50
+ /** Whether to offer "Continue with Google". Defaults to `true`. See {@link SocialConnectionsProps.showApple}. */
51
+ showGoogle?: boolean;
52
+ /** Whether to offer "Continue with Microsoft". Defaults to `true`. See {@link SocialConnectionsProps.showApple}. */
53
+ showMicrosoft?: boolean;
54
+ /**
55
+ * Providers this component has no brand mark for, rendered after the four it
56
+ * does — Okta, Keycloak, GitHub, another Access instance, anything an
57
+ * operator configures.
58
+ *
59
+ * ## Why these are a caller-supplied list rather than more `ProviderKey`s
60
+ *
61
+ * The four above are a closed set with bundled glyphs and a fixed order, so
62
+ * they can be named in the type. What an operator connects is open: adding a
63
+ * key here for every provider anyone might configure is a release of this
64
+ * package per connector, and the whole point of the identity provider's
65
+ * connector table is that it does not need one.
66
+ *
67
+ * Ordered as given, after the built-ins, so the row is stable — the
68
+ * built-ins keep the positions users have learned and new connectors append.
69
+ *
70
+ * `iconUrl` is optional and points at whatever the operator supplied. Absent,
71
+ * unreachable, or malformed, the button falls back to a neutral glyph rather
72
+ * than an empty square: a connector with no icon must still be pressable and
73
+ * still be labelled.
74
+ */
75
+ custom?: readonly CustomProvider[];
30
76
  }
77
+ /**
78
+ * A provider this package does not ship a mark for.
79
+ *
80
+ * `key` only has to be unique within the row; it is not a {@link ProviderKey}
81
+ * and is never matched against one. Prefix collisions with the built-ins are
82
+ * impossible because the rendered React keys are namespaced.
83
+ */
84
+ export interface CustomProvider {
85
+ key: string;
86
+ /** What the tooltip and the screen reader say: "Continue with {label}". */
87
+ label: string;
88
+ /**
89
+ * Absolute URL of the provider's mark. Rendered at 22px square, matching the
90
+ * bundled glyphs.
91
+ */
92
+ iconUrl?: string | null;
93
+ onPress?: () => void;
94
+ }
95
+ /** The federated providers this row can draw. */
96
+ export type ProviderKey = "entropy" | "apple" | "google" | "microsoft";
97
+ export interface Provider {
98
+ key: ProviderKey;
99
+ label: string;
100
+ /** Ionicons glyph, or `null` for Entropy, which draws its own brand mark. */
101
+ icon: "logo-apple" | "logo-google" | "logo-microsoft" | null;
102
+ }
103
+ /** Which providers each `show*` flag governs. Every key is required, so a new
104
+ * provider in {@link PROVIDERS} cannot be added without deciding its flag. */
105
+ export type ProviderVisibility = Record<ProviderKey, boolean>;
106
+ /**
107
+ * The providers to render, in {@link PROVIDERS} order.
108
+ *
109
+ * Exported and pure so the visibility rules are testable without a render
110
+ * runtime — the component itself pulls in `@expo/vector-icons` and reanimated,
111
+ * which the unit-suite transform does not process.
112
+ *
113
+ * Order comes from `PROVIDERS`, not from the caller's flags, so the row cannot
114
+ * reshuffle between renders or between consumers.
115
+ */
116
+ export declare function visibleProviders(shown: ProviderVisibility): readonly Provider[];
117
+ /**
118
+ * What a caller-supplied provider's button should draw.
119
+ *
120
+ * Pure, exported and separate from the component for the same reason
121
+ * {@link visibleProviders} is: the fallback rule is the part worth testing, and
122
+ * the component pulls in `@expo/vector-icons` and reanimated, which the unit
123
+ * suite's transform does not process.
124
+ *
125
+ * A blank or whitespace-only URL counts as absent, not as a URL to fetch: an
126
+ * operator who clears the field in a form leaves `""` behind far more often
127
+ * than `null`, and `<Image source={{uri: ""}}>` is a request that fails
128
+ * silently on web and renders an empty square on native.
129
+ */
130
+ export declare function customIcon(iconUrl: string | null | undefined, failed: boolean): {
131
+ kind: "image";
132
+ uri: string;
133
+ } | {
134
+ kind: "fallback";
135
+ };
31
136
  /**
32
137
  * Button row for federated sign-in: Entropy / Apple / Google / Microsoft.
33
138
  *
@@ -37,12 +142,17 @@ export interface SocialConnectionsProps {
37
142
  * (`/api/auth/oidc/start?connector=…`); the difference between them is a hint
38
143
  * passed to Access, not a separate flow.
39
144
  *
145
+ * To render only some providers, use the `show*` flags — one per provider, all
146
+ * defaulting to `true`. A consumer that knows which upstream connectors are
147
+ * configured (Entropy Access does, per tenant) should pass them, or it will
148
+ * draw brand marks for providers that cannot complete a sign-in.
149
+ *
40
150
  * The buttons are icon-only, so each carries a tooltip AND an
41
151
  * `accessibilityLabel`. The two are not redundant: the tooltip is for a sighted
42
152
  * user hovering on web or long-pressing on native, the label is what a screen
43
153
  * reader announces. Without the tooltip these are four unlabelled squares, and
44
154
  * a user has to guess which mark is which.
45
155
  */
46
- export declare function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, showEntropy, }: SocialConnectionsProps): React.JSX.Element;
156
+ export declare function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, showEntropy, showApple, showGoogle, showMicrosoft, custom, }: SocialConnectionsProps): React.JSX.Element;
47
157
  export default SocialConnections;
48
158
  //# sourceMappingURL=social-connections.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"social-connections.d.ts","sourceRoot":"","sources":["../../src/components/social-connections.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAqBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,WAAkB,GACnB,EAAE,sBAAsB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAwC5C;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"social-connections.d.ts","sourceRoot":"","sources":["../../src/components/social-connections.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAO1B,MAAM,WAAW,sBAAsB;IACrC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iHAAiH;IACjH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oHAAoH;IACpH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,MAAM,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;CACpC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,iDAAiD;AACjD,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,GAAG,WAAW,CAAC;AAEvE,MAAM,WAAW,QAAQ;IACvB,GAAG,EAAE,WAAW,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,6EAA6E;IAC7E,IAAI,EAAE,YAAY,GAAG,aAAa,GAAG,gBAAgB,GAAG,IAAI,CAAC;CAC9D;AAYD;8EAC8E;AAC9E,MAAM,MAAM,kBAAkB,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AAE9D;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,kBAAkB,GACxB,SAAS,QAAQ,EAAE,CAErB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAClC,MAAM,EAAE,OAAO,GACd;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAMvD;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,WAAkB,EAClB,SAAgB,EAChB,UAAiB,EACjB,aAAoB,EACpB,MAAW,GACZ,EAAE,sBAAsB,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CA6D5C;AAqDD,eAAe,iBAAiB,CAAC"}
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Pressable, View } from "react-native";
2
+ import { Image, Pressable, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
4
  import { useColorScheme } from "nativewind";
5
5
  import { EntropyMark } from "./entropy-mark";
@@ -13,6 +13,39 @@ const PROVIDERS = [
13
13
  { key: "google", label: "Google", icon: "logo-google" },
14
14
  { key: "microsoft", label: "Microsoft", icon: "logo-microsoft" },
15
15
  ];
16
+ /**
17
+ * The providers to render, in {@link PROVIDERS} order.
18
+ *
19
+ * Exported and pure so the visibility rules are testable without a render
20
+ * runtime — the component itself pulls in `@expo/vector-icons` and reanimated,
21
+ * which the unit-suite transform does not process.
22
+ *
23
+ * Order comes from `PROVIDERS`, not from the caller's flags, so the row cannot
24
+ * reshuffle between renders or between consumers.
25
+ */
26
+ export function visibleProviders(shown) {
27
+ return PROVIDERS.filter(({ key }) => shown[key]);
28
+ }
29
+ /**
30
+ * What a caller-supplied provider's button should draw.
31
+ *
32
+ * Pure, exported and separate from the component for the same reason
33
+ * {@link visibleProviders} is: the fallback rule is the part worth testing, and
34
+ * the component pulls in `@expo/vector-icons` and reanimated, which the unit
35
+ * suite's transform does not process.
36
+ *
37
+ * A blank or whitespace-only URL counts as absent, not as a URL to fetch: an
38
+ * operator who clears the field in a form leaves `""` behind far more often
39
+ * than `null`, and `<Image source={{uri: ""}}>` is a request that fails
40
+ * silently on web and renders an empty square on native.
41
+ */
42
+ export function customIcon(iconUrl, failed) {
43
+ const uri = iconUrl?.trim();
44
+ if (!uri || failed) {
45
+ return { kind: "fallback" };
46
+ }
47
+ return { kind: "image", uri };
48
+ }
16
49
  /**
17
50
  * Button row for federated sign-in: Entropy / Apple / Google / Microsoft.
18
51
  *
@@ -22,13 +55,18 @@ const PROVIDERS = [
22
55
  * (`/api/auth/oidc/start?connector=…`); the difference between them is a hint
23
56
  * passed to Access, not a separate flow.
24
57
  *
58
+ * To render only some providers, use the `show*` flags — one per provider, all
59
+ * defaulting to `true`. A consumer that knows which upstream connectors are
60
+ * configured (Entropy Access does, per tenant) should pass them, or it will
61
+ * draw brand marks for providers that cannot complete a sign-in.
62
+ *
25
63
  * The buttons are icon-only, so each carries a tooltip AND an
26
64
  * `accessibilityLabel`. The two are not redundant: the tooltip is for a sighted
27
65
  * user hovering on web or long-pressing on native, the label is what a screen
28
66
  * reader announces. Without the tooltip these are four unlabelled squares, and
29
67
  * a user has to guess which mark is which.
30
68
  */
31
- export function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, showEntropy = true, }) {
69
+ export function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, showEntropy = true, showApple = true, showGoogle = true, showMicrosoft = true, custom = [], }) {
32
70
  const { colorScheme } = useColorScheme();
33
71
  const isDark = colorScheme === "dark";
34
72
  const handlers = {
@@ -37,8 +75,19 @@ export function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, s
37
75
  google: onGoogle,
38
76
  microsoft: onMicrosoft,
39
77
  };
78
+ // One map rather than four `key !== "x" || showX` clauses: the filter used
79
+ // to name `entropy` explicitly, which read fine for one exception and would
80
+ // be four boolean conditions for four. `ProviderVisibility` requires every
81
+ // key, so adding a provider to PROVIDERS without deciding its flag is a type
82
+ // error rather than a button nobody can turn off.
83
+ const shown = {
84
+ entropy: showEntropy,
85
+ apple: showApple,
86
+ google: showGoogle,
87
+ microsoft: showMicrosoft,
88
+ };
40
89
  return (<View className="w-full flex-row items-center justify-center gap-3">
41
- {PROVIDERS.filter(({ key }) => key !== "entropy" || showEntropy).map(({ key, icon, label }) => (<Tooltip key={key} delayDuration={200}>
90
+ {visibleProviders(shown).map(({ key, icon, label }) => (<Tooltip key={key} delayDuration={200}>
42
91
  <TooltipTrigger asChild>
43
92
  <Pressable onPress={handlers[key]} className="h-12 w-12 items-center justify-center rounded-lg border border-border dark:border-neutral-600 active:opacity-70" accessibilityRole="button" accessibilityLabel={`Continue with ${label}`}>
44
93
  {icon ? (<Ionicons name={icon} size={22} color={isDark ? "#FFFFFF" : "#000000"}/>) : (
@@ -51,7 +100,39 @@ export function SocialConnections({ onApple, onGoogle, onMicrosoft, onEntropy, s
51
100
  </TooltipTrigger>
52
101
  <TooltipContent>{`Continue with ${label}`}</TooltipContent>
53
102
  </Tooltip>))}
103
+ {custom.map((provider) => (<CustomProviderButton
104
+ // Namespaced so a caller's key cannot collide with a built-in's and
105
+ // silently replace it in the reconciler.
106
+ key={`custom:${provider.key}`} provider={provider} isDark={isDark}/>))}
54
107
  </View>);
55
108
  }
109
+ /**
110
+ * One caller-supplied provider.
111
+ *
112
+ * Its own component because it holds state the built-ins do not: whether the
113
+ * remote icon failed. A broken URL has to degrade to the neutral glyph, and
114
+ * `onError` can only be observed per image.
115
+ */
116
+ function CustomProviderButton({ provider, isDark, }) {
117
+ const [iconFailed, setIconFailed] = React.useState(false);
118
+ const tint = isDark ? "#FFFFFF" : "#000000";
119
+ const icon = customIcon(provider.iconUrl, iconFailed);
120
+ return (<Tooltip delayDuration={200}>
121
+ <TooltipTrigger asChild>
122
+ <Pressable onPress={provider.onPress} className="h-12 w-12 items-center justify-center rounded-lg border border-border dark:border-neutral-600 active:opacity-70" accessibilityRole="button" accessibilityLabel={`Continue with ${provider.label}`}>
123
+ {icon.kind === "image" ? (<Image source={{ uri: icon.uri }} style={{ width: 22, height: 22 }}
124
+ // `contain` rather than `cover`: an operator's mark is whatever
125
+ // aspect ratio they have, and cropping somebody's logo to a
126
+ // square is worse than letterboxing it.
127
+ resizeMode="contain" onError={() => setIconFailed(true)} accessibilityIgnoresInvertColors/>) : (
128
+ // The neutral fallback. A key rather than a globe or a puzzle
129
+ // piece: this is a sign-in credential, and the glyph should say so
130
+ // without implying a vendor.
131
+ <Ionicons name="key-outline" size={22} color={tint}/>)}
132
+ </Pressable>
133
+ </TooltipTrigger>
134
+ <TooltipContent>{`Continue with ${provider.label}`}</TooltipContent>
135
+ </Tooltip>);
136
+ }
56
137
  export default SocialConnections;
57
138
  //# sourceMappingURL=social-connections.jsx.map
@@ -1 +1 @@
1
- {"version":3,"file":"social-connections.jsx","sourceRoot":"","sources":["../../src/components/social-connections.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAyCpE,MAAM,SAAS,GAAwB;IACrC,0EAA0E;IAC1E,yEAAyE;IACzE,sDAAsD;IACtD,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE;IAChD,EAAE,GAAG,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE;IACpD,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE;IACvD,EAAE,GAAG,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,EAAE;CACjE,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAChC,OAAO,EACP,QAAQ,EACR,WAAW,EACX,SAAS,EACT,WAAW,GAAG,IAAI,GACK;IACvB,MAAM,EAAE,WAAW,EAAE,GAAG,cAAc,EAAE,CAAC;IACzC,MAAM,MAAM,GAAG,WAAW,KAAK,MAAM,CAAC;IAEtC,MAAM,QAAQ,GAAkD;QAC9D,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,OAAO;QACd,MAAM,EAAE,QAAQ;QAChB,SAAS,EAAE,WAAW;KACvB,CAAC;IAEF,OAAO,CACL,CAAC,IAAI,CAAC,SAAS,CAAC,mDAAmD,CACjE;MAAA,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC,GAAG,KAAK,SAAS,IAAI,WAAW,CAAC,CAAC,GAAG,CAClE,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CACxB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CACpC;YAAA,CAAC,cAAc,CAAC,OAAO,CACrB;cAAA,CAAC,SAAS,CACR,OAAO,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CACvB,SAAS,CAAC,iHAAiH,CAC3H,iBAAiB,CAAC,QAAQ,CAC1B,kBAAkB,CAAC,CAAC,iBAAiB,KAAK,EAAE,CAAC,CAE7C;gBAAA,CAAC,IAAI,CAAC,CAAC,CAAC,CACN,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,EAAG,CAC1E,CAAC,CAAC,CAAC;YACF,6DAA6D;YAC7D,gEAAgE;YAChE,+DAA+D;YAC/D,sCAAsC;YACtC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAG,CACzC,CACH;cAAA,EAAE,SAAS,CACb;YAAA,EAAE,cAAc,CAChB;YAAA,CAAC,cAAc,CAAC,CAAC,iBAAiB,KAAK,EAAE,CAAC,EAAE,cAAc,CAC5D;UAAA,EAAE,OAAO,CAAC,CACX,CACF,CACH;IAAA,EAAE,IAAI,CAAC,CACR,CAAC;AACJ,CAAC;AAED,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"social-connections.jsx","sourceRoot":"","sources":["../../src/components/social-connections.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entropy-softworks/ui",
3
- "version": "2026.910.1",
3
+ "version": "2026.910.3",
4
4
  "private": false,
5
5
  "description": "Shared React Native UI primitives, auth screens, hooks, and services for Entropy Softworks Expo apps",
6
6
  "license": "MIT",
@@ -0,0 +1,132 @@
1
+ import {
2
+ customIcon,
3
+ visibleProviders,
4
+ type ProviderKey,
5
+ type ProviderVisibility,
6
+ } from "../social-connections";
7
+
8
+ // The component module imports view-layer deps (`@expo/vector-icons`,
9
+ // `react-native-reanimated` via Tooltip/EntropyMark) that the unit-suite babel
10
+ // transform does not process. The shared setup stubs `react-native` /
11
+ // `nativewind`; stub the rest here so the pure helper under test loads without
12
+ // a render runtime. Same pattern as the pagination test.
13
+ jest.mock("@expo/vector-icons", () => ({ Ionicons: "Ionicons" }));
14
+ // `react-native-svg` is not in the transform whitelist, so its TypeScript
15
+ // source reaches jest untranspiled; EntropyMark pulls it in for the brand mark.
16
+ jest.mock("react-native-svg", () => ({
17
+ __esModule: true,
18
+ default: "Svg",
19
+ Path: "Path",
20
+ }));
21
+ jest.mock("react-native-reanimated", () => ({
22
+ __esModule: true,
23
+ default: { View: "Animated.View" },
24
+ useSharedValue: () => ({ value: 0 }),
25
+ useAnimatedStyle: () => ({}),
26
+ withTiming: (v: number) => v,
27
+ }));
28
+
29
+ const ALL: ProviderVisibility = {
30
+ entropy: true,
31
+ apple: true,
32
+ google: true,
33
+ microsoft: true,
34
+ };
35
+
36
+ const keysOf = (shown: ProviderVisibility): ProviderKey[] =>
37
+ visibleProviders(shown).map(({ key }) => key);
38
+
39
+ describe("visibleProviders", () => {
40
+ it("renders every provider when all flags are set", () => {
41
+ // The component's defaults are all `true`, so this is what an existing
42
+ // caller that passes no `show*` prop still gets.
43
+ expect(keysOf(ALL)).toEqual(["entropy", "apple", "google", "microsoft"]);
44
+ });
45
+
46
+ it.each<ProviderKey>(["entropy", "apple", "google", "microsoft"])(
47
+ "suppresses only %s when its flag is false",
48
+ (key) => {
49
+ const got = keysOf({ ...ALL, [key]: false });
50
+ expect(got).not.toContain(key);
51
+ expect(got).toHaveLength(3);
52
+ }
53
+ );
54
+
55
+ it("renders one provider when only its flag is set", () => {
56
+ // The case that motivated per-provider flags: a tenant with a Google
57
+ // connector and nothing else drew Apple and Microsoft as dead buttons.
58
+ expect(
59
+ keysOf({ entropy: false, apple: false, google: true, microsoft: false })
60
+ ).toEqual(["google"]);
61
+ });
62
+
63
+ it("returns nothing when every flag is false", () => {
64
+ // A caller that hides all four gets an empty row, not a stray gap-spaced
65
+ // container with one invisible child — the consumer is expected to skip
66
+ // rendering the component at all, and this is what it can check against.
67
+ expect(
68
+ keysOf({
69
+ entropy: false,
70
+ apple: false,
71
+ google: false,
72
+ microsoft: false,
73
+ })
74
+ ).toEqual([]);
75
+ });
76
+
77
+ it("keeps PROVIDERS order regardless of the flag object's key order", () => {
78
+ // Order comes from the module's provider list, not from the caller, so the
79
+ // row cannot reshuffle between consumers that build the map differently.
80
+ const reversed: ProviderVisibility = {
81
+ microsoft: true,
82
+ google: true,
83
+ apple: true,
84
+ entropy: true,
85
+ };
86
+ expect(keysOf(reversed)).toEqual(keysOf(ALL));
87
+ });
88
+
89
+ it("carries the brand mark opt-out only for entropy", () => {
90
+ // `icon: null` is what tells the component to draw EntropyMark instead of
91
+ // an Ionicons glyph; the other three must keep a glyph name.
92
+ const byKey = new Map(visibleProviders(ALL).map((p) => [p.key, p]));
93
+ expect(byKey.get("entropy")?.icon).toBeNull();
94
+ expect(byKey.get("apple")?.icon).toBe("logo-apple");
95
+ expect(byKey.get("google")?.icon).toBe("logo-google");
96
+ expect(byKey.get("microsoft")?.icon).toBe("logo-microsoft");
97
+ });
98
+ });
99
+
100
+ describe("customIcon", () => {
101
+ it("uses the operator's URL when there is one", () => {
102
+ expect(customIcon("https://cdn.example/okta.svg", false)).toEqual({
103
+ kind: "image",
104
+ uri: "https://cdn.example/okta.svg",
105
+ });
106
+ });
107
+
108
+ it("trims, because a pasted URL usually arrives with whitespace", () => {
109
+ expect(customIcon(" https://cdn.example/k.png\n", false)).toEqual({
110
+ kind: "image",
111
+ uri: "https://cdn.example/k.png",
112
+ });
113
+ });
114
+
115
+ it.each([undefined, null, "", " "])(
116
+ "falls back when the URL is %p",
117
+ (value) => {
118
+ // A blank string is the case that matters: clearing a form field leaves
119
+ // "" far more often than null, and `<Image source={{uri: ""}}>` renders
120
+ // an empty square rather than failing loudly.
121
+ expect(customIcon(value, false)).toEqual({ kind: "fallback" });
122
+ }
123
+ );
124
+
125
+ it("falls back once the image has failed, URL or not", () => {
126
+ // An unreachable or malformed URL must still leave a pressable, labelled
127
+ // button — a connector the operator can see and use.
128
+ expect(customIcon("https://cdn.example/gone.png", true)).toEqual({
129
+ kind: "fallback",
130
+ });
131
+ });
132
+ });
@@ -129,7 +129,16 @@ export { TotpDisplay, type TotpDisplayProps } from "./totp-display";
129
129
 
130
130
  export { UserAvatar, type UserAvatarProps } from "./user-avatar";
131
131
 
132
- export { SocialConnections, type SocialConnectionsProps } from "./social-connections";
132
+ export {
133
+ SocialConnections,
134
+ visibleProviders,
135
+ customIcon,
136
+ type SocialConnectionsProps,
137
+ type Provider,
138
+ type ProviderKey,
139
+ type ProviderVisibility,
140
+ type CustomProvider,
141
+ } from "./social-connections";
133
142
 
134
143
  export {
135
144
  ProfileDialog,
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { Pressable, View } from "react-native";
2
+ import { Image, Pressable, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
4
  import { useColorScheme } from "nativewind";
5
5
  import { EntropyMark } from "./entropy-mark";
@@ -33,11 +33,77 @@ export interface SocialConnectionsProps {
33
33
  * button away from consumers that want the placeholder.
34
34
  */
35
35
  showEntropy?: boolean;
36
+ /**
37
+ * Whether to offer "Continue with Apple". Defaults to `true`.
38
+ *
39
+ * ## Why these three needed flags of their own
40
+ *
41
+ * Apple, Google and Microsoft used to render unconditionally, so a consumer
42
+ * could suppress the whole row or none of it. That is the wrong granularity
43
+ * for an identity provider whose upstream connectors are configured per
44
+ * tenant: a tenant with only a Google connector still drew Apple and
45
+ * Microsoft, as two squares that respond to a press by doing nothing.
46
+ *
47
+ * `undefined` handlers do not hide a button, and that is deliberate and
48
+ * unchanged — a handler-less provider is the "coming soon" placeholder the
49
+ * consumer apps use during a staged rollout, and simple-finance renders the
50
+ * whole row with no handlers at all. So intent cannot be inferred from a
51
+ * missing handler; it has to be said. Same shape and same default as
52
+ * {@link SocialConnectionsProps.showEntropy}, so existing callers are
53
+ * unaffected.
54
+ */
55
+ showApple?: boolean;
56
+ /** Whether to offer "Continue with Google". Defaults to `true`. See {@link SocialConnectionsProps.showApple}. */
57
+ showGoogle?: boolean;
58
+ /** Whether to offer "Continue with Microsoft". Defaults to `true`. See {@link SocialConnectionsProps.showApple}. */
59
+ showMicrosoft?: boolean;
60
+ /**
61
+ * Providers this component has no brand mark for, rendered after the four it
62
+ * does — Okta, Keycloak, GitHub, another Access instance, anything an
63
+ * operator configures.
64
+ *
65
+ * ## Why these are a caller-supplied list rather than more `ProviderKey`s
66
+ *
67
+ * The four above are a closed set with bundled glyphs and a fixed order, so
68
+ * they can be named in the type. What an operator connects is open: adding a
69
+ * key here for every provider anyone might configure is a release of this
70
+ * package per connector, and the whole point of the identity provider's
71
+ * connector table is that it does not need one.
72
+ *
73
+ * Ordered as given, after the built-ins, so the row is stable — the
74
+ * built-ins keep the positions users have learned and new connectors append.
75
+ *
76
+ * `iconUrl` is optional and points at whatever the operator supplied. Absent,
77
+ * unreachable, or malformed, the button falls back to a neutral glyph rather
78
+ * than an empty square: a connector with no icon must still be pressable and
79
+ * still be labelled.
80
+ */
81
+ custom?: readonly CustomProvider[];
36
82
  }
37
83
 
38
- type ProviderKey = "entropy" | "apple" | "google" | "microsoft";
84
+ /**
85
+ * A provider this package does not ship a mark for.
86
+ *
87
+ * `key` only has to be unique within the row; it is not a {@link ProviderKey}
88
+ * and is never matched against one. Prefix collisions with the built-ins are
89
+ * impossible because the rendered React keys are namespaced.
90
+ */
91
+ export interface CustomProvider {
92
+ key: string;
93
+ /** What the tooltip and the screen reader say: "Continue with {label}". */
94
+ label: string;
95
+ /**
96
+ * Absolute URL of the provider's mark. Rendered at 22px square, matching the
97
+ * bundled glyphs.
98
+ */
99
+ iconUrl?: string | null;
100
+ onPress?: () => void;
101
+ }
102
+
103
+ /** The federated providers this row can draw. */
104
+ export type ProviderKey = "entropy" | "apple" | "google" | "microsoft";
39
105
 
40
- interface Provider {
106
+ export interface Provider {
41
107
  key: ProviderKey;
42
108
  label: string;
43
109
  /** Ionicons glyph, or `null` for Entropy, which draws its own brand mark. */
@@ -54,6 +120,50 @@ const PROVIDERS: readonly Provider[] = [
54
120
  { key: "microsoft", label: "Microsoft", icon: "logo-microsoft" },
55
121
  ];
56
122
 
123
+ /** Which providers each `show*` flag governs. Every key is required, so a new
124
+ * provider in {@link PROVIDERS} cannot be added without deciding its flag. */
125
+ export type ProviderVisibility = Record<ProviderKey, boolean>;
126
+
127
+ /**
128
+ * The providers to render, in {@link PROVIDERS} order.
129
+ *
130
+ * Exported and pure so the visibility rules are testable without a render
131
+ * runtime — the component itself pulls in `@expo/vector-icons` and reanimated,
132
+ * which the unit-suite transform does not process.
133
+ *
134
+ * Order comes from `PROVIDERS`, not from the caller's flags, so the row cannot
135
+ * reshuffle between renders or between consumers.
136
+ */
137
+ export function visibleProviders(
138
+ shown: ProviderVisibility
139
+ ): readonly Provider[] {
140
+ return PROVIDERS.filter(({ key }) => shown[key]);
141
+ }
142
+
143
+ /**
144
+ * What a caller-supplied provider's button should draw.
145
+ *
146
+ * Pure, exported and separate from the component for the same reason
147
+ * {@link visibleProviders} is: the fallback rule is the part worth testing, and
148
+ * the component pulls in `@expo/vector-icons` and reanimated, which the unit
149
+ * suite's transform does not process.
150
+ *
151
+ * A blank or whitespace-only URL counts as absent, not as a URL to fetch: an
152
+ * operator who clears the field in a form leaves `""` behind far more often
153
+ * than `null`, and `<Image source={{uri: ""}}>` is a request that fails
154
+ * silently on web and renders an empty square on native.
155
+ */
156
+ export function customIcon(
157
+ iconUrl: string | null | undefined,
158
+ failed: boolean
159
+ ): { kind: "image"; uri: string } | { kind: "fallback" } {
160
+ const uri = iconUrl?.trim();
161
+ if (!uri || failed) {
162
+ return { kind: "fallback" };
163
+ }
164
+ return { kind: "image", uri };
165
+ }
166
+
57
167
  /**
58
168
  * Button row for federated sign-in: Entropy / Apple / Google / Microsoft.
59
169
  *
@@ -63,6 +173,11 @@ const PROVIDERS: readonly Provider[] = [
63
173
  * (`/api/auth/oidc/start?connector=…`); the difference between them is a hint
64
174
  * passed to Access, not a separate flow.
65
175
  *
176
+ * To render only some providers, use the `show*` flags — one per provider, all
177
+ * defaulting to `true`. A consumer that knows which upstream connectors are
178
+ * configured (Entropy Access does, per tenant) should pass them, or it will
179
+ * draw brand marks for providers that cannot complete a sign-in.
180
+ *
66
181
  * The buttons are icon-only, so each carries a tooltip AND an
67
182
  * `accessibilityLabel`. The two are not redundant: the tooltip is for a sighted
68
183
  * user hovering on web or long-pressing on native, the label is what a screen
@@ -75,6 +190,10 @@ export function SocialConnections({
75
190
  onMicrosoft,
76
191
  onEntropy,
77
192
  showEntropy = true,
193
+ showApple = true,
194
+ showGoogle = true,
195
+ showMicrosoft = true,
196
+ custom = [],
78
197
  }: SocialConnectionsProps): React.JSX.Element {
79
198
  const { colorScheme } = useColorScheme();
80
199
  const isDark = colorScheme === "dark";
@@ -86,9 +205,21 @@ export function SocialConnections({
86
205
  microsoft: onMicrosoft,
87
206
  };
88
207
 
208
+ // One map rather than four `key !== "x" || showX` clauses: the filter used
209
+ // to name `entropy` explicitly, which read fine for one exception and would
210
+ // be four boolean conditions for four. `ProviderVisibility` requires every
211
+ // key, so adding a provider to PROVIDERS without deciding its flag is a type
212
+ // error rather than a button nobody can turn off.
213
+ const shown: ProviderVisibility = {
214
+ entropy: showEntropy,
215
+ apple: showApple,
216
+ google: showGoogle,
217
+ microsoft: showMicrosoft,
218
+ };
219
+
89
220
  return (
90
221
  <View className="w-full flex-row items-center justify-center gap-3">
91
- {PROVIDERS.filter(({ key }) => key !== "entropy" || showEntropy).map(
222
+ {visibleProviders(shown).map(
92
223
  ({ key, icon, label }) => (
93
224
  <Tooltip key={key} delayDuration={200}>
94
225
  <TooltipTrigger asChild>
@@ -113,8 +244,68 @@ export function SocialConnections({
113
244
  </Tooltip>
114
245
  )
115
246
  )}
247
+ {custom.map((provider) => (
248
+ <CustomProviderButton
249
+ // Namespaced so a caller's key cannot collide with a built-in's and
250
+ // silently replace it in the reconciler.
251
+ key={`custom:${provider.key}`}
252
+ provider={provider}
253
+ isDark={isDark}
254
+ />
255
+ ))}
116
256
  </View>
117
257
  );
118
258
  }
119
259
 
260
+ /**
261
+ * One caller-supplied provider.
262
+ *
263
+ * Its own component because it holds state the built-ins do not: whether the
264
+ * remote icon failed. A broken URL has to degrade to the neutral glyph, and
265
+ * `onError` can only be observed per image.
266
+ */
267
+ function CustomProviderButton({
268
+ provider,
269
+ isDark,
270
+ }: {
271
+ provider: CustomProvider;
272
+ isDark: boolean;
273
+ }): React.JSX.Element {
274
+ const [iconFailed, setIconFailed] = React.useState(false);
275
+ const tint = isDark ? "#FFFFFF" : "#000000";
276
+ const icon = customIcon(provider.iconUrl, iconFailed);
277
+
278
+ return (
279
+ <Tooltip delayDuration={200}>
280
+ <TooltipTrigger asChild>
281
+ <Pressable
282
+ onPress={provider.onPress}
283
+ className="h-12 w-12 items-center justify-center rounded-lg border border-border dark:border-neutral-600 active:opacity-70"
284
+ accessibilityRole="button"
285
+ accessibilityLabel={`Continue with ${provider.label}`}
286
+ >
287
+ {icon.kind === "image" ? (
288
+ <Image
289
+ source={{ uri: icon.uri }}
290
+ style={{ width: 22, height: 22 }}
291
+ // `contain` rather than `cover`: an operator's mark is whatever
292
+ // aspect ratio they have, and cropping somebody's logo to a
293
+ // square is worse than letterboxing it.
294
+ resizeMode="contain"
295
+ onError={() => setIconFailed(true)}
296
+ accessibilityIgnoresInvertColors
297
+ />
298
+ ) : (
299
+ // The neutral fallback. A key rather than a globe or a puzzle
300
+ // piece: this is a sign-in credential, and the glyph should say so
301
+ // without implying a vendor.
302
+ <Ionicons name="key-outline" size={22} color={tint} />
303
+ )}
304
+ </Pressable>
305
+ </TooltipTrigger>
306
+ <TooltipContent>{`Continue with ${provider.label}`}</TooltipContent>
307
+ </Tooltip>
308
+ );
309
+ }
310
+
120
311
  export default SocialConnections;