@wasto/cms-components 1.5.15 → 1.5.18

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.mts CHANGED
@@ -95,9 +95,11 @@ type AppHeaderLogoElement = {
95
95
  id: string;
96
96
  type: "logo";
97
97
  slot: AppHeaderSlot;
98
- sourceType: "url" | "media";
98
+ sourceType: "url" | "media" | "bundled";
99
99
  url?: string;
100
100
  mediaId?: string;
101
+ /** `require(...)` d’un asset embarqué à l’export (ImageSource). */
102
+ module?: number;
101
103
  };
102
104
  type AppHeaderTitleElement = {
103
105
  id: string;
@@ -133,15 +135,20 @@ type HeaderProps = {
133
135
  canGoBack: () => boolean;
134
136
  goBack: () => void;
135
137
  };
136
- /** Flotte au-dessus du contenu (ex. carte) : fond transparent. */
138
+ /** Flotte au-dessus du contenu (ex. carte) : fond transparent + safe-area top. */
137
139
  readonly overlay?: boolean;
138
140
  /**
139
141
  * Config layout (`header.elements`) — logo / actions.
140
142
  * Si fournie (même vide), le fallback titre d’écran ne s’applique pas.
141
143
  */
142
144
  readonly headerConfig?: AppHeaderConfig | null;
145
+ /**
146
+ * Padding safe-area top. Défaut `true` (header custom RN = edge-to-edge,
147
+ * il faut l’inset). Passer `false` seulement si le parent inset déjà.
148
+ */
149
+ readonly safeTop?: boolean;
143
150
  };
144
- declare function Header({ options, route, navigation, overlay, headerConfig, }: HeaderProps): ReactNode;
151
+ declare function Header({ options, route, navigation, overlay, headerConfig, safeTop, }: HeaderProps): ReactNode;
145
152
 
146
153
  type LocationsMapProps = {
147
154
  fullScreen?: boolean;
package/dist/index.d.ts CHANGED
@@ -95,9 +95,11 @@ type AppHeaderLogoElement = {
95
95
  id: string;
96
96
  type: "logo";
97
97
  slot: AppHeaderSlot;
98
- sourceType: "url" | "media";
98
+ sourceType: "url" | "media" | "bundled";
99
99
  url?: string;
100
100
  mediaId?: string;
101
+ /** `require(...)` d’un asset embarqué à l’export (ImageSource). */
102
+ module?: number;
101
103
  };
102
104
  type AppHeaderTitleElement = {
103
105
  id: string;
@@ -133,15 +135,20 @@ type HeaderProps = {
133
135
  canGoBack: () => boolean;
134
136
  goBack: () => void;
135
137
  };
136
- /** Flotte au-dessus du contenu (ex. carte) : fond transparent. */
138
+ /** Flotte au-dessus du contenu (ex. carte) : fond transparent + safe-area top. */
137
139
  readonly overlay?: boolean;
138
140
  /**
139
141
  * Config layout (`header.elements`) — logo / actions.
140
142
  * Si fournie (même vide), le fallback titre d’écran ne s’applique pas.
141
143
  */
142
144
  readonly headerConfig?: AppHeaderConfig | null;
145
+ /**
146
+ * Padding safe-area top. Défaut `true` (header custom RN = edge-to-edge,
147
+ * il faut l’inset). Passer `false` seulement si le parent inset déjà.
148
+ */
149
+ readonly safeTop?: boolean;
143
150
  };
144
- declare function Header({ options, route, navigation, overlay, headerConfig, }: HeaderProps): ReactNode;
151
+ declare function Header({ options, route, navigation, overlay, headerConfig, safeTop, }: HeaderProps): ReactNode;
145
152
 
146
153
  type LocationsMapProps = {
147
154
  fullScreen?: boolean;
package/dist/index.js CHANGED
@@ -8264,6 +8264,7 @@ var DEFAULT_HEADER_ACTIONS = [
8264
8264
  }
8265
8265
  ];
8266
8266
  function resolveLogoUri(apiUrl, el) {
8267
+ if (el.sourceType === "bundled") return null;
8267
8268
  const url = (el.url || "").trim();
8268
8269
  if (url) return url;
8269
8270
  if (el.sourceType === "media" || el.mediaId) {
@@ -8286,6 +8287,17 @@ function HeaderLogo({
8286
8287
  element
8287
8288
  }) {
8288
8289
  const { apiUrl } = useWasto();
8290
+ if (element.sourceType === "bundled" && element.module != null) {
8291
+ return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
8292
+ import_react_native34.Image,
8293
+ {
8294
+ source: element.module,
8295
+ style: styles29.logo,
8296
+ resizeMode: "contain",
8297
+ accessibilityLabel: "Logo"
8298
+ }
8299
+ );
8300
+ }
8289
8301
  const uri = resolveLogoUri(apiUrl, element);
8290
8302
  if (!uri) return null;
8291
8303
  return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(HeaderLogoImage, { uri });
@@ -8295,8 +8307,10 @@ function CityLogoFallback() {
8295
8307
  const appData = useOptionalAppData();
8296
8308
  const logoMediaId = typeof appData?.city?.logoMediaId === "string" ? appData.city.logoMediaId : null;
8297
8309
  const uri = publicMediaUrl(apiUrl, logoMediaId);
8298
- if (!uri) return null;
8299
- return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(HeaderLogoImage, { uri });
8310
+ if (uri) return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(HeaderLogoImage, { uri });
8311
+ const cityName = typeof appData?.city?.name === "string" ? appData.city.name.trim() : "";
8312
+ if (cityName) return /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(Title, { text: cityName, level: "1" });
8313
+ return null;
8300
8314
  }
8301
8315
  function HeaderActionButton({
8302
8316
  onPress,
@@ -8320,14 +8334,17 @@ function Header({
8320
8334
  route,
8321
8335
  navigation,
8322
8336
  overlay = false,
8323
- headerConfig
8337
+ headerConfig,
8338
+ safeTop
8324
8339
  }) {
8325
8340
  const { theme } = useTheme();
8326
8341
  const t = resolveTheme(theme);
8327
8342
  const themedStyles = createThemedStyles14(theme);
8328
- const topInset = useSafeAreaPadding(["top"]);
8343
+ const applyTopInset = safeTop ?? true;
8344
+ const topInset = useSafeAreaPadding(applyTopInset ? ["top"] : []);
8329
8345
  const { hasUnread } = useAnnouncements();
8330
8346
  const { push } = useAppNavigation();
8347
+ const { apiUrl } = useWasto();
8331
8348
  const screenTitle = (0, import_react35.useMemo)(
8332
8349
  () => options.title ?? route.name,
8333
8350
  [options.title, route.name]
@@ -8338,7 +8355,11 @@ function Header({
8338
8355
  return raw.filter((el) => el.type !== "title");
8339
8356
  }, [headerConfig]);
8340
8357
  const hasCustomActions = elements.some((el) => el.type === "action");
8341
- const hasLogo = elements.some((el) => el.type === "logo");
8358
+ const hasResolvableLogo = elements.some((el) => {
8359
+ if (el.type !== "logo") return false;
8360
+ if (el.sourceType === "bundled" && el.module != null) return true;
8361
+ return resolveLogoUri(apiUrl, el) != null;
8362
+ });
8342
8363
  const bySlot = (0, import_react35.useMemo)(() => {
8343
8364
  const left = [];
8344
8365
  const center = [];
@@ -8461,7 +8482,7 @@ function Header({
8461
8482
  }
8462
8483
  ) : null,
8463
8484
  bySlot.left.map(renderElement),
8464
- !hasLogo ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CityLogoFallback, {}) : null
8485
+ !hasResolvableLogo ? /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(CityLogoFallback, {}) : null
8465
8486
  ] }),
8466
8487
  /* @__PURE__ */ (0, import_jsx_runtime41.jsx)(
8467
8488
  import_react_native34.View,
package/dist/index.mjs CHANGED
@@ -8342,6 +8342,7 @@ var DEFAULT_HEADER_ACTIONS = [
8342
8342
  }
8343
8343
  ];
8344
8344
  function resolveLogoUri(apiUrl, el) {
8345
+ if (el.sourceType === "bundled") return null;
8345
8346
  const url = (el.url || "").trim();
8346
8347
  if (url) return url;
8347
8348
  if (el.sourceType === "media" || el.mediaId) {
@@ -8364,6 +8365,17 @@ function HeaderLogo({
8364
8365
  element
8365
8366
  }) {
8366
8367
  const { apiUrl } = useWasto();
8368
+ if (element.sourceType === "bundled" && element.module != null) {
8369
+ return /* @__PURE__ */ jsx41(
8370
+ Image11,
8371
+ {
8372
+ source: element.module,
8373
+ style: styles29.logo,
8374
+ resizeMode: "contain",
8375
+ accessibilityLabel: "Logo"
8376
+ }
8377
+ );
8378
+ }
8367
8379
  const uri = resolveLogoUri(apiUrl, element);
8368
8380
  if (!uri) return null;
8369
8381
  return /* @__PURE__ */ jsx41(HeaderLogoImage, { uri });
@@ -8373,8 +8385,10 @@ function CityLogoFallback() {
8373
8385
  const appData = useOptionalAppData();
8374
8386
  const logoMediaId = typeof appData?.city?.logoMediaId === "string" ? appData.city.logoMediaId : null;
8375
8387
  const uri = publicMediaUrl(apiUrl, logoMediaId);
8376
- if (!uri) return null;
8377
- return /* @__PURE__ */ jsx41(HeaderLogoImage, { uri });
8388
+ if (uri) return /* @__PURE__ */ jsx41(HeaderLogoImage, { uri });
8389
+ const cityName = typeof appData?.city?.name === "string" ? appData.city.name.trim() : "";
8390
+ if (cityName) return /* @__PURE__ */ jsx41(Title, { text: cityName, level: "1" });
8391
+ return null;
8378
8392
  }
8379
8393
  function HeaderActionButton({
8380
8394
  onPress,
@@ -8398,14 +8412,17 @@ function Header({
8398
8412
  route,
8399
8413
  navigation,
8400
8414
  overlay = false,
8401
- headerConfig
8415
+ headerConfig,
8416
+ safeTop
8402
8417
  }) {
8403
8418
  const { theme } = useTheme();
8404
8419
  const t = resolveTheme(theme);
8405
8420
  const themedStyles = createThemedStyles14(theme);
8406
- const topInset = useSafeAreaPadding(["top"]);
8421
+ const applyTopInset = safeTop ?? true;
8422
+ const topInset = useSafeAreaPadding(applyTopInset ? ["top"] : []);
8407
8423
  const { hasUnread } = useAnnouncements();
8408
8424
  const { push } = useAppNavigation();
8425
+ const { apiUrl } = useWasto();
8409
8426
  const screenTitle = useMemo20(
8410
8427
  () => options.title ?? route.name,
8411
8428
  [options.title, route.name]
@@ -8416,7 +8433,11 @@ function Header({
8416
8433
  return raw.filter((el) => el.type !== "title");
8417
8434
  }, [headerConfig]);
8418
8435
  const hasCustomActions = elements.some((el) => el.type === "action");
8419
- const hasLogo = elements.some((el) => el.type === "logo");
8436
+ const hasResolvableLogo = elements.some((el) => {
8437
+ if (el.type !== "logo") return false;
8438
+ if (el.sourceType === "bundled" && el.module != null) return true;
8439
+ return resolveLogoUri(apiUrl, el) != null;
8440
+ });
8420
8441
  const bySlot = useMemo20(() => {
8421
8442
  const left = [];
8422
8443
  const center = [];
@@ -8539,7 +8560,7 @@ function Header({
8539
8560
  }
8540
8561
  ) : null,
8541
8562
  bySlot.left.map(renderElement),
8542
- !hasLogo ? /* @__PURE__ */ jsx41(CityLogoFallback, {}) : null
8563
+ !hasResolvableLogo ? /* @__PURE__ */ jsx41(CityLogoFallback, {}) : null
8543
8564
  ] }),
8544
8565
  /* @__PURE__ */ jsx41(
8545
8566
  View29,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wasto/cms-components",
3
- "version": "1.5.15",
3
+ "version": "1.5.18",
4
4
  "description": "Wast'O CMS React Native components",
5
5
  "main": "./src/index.ts",
6
6
  "module": "./src/index.ts",
@@ -27,9 +27,11 @@ export type AppHeaderLogoElement = {
27
27
  id: string;
28
28
  type: "logo";
29
29
  slot: AppHeaderSlot;
30
- sourceType: "url" | "media";
30
+ sourceType: "url" | "media" | "bundled";
31
31
  url?: string;
32
32
  mediaId?: string;
33
+ /** `require(...)` d’un asset embarqué à l’export (ImageSource). */
34
+ module?: number;
33
35
  };
34
36
 
35
37
  export type AppHeaderTitleElement = {
@@ -86,19 +88,25 @@ export type HeaderProps = {
86
88
  canGoBack: () => boolean;
87
89
  goBack: () => void;
88
90
  };
89
- /** Flotte au-dessus du contenu (ex. carte) : fond transparent. */
91
+ /** Flotte au-dessus du contenu (ex. carte) : fond transparent + safe-area top. */
90
92
  readonly overlay?: boolean;
91
93
  /**
92
94
  * Config layout (`header.elements`) — logo / actions.
93
95
  * Si fournie (même vide), le fallback titre d’écran ne s’applique pas.
94
96
  */
95
97
  readonly headerConfig?: AppHeaderConfig | null;
98
+ /**
99
+ * Padding safe-area top. Défaut `true` (header custom RN = edge-to-edge,
100
+ * il faut l’inset). Passer `false` seulement si le parent inset déjà.
101
+ */
102
+ readonly safeTop?: boolean;
96
103
  };
97
104
 
98
105
  function resolveLogoUri(
99
106
  apiUrl: string,
100
107
  el: AppHeaderLogoElement,
101
108
  ): string | null {
109
+ if (el.sourceType === "bundled") return null;
102
110
  const url = (el.url || "").trim();
103
111
  if (url) return url;
104
112
  // Fallback média (export parfois laisse mediaId si rewrite manqué)
@@ -125,6 +133,16 @@ function HeaderLogo({
125
133
  readonly element: AppHeaderLogoElement;
126
134
  }): ReactNode {
127
135
  const { apiUrl } = useWasto();
136
+ if (element.sourceType === "bundled" && element.module != null) {
137
+ return (
138
+ <Image
139
+ source={element.module}
140
+ style={styles.logo as ImageStyle}
141
+ resizeMode="contain"
142
+ accessibilityLabel="Logo"
143
+ />
144
+ );
145
+ }
128
146
  const uri = resolveLogoUri(apiUrl, element);
129
147
  if (!uri) return null;
130
148
  return <HeaderLogoImage uri={uri} />;
@@ -138,8 +156,11 @@ function CityLogoFallback(): ReactNode {
138
156
  ? appData.city.logoMediaId
139
157
  : null;
140
158
  const uri = publicMediaUrl(apiUrl, logoMediaId);
141
- if (!uri) return null;
142
- return <HeaderLogoImage uri={uri} />;
159
+ if (uri) return <HeaderLogoImage uri={uri} />;
160
+ const cityName =
161
+ typeof appData?.city?.name === "string" ? appData.city.name.trim() : "";
162
+ if (cityName) return <Title text={cityName} level="1" />;
163
+ return null;
143
164
  }
144
165
 
145
166
  function HeaderActionButton({
@@ -172,13 +193,17 @@ export function Header({
172
193
  navigation,
173
194
  overlay = false,
174
195
  headerConfig,
196
+ safeTop,
175
197
  }: HeaderProps): ReactNode {
176
198
  const { theme } = useTheme();
177
199
  const t = resolveTheme(theme);
178
200
  const themedStyles = createThemedStyles(theme);
179
- const topInset = useSafeAreaPadding(["top"]);
201
+ // Header custom = plein écran sous le notch → inset requis (Page ne le re-ajoute plus).
202
+ const applyTopInset = safeTop ?? true;
203
+ const topInset = useSafeAreaPadding(applyTopInset ? ["top"] : []);
180
204
  const { hasUnread } = useAnnouncements();
181
205
  const { push } = useAppNavigation();
206
+ const { apiUrl } = useWasto();
182
207
 
183
208
  const screenTitle = useMemo(
184
209
  () => options.title ?? route.name,
@@ -193,7 +218,12 @@ export function Header({
193
218
  return raw.filter((el) => el.type !== "title");
194
219
  }, [headerConfig]);
195
220
  const hasCustomActions = elements.some((el) => el.type === "action");
196
- const hasLogo = elements.some((el) => el.type === "logo");
221
+ // Logo CMS sans URI / sans module bundlé → fallback ville.
222
+ const hasResolvableLogo = elements.some((el) => {
223
+ if (el.type !== "logo") return false;
224
+ if (el.sourceType === "bundled" && el.module != null) return true;
225
+ return resolveLogoUri(apiUrl, el) != null;
226
+ });
197
227
 
198
228
  const bySlot = useMemo(() => {
199
229
  const left: AppHeaderElement[] = [];
@@ -320,7 +350,7 @@ export function Header({
320
350
  </HeaderActionButton>
321
351
  ) : null}
322
352
  {bySlot.left.map(renderElement)}
323
- {!hasLogo ? <CityLogoFallback /> : null}
353
+ {!hasResolvableLogo ? <CityLogoFallback /> : null}
324
354
  </View>
325
355
  <View
326
356
  style={styles.slotCenter}