@nextelco/common-ui 2.1.103 → 2.1.105

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 (53) hide show
  1. package/dist/esm/assets/imagem-6ee08a37d9.png +0 -0
  2. package/dist/esm/assets/imagem-6fcae1b87b.png +0 -0
  3. package/dist/esm/index.js +142 -119
  4. package/dist/esm/index.js.map +1 -1
  5. package/dist/esm/legacy/components/layout/Header/Header.css +1 -1
  6. package/dist/esm/legacy/components/molecules/PhotoAvatar/PhotoAvatar.js +9 -4
  7. package/dist/esm/legacy/components/molecules/PhotoAvatar/PhotoAvatar.js.map +1 -1
  8. package/dist/esm/legacy/components/molecules/Sidebar/Sidebar.css +1 -1
  9. package/dist/esm/legacy/components/organisms/RoleGuard/RoleGuard.js +14 -3
  10. package/dist/esm/legacy/components/organisms/RoleGuard/RoleGuard.js.map +1 -1
  11. package/dist/esm/legacy/pages/Error/ErrorPage.css +1 -1
  12. package/dist/esm/legacy/pages/Loading/Loading.css +2 -2
  13. package/dist/esm/legacy/pages/NotFound/NotFound.css +1 -1
  14. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js +214 -0
  15. package/dist/esm/v1/components/lists/ListFinder/ListFinder.js.map +1 -0
  16. package/dist/esm/v1/components/lists/ListFinder/labels.js +18 -0
  17. package/dist/esm/v1/components/lists/ListFinder/labels.js.map +1 -0
  18. package/dist/esm/v1/components/tables/PaginatedTable/PaginatedTable.js +50 -0
  19. package/dist/esm/v1/components/tables/PaginatedTable/PaginatedTable.js.map +1 -0
  20. package/dist/esm/v1/components/tables/Table/ColumnFilter.js +292 -0
  21. package/dist/esm/v1/components/tables/Table/ColumnFilter.js.map +1 -0
  22. package/dist/esm/v1/components/tables/Table/OverlayScrollbar.js +129 -0
  23. package/dist/esm/v1/components/tables/Table/OverlayScrollbar.js.map +1 -0
  24. package/dist/esm/v1/components/tables/Table/Table.js +1410 -0
  25. package/dist/esm/v1/components/tables/Table/Table.js.map +1 -0
  26. package/dist/esm/v1/components/tables/Table/labels.js +44 -0
  27. package/dist/esm/v1/components/tables/Table/labels.js.map +1 -0
  28. package/dist/esm/v1/foundations/icons/icons.js +45 -2
  29. package/dist/esm/v1/foundations/icons/icons.js.map +1 -1
  30. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js +32 -0
  31. package/dist/esm/v1/primitives/forms/Checkbox/Checkbox.js.map +1 -0
  32. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js +28 -0
  33. package/dist/esm/v1/primitives/lists/ListItem/ListCheckbox.js.map +1 -0
  34. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js +39 -0
  35. package/dist/esm/v1/primitives/lists/ListItem/ListItem.js.map +1 -0
  36. package/dist/style.components.css +1 -1
  37. package/dist/style.css +1 -1
  38. package/dist/style.layered.css +1 -1
  39. package/dist/theme.css +256 -0
  40. package/dist/types/legacy/components/molecules/PhotoAvatar/PhotoAvatar.d.ts +2 -0
  41. package/dist/types/v1/components/lists/ListFinder/ListFinder.d.ts +60 -0
  42. package/dist/types/v1/components/lists/ListFinder/labels.d.ts +24 -0
  43. package/dist/types/v1/components/tables/PaginatedTable/PaginatedTable.d.ts +41 -0
  44. package/dist/types/v1/components/tables/Table/ColumnFilter.d.ts +48 -0
  45. package/dist/types/v1/components/tables/Table/OverlayScrollbar.d.ts +34 -0
  46. package/dist/types/v1/components/tables/Table/Table.d.ts +250 -0
  47. package/dist/types/v1/components/tables/Table/labels.d.ts +54 -0
  48. package/dist/types/v1/foundations/icons/icons.d.ts +17 -0
  49. package/dist/types/v1/index.d.ts +15 -1
  50. package/dist/types/v1/primitives/forms/Checkbox/Checkbox.d.ts +18 -0
  51. package/dist/types/v1/primitives/lists/ListItem/ListCheckbox.d.ts +14 -0
  52. package/dist/types/v1/primitives/lists/ListItem/ListItem.d.ts +35 -0
  53. package/package.json +2 -2
@@ -3,13 +3,15 @@ import { useRef, useState, useEffect } from "react";
3
3
  import * as MsalReact from "@azure/msal-react";
4
4
  import UserAvatar from "../UserAvatar/UserAvatar.js";
5
5
  const GRAPH = "https://graph.microsoft.com/v1.0";
6
- async function getGraphToken(instance, account) {
6
+ const SCOPE_PROPRIO = ["User.Read"];
7
+ const SCOPE_OUTROS = ["User.ReadBasic.All"];
8
+ async function getGraphToken(instance, account, scopes) {
7
9
  try {
8
- const res = await instance.acquireTokenSilent({ scopes: ["User.Read"], account });
10
+ const res = await instance.acquireTokenSilent({ scopes, account });
9
11
  return res.accessToken;
10
12
  } catch (err) {
11
13
  if (err?.name === "InteractionRequiredAuthError") {
12
- const res = await instance.acquireTokenPopup({ scopes: ["User.Read"] });
14
+ const res = await instance.acquireTokenPopup({ scopes });
13
15
  return res.accessToken;
14
16
  }
15
17
  throw err;
@@ -66,9 +68,10 @@ function PhotoAvatar({ oid, name, email, className = "", showCard = false, getTo
66
68
  const fornecer = getTokenRef.current;
67
69
  if (fornecer) return { token: await fornecer(), base: "/me" };
68
70
  const conta = accountRef.current;
69
- const token = await getGraphToken(instance, conta);
70
71
  const selfOid = conta.idTokenClaims?.oid;
71
72
  const base = !oid || selfOid && selfOid === oid ? "/me" : `/users/${oid}`;
73
+ const scopes = base === "/me" ? SCOPE_PROPRIO : SCOPE_OUTROS;
74
+ const token = await getGraphToken(instance, conta, scopes);
72
75
  return { token, base };
73
76
  };
74
77
  resolve().then(async ({ token, base }) => {
@@ -104,6 +107,8 @@ function PhotoAvatar({ oid, name, email, className = "", showCard = false, getTo
104
107
  );
105
108
  }
106
109
  export {
110
+ SCOPE_OUTROS,
111
+ SCOPE_PROPRIO,
107
112
  PhotoAvatar as default
108
113
  };
109
114
  //# sourceMappingURL=PhotoAvatar.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PhotoAvatar.js","sources":["../../../../../../src/legacy/components/molecules/PhotoAvatar/PhotoAvatar.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\n// O MSAL é peer opcional: numa app sem ele, o bundler troca-o por um módulo vazio, e\r\n// imports com nome (useMsal, InteractionRequiredAuthError) partiam o build dessa app.\r\nimport * as MsalReact from '@azure/msal-react'\r\nimport type { IPublicClientApplication, AccountInfo } from '@azure/msal-browser'\r\nimport UserAvatar from '../UserAvatar/UserAvatar'\r\n\r\nconst GRAPH = 'https://graph.microsoft.com/v1.0'\r\n\r\nasync function getGraphToken(instance: IPublicClientApplication, account: AccountInfo): Promise<string> {\r\n try {\r\n const res = await instance.acquireTokenSilent({ scopes: ['User.Read'], account })\r\n return res.accessToken\r\n } catch (err) {\r\n if ((err as { name?: string } | null)?.name === 'InteractionRequiredAuthError') {\r\n const res = await instance.acquireTokenPopup({ scopes: ['User.Read'] })\r\n return res.accessToken\r\n }\r\n throw err\r\n }\r\n}\r\n\r\n/** Busca a foto (240×240, fallback /photo). null se 404/erro. */\r\nasync function fetchPhotoBlob(token: string, base: string): Promise<Blob | null> {\r\n for (const seg of ['/photos/240x240/$value', '/photo/$value']) {\r\n const resp = await fetch(`${GRAPH}${base}${seg}`, { headers: { Authorization: `Bearer ${token}` } })\r\n if (resp.ok) return resp.blob()\r\n if (resp.status === 404) continue\r\n return null\r\n }\r\n return null\r\n}\r\n\r\nexport interface GraphProfile {\r\n name?: string | null\r\n email?: string | null\r\n company?: string | null\r\n department?: string | null\r\n jobTitle?: string | null\r\n}\r\n\r\n/** Perfil p/ o cartão de hover (mesmo token User.Read). null se erro. */\r\nasync function fetchProfile(token: string, base: string): Promise<GraphProfile | null> {\r\n const resp = await fetch(`${GRAPH}${base}?$select=displayName,mail,userPrincipalName,jobTitle,department,companyName`, {\r\n headers: { Authorization: `Bearer ${token}` },\r\n })\r\n if (!resp.ok) return null\r\n const d = (await resp.json()) as Record<string, string | null>\r\n return {\r\n name: d.displayName,\r\n email: d.mail ?? d.userPrincipalName,\r\n company: d.companyName,\r\n department: d.department,\r\n jobTitle: d.jobTitle,\r\n }\r\n}\r\n\r\ninterface Props {\r\n oid?: string | null\r\n name?: string | null\r\n email?: string | null\r\n className?: string\r\n /** Passa para UserAvatar: cartão de detalhes em hover. Default: off. */\r\n showCard?: boolean\r\n /**\r\n * Fornece um token do Graph (scope `User.Read`) usando o MSAL DA APP.\r\n * PREFERIR isto: evita o `useMsal` interno, que pode ler um `MsalContext`\r\n * diferente quando o bundler duplica o `@azure/msal-react` (→ account null →\r\n * sem foto). Com `getToken`, a foto do próprio (/me) resolve sempre.\r\n * Ex.: `getToken={() => msalInstance.acquireTokenSilent({scopes:['User.Read'],account}).then(r=>r.accessToken)}`\r\n */\r\n getToken?: () => Promise<string>\r\n}\r\n\r\n// Cache em memória (evita refetch entre montagens).\r\n//\r\n// Guarda o OBJECT URL já criado, não o Blob. Com o Blob, cada passagem pelo\r\n// efeito criava um URL novo, o que mudava o estado e disparava outro render —\r\n// e outra passagem. Era um ciclo infinito, visível como a foto a alternar com\r\n// as iniciais.\r\n//\r\n// O URL nunca é revogado: é uma imagem para toda a sessão, partilhada por\r\n// todas as montagens com a mesma chave. Revogá-la ao desmontar partia as que\r\n// ficavam a usá-la.\r\nconst photoCache = new Map<string, string | null>()\r\nconst profileCache = new Map<string, GraphProfile | null>()\r\n\r\n/**\r\n * Avatar com a foto real do MS Graph. Fallback: iniciais (UserAvatar).\r\n * Token: via `getToken` (recomendado) ou, em alternativa, via `useMsal` interno.\r\n * Peers: @azure/msal-react, @azure/msal-browser.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function PhotoAvatar({ oid, name, email, className = '', showCard = false, getToken }: Props) {\r\n const { instance, accounts } = MsalReact.useMsal()\r\n const account = instance.getActiveAccount() ?? accounts[0]\r\n\r\n // O MSAL reconstrói o objeto da conta a cada chamada, por isso ele nunca\r\n // serve como dependência do efeito: fazia-o correr em todos os renders. A\r\n // identidade estável é o homeAccountId.\r\n const accountKey = account?.homeAccountId ?? null\r\n\r\n // O getToken é quase sempre uma arrow inline na app, logo também muda a cada\r\n // render. Fica numa ref, fora das dependências; o que importa é se existe.\r\n const getTokenRef = useRef(getToken)\r\n getTokenRef.current = getToken\r\n const accountRef = useRef(account)\r\n accountRef.current = account\r\n const temGetToken = Boolean(getToken)\r\n\r\n const cacheKey = temGetToken ? 'self' : oid ?? accountKey ?? 'me'\r\n\r\n // Arranca já com o que estiver em cache, para não piscar as iniciais ao\r\n // mudar de página.\r\n const [url, setUrl] = useState<string | null>(() => photoCache.get(cacheKey) ?? null)\r\n const [profile, setProfile] = useState<GraphProfile | null>(() => profileCache.get(cacheKey) ?? null)\r\n\r\n useEffect(() => {\r\n if (!temGetToken && !accountKey) return undefined\r\n let cancelled = false\r\n\r\n const cachedPhoto = photoCache.get(cacheKey)\r\n const cachedProfile = profileCache.get(cacheKey)\r\n if (cachedPhoto !== undefined && (!showCard || cachedProfile !== undefined)) {\r\n setUrl(cachedPhoto)\r\n if (cachedProfile) setProfile(cachedProfile)\r\n return undefined\r\n }\r\n\r\n // Token + base do Graph resolvidos 1x, reutilizados p/ foto e perfil.\r\n const resolve = async (): Promise<{ token: string; base: string }> => {\r\n const fornecer = getTokenRef.current\r\n if (fornecer) return { token: await fornecer(), base: '/me' }\r\n const conta = accountRef.current!\r\n const token = await getGraphToken(instance, conta)\r\n const selfOid = (conta.idTokenClaims as { oid?: string } | undefined)?.oid\r\n const base = !oid || (selfOid && selfOid === oid) ? '/me' : `/users/${oid}`\r\n return { token, base }\r\n }\r\n\r\n resolve()\r\n .then(async ({ token, base }) => {\r\n const blob = await fetchPhotoBlob(token, base)\r\n const objectUrl = blob ? URL.createObjectURL(blob) : null\r\n photoCache.set(cacheKey, objectUrl)\r\n if (!cancelled) setUrl(objectUrl)\r\n // Perfil só quando há cartão de hover (evita chamada Graph extra).\r\n if (showCard) {\r\n const prof = await fetchProfile(token, base)\r\n profileCache.set(cacheKey, prof)\r\n if (!cancelled && prof) setProfile(prof)\r\n }\r\n })\r\n .catch(() => {\r\n photoCache.set(cacheKey, null)\r\n if (showCard) profileCache.set(cacheKey, null)\r\n })\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [cacheKey, accountKey, temGetToken, showCard, oid, instance])\r\n\r\n // Redondo por default (a app pode sobrepor a forma no className, ex: rounded-none).\r\n return (\r\n <UserAvatar\r\n name={name ?? profile?.name}\r\n email={email ?? profile?.email}\r\n company={profile?.company}\r\n department={profile?.department}\r\n jobTitle={profile?.jobTitle}\r\n photo={url}\r\n className={`rounded-full ${className}`}\r\n showCard={showCard}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PhotoAvatar.js","sources":["../../../../../../src/legacy/components/molecules/PhotoAvatar/PhotoAvatar.tsx"],"sourcesContent":["import React, { useEffect, useRef, useState } from 'react'\r\n// O MSAL é peer opcional: numa app sem ele, o bundler troca-o por um módulo vazio, e\r\n// imports com nome (useMsal, InteractionRequiredAuthError) partiam o build dessa app.\r\nimport * as MsalReact from '@azure/msal-react'\r\nimport type { IPublicClientApplication, AccountInfo } from '@azure/msal-browser'\r\nimport UserAvatar from '../UserAvatar/UserAvatar'\r\n\r\nconst GRAPH = 'https://graph.microsoft.com/v1.0'\r\n\r\n// `User.Read` so da acesso ao proprio utilizador. Para a foto ou o perfil de\r\n// OUTRA pessoa (`/users/<oid>`) e preciso `User.ReadBasic.All`, que a App\r\n// Registration da app consumidora tem de ter com consentimento de\r\n// administrador. Pedir `User.Read` para terceiros arrisca um 403, com o avatar\r\n// a cair nas iniciais sem nada que explique porque.\r\nexport const SCOPE_PROPRIO = ['User.Read']\r\nexport const SCOPE_OUTROS = ['User.ReadBasic.All']\r\n\r\nasync function getGraphToken(\r\n instance: IPublicClientApplication,\r\n account: AccountInfo,\r\n scopes: string[]\r\n): Promise<string> {\r\n try {\r\n const res = await instance.acquireTokenSilent({ scopes, account })\r\n return res.accessToken\r\n } catch (err) {\r\n if ((err as { name?: string } | null)?.name === 'InteractionRequiredAuthError') {\r\n const res = await instance.acquireTokenPopup({ scopes })\r\n return res.accessToken\r\n }\r\n throw err\r\n }\r\n}\r\n\r\n/** Busca a foto (240×240, fallback /photo). null se 404/erro. */\r\nasync function fetchPhotoBlob(token: string, base: string): Promise<Blob | null> {\r\n for (const seg of ['/photos/240x240/$value', '/photo/$value']) {\r\n const resp = await fetch(`${GRAPH}${base}${seg}`, { headers: { Authorization: `Bearer ${token}` } })\r\n if (resp.ok) return resp.blob()\r\n if (resp.status === 404) continue\r\n return null\r\n }\r\n return null\r\n}\r\n\r\nexport interface GraphProfile {\r\n name?: string | null\r\n email?: string | null\r\n company?: string | null\r\n department?: string | null\r\n jobTitle?: string | null\r\n}\r\n\r\n/** Perfil p/ o cartão de hover (mesmo token User.Read). null se erro. */\r\nasync function fetchProfile(token: string, base: string): Promise<GraphProfile | null> {\r\n const resp = await fetch(`${GRAPH}${base}?$select=displayName,mail,userPrincipalName,jobTitle,department,companyName`, {\r\n headers: { Authorization: `Bearer ${token}` },\r\n })\r\n if (!resp.ok) return null\r\n const d = (await resp.json()) as Record<string, string | null>\r\n return {\r\n name: d.displayName,\r\n email: d.mail ?? d.userPrincipalName,\r\n company: d.companyName,\r\n department: d.department,\r\n jobTitle: d.jobTitle,\r\n }\r\n}\r\n\r\ninterface Props {\r\n oid?: string | null\r\n name?: string | null\r\n email?: string | null\r\n className?: string\r\n /** Passa para UserAvatar: cartão de detalhes em hover. Default: off. */\r\n showCard?: boolean\r\n /**\r\n * Fornece um token do Graph (scope `User.Read`) usando o MSAL DA APP.\r\n * PREFERIR isto: evita o `useMsal` interno, que pode ler um `MsalContext`\r\n * diferente quando o bundler duplica o `@azure/msal-react` (→ account null →\r\n * sem foto). Com `getToken`, a foto do próprio (/me) resolve sempre.\r\n * Ex.: `getToken={() => msalInstance.acquireTokenSilent({scopes:['User.Read'],account}).then(r=>r.accessToken)}`\r\n */\r\n getToken?: () => Promise<string>\r\n}\r\n\r\n// Cache em memória (evita refetch entre montagens).\r\n//\r\n// Guarda o OBJECT URL já criado, não o Blob. Com o Blob, cada passagem pelo\r\n// efeito criava um URL novo, o que mudava o estado e disparava outro render —\r\n// e outra passagem. Era um ciclo infinito, visível como a foto a alternar com\r\n// as iniciais.\r\n//\r\n// O URL nunca é revogado: é uma imagem para toda a sessão, partilhada por\r\n// todas as montagens com a mesma chave. Revogá-la ao desmontar partia as que\r\n// ficavam a usá-la.\r\nconst photoCache = new Map<string, string | null>()\r\nconst profileCache = new Map<string, GraphProfile | null>()\r\n\r\n/**\r\n * Avatar com a foto real do MS Graph. Fallback: iniciais (UserAvatar).\r\n * Token: via `getToken` (recomendado) ou, em alternativa, via `useMsal` interno.\r\n * Peers: @azure/msal-react, @azure/msal-browser.\r\n * @deprecated Legacy — não usar em código novo. Ver Storybook › Legacy.\r\n */\r\nexport default function PhotoAvatar({ oid, name, email, className = '', showCard = false, getToken }: Props) {\r\n const { instance, accounts } = MsalReact.useMsal()\r\n const account = instance.getActiveAccount() ?? accounts[0]\r\n\r\n // O MSAL reconstrói o objeto da conta a cada chamada, por isso ele nunca\r\n // serve como dependência do efeito: fazia-o correr em todos os renders. A\r\n // identidade estável é o homeAccountId.\r\n const accountKey = account?.homeAccountId ?? null\r\n\r\n // O getToken é quase sempre uma arrow inline na app, logo também muda a cada\r\n // render. Fica numa ref, fora das dependências; o que importa é se existe.\r\n const getTokenRef = useRef(getToken)\r\n getTokenRef.current = getToken\r\n const accountRef = useRef(account)\r\n accountRef.current = account\r\n const temGetToken = Boolean(getToken)\r\n\r\n const cacheKey = temGetToken ? 'self' : oid ?? accountKey ?? 'me'\r\n\r\n // Arranca já com o que estiver em cache, para não piscar as iniciais ao\r\n // mudar de página.\r\n const [url, setUrl] = useState<string | null>(() => photoCache.get(cacheKey) ?? null)\r\n const [profile, setProfile] = useState<GraphProfile | null>(() => profileCache.get(cacheKey) ?? null)\r\n\r\n useEffect(() => {\r\n if (!temGetToken && !accountKey) return undefined\r\n let cancelled = false\r\n\r\n const cachedPhoto = photoCache.get(cacheKey)\r\n const cachedProfile = profileCache.get(cacheKey)\r\n if (cachedPhoto !== undefined && (!showCard || cachedProfile !== undefined)) {\r\n setUrl(cachedPhoto)\r\n if (cachedProfile) setProfile(cachedProfile)\r\n return undefined\r\n }\r\n\r\n // Token + base do Graph resolvidos 1x, reutilizados p/ foto e perfil.\r\n const resolve = async (): Promise<{ token: string; base: string }> => {\r\n const fornecer = getTokenRef.current\r\n if (fornecer) return { token: await fornecer(), base: '/me' }\r\n const conta = accountRef.current!\r\n const selfOid = (conta.idTokenClaims as { oid?: string } | undefined)?.oid\r\n // A base decide-se ANTES do token, porque e ela que determina o scope.\r\n const base = !oid || (selfOid && selfOid === oid) ? '/me' : `/users/${oid}`\r\n const scopes = base === '/me' ? SCOPE_PROPRIO : SCOPE_OUTROS\r\n const token = await getGraphToken(instance, conta, scopes)\r\n return { token, base }\r\n }\r\n\r\n resolve()\r\n .then(async ({ token, base }) => {\r\n const blob = await fetchPhotoBlob(token, base)\r\n const objectUrl = blob ? URL.createObjectURL(blob) : null\r\n photoCache.set(cacheKey, objectUrl)\r\n if (!cancelled) setUrl(objectUrl)\r\n // Perfil só quando há cartão de hover (evita chamada Graph extra).\r\n if (showCard) {\r\n const prof = await fetchProfile(token, base)\r\n profileCache.set(cacheKey, prof)\r\n if (!cancelled && prof) setProfile(prof)\r\n }\r\n })\r\n .catch(() => {\r\n photoCache.set(cacheKey, null)\r\n if (showCard) profileCache.set(cacheKey, null)\r\n })\r\n\r\n return () => {\r\n cancelled = true\r\n }\r\n }, [cacheKey, accountKey, temGetToken, showCard, oid, instance])\r\n\r\n // Redondo por default (a app pode sobrepor a forma no className, ex: rounded-none).\r\n return (\r\n <UserAvatar\r\n name={name ?? profile?.name}\r\n email={email ?? profile?.email}\r\n company={profile?.company}\r\n department={profile?.department}\r\n jobTitle={profile?.jobTitle}\r\n photo={url}\r\n className={`rounded-full ${className}`}\r\n showCard={showCard}\r\n />\r\n )\r\n}\r\n"],"names":[],"mappings":"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"}