@1e99/components 0.1.0 → 0.1.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 1e99
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,42 +1,32 @@
1
1
  # @1e99/components
2
2
 
3
- Componentes React compartilhados pelos produtos 1e99. `Navbar`,
4
- `GoogleSignInButton` e `Selector` foram extraídos fielmente de
5
- `domain-website` e `curriculo`, sem introduzir dependência de Next.js,
6
- autenticação, i18n ou geração de PDF.
3
+ Componentes React reutilizáveis, estilizados com Tailwind CSS.
7
4
 
8
- ## Instalação futura
9
-
10
- Após a primeira publicação pública, instale o pacote no app consumidor:
5
+ ## Instalação
11
6
 
12
7
  ```sh
13
8
  pnpm add @1e99/components
14
9
  ```
15
10
 
16
- O pacote declara `react`, `react-dom`, `react-icons` e `tailwindcss` como
17
- peer dependencies. Os apps 1e99 já usam `react-icons` 5.7.0; ele fornece os
18
- ícones Google e Lucide que o pacote renderiza, sem duplicar SVGs manualmente.
11
+ O pacote declara `react`, `react-dom`, `react-icons` e `tailwindcss` como peer
12
+ dependencies.
19
13
 
20
14
  ### Tailwind CSS 4
21
15
 
22
- O Tailwind 4 não lê automaticamente classes presentes em `node_modules`.
23
- Adicione esta diretiva à folha CSS que já contém `@import "tailwindcss"`, com
24
- o caminho relativo àquela folha:
16
+ O Tailwind 4 não examina automaticamente classes em `node_modules`. Na folha
17
+ CSS que importa o Tailwind, adicione uma diretiva `@source` cujo caminho seja
18
+ relativo a ela:
25
19
 
26
20
  ```css
21
+ @import "tailwindcss";
27
22
  @source "../../node_modules/@1e99/components/dist";
28
23
  ```
29
24
 
30
- Nos consumidores atuais, a alteração futura será em:
31
-
32
- - `domain-website/src/app/globals.css`;
33
- - `curriculo/src/app/brand.css`.
34
-
35
- Os dois projetos já fornecem os tokens usados pela navbar, incluindo
25
+ Os componentes usam tokens Tailwind do tema do aplicativo, como
36
26
  `surface-page`, `content-primary`, `content-secondary`, `border-subtle`,
37
27
  `action-primary`, `surface-raised`, `content-inverse`, `shadow-card`,
38
28
  `shadow-float`, `shadow-focus`, `ease-brand`, `shell`, `control` e `card`.
39
- O pacote não redefine esses tokens nem importa CSS global no aplicativo.
29
+ O pacote não cria tokens nem importa CSS global.
40
30
 
41
31
  ## Uso
42
32
 
@@ -48,117 +38,65 @@ import {
48
38
  Selector,
49
39
  } from "@1e99/components";
50
40
 
51
- type LocaleOption = {
52
- label: string;
53
- languageTag: string;
54
- value: string;
55
- };
56
-
57
- const localeOptions = [
58
- { value: "pt", label: "Português", languageTag: "pt-BR" },
59
- { value: "en", label: "English", languageTag: "en" },
60
- { value: "es", label: "Español", languageTag: "es" },
61
- ] satisfies readonly LocaleOption[];
41
+ const languageOptions = [
42
+ {label: "Português", value: "pt"},
43
+ {label: "English", value: "en"},
44
+ ] as const;
62
45
 
63
46
  function Header() {
64
- const [locale, setLocale] = useState("pt");
47
+ const [language, setLanguage] = useState("pt");
65
48
 
66
49
  return (
67
50
  <Navbar
68
- brand={<a href="/"><BrandLogo /></a>}
69
- items={[
70
- { href: "/apps", label: "Apps" },
71
- { href: "/about", label: "Sobre" },
72
- ]}
73
- navigationLabel="Navegação principal"
74
- renderLink={({ href, children, className }) => (
75
- <Link className={className} href={href}>{children}</Link>
76
- )}
77
51
  actions={
78
52
  <div className="hidden items-center gap-3 md:flex">
79
53
  <Selector
80
- label={t("language")}
81
- onValueChange={setLocale}
82
- options={localeOptions}
83
- renderOption={({ option }) => (
84
- <span lang={option.languageTag}>{option.label}</span>
85
- )}
86
- renderTrigger={({ selectedOption, value }) => (
87
- <span lang={selectedOption?.languageTag}>
88
- {selectedOption?.label ?? value}
89
- </span>
90
- )}
91
- value={locale}
54
+ label="Idioma"
55
+ onValueChange={setLanguage}
56
+ options={languageOptions}
57
+ value={language}
92
58
  />
93
59
  <GoogleSignInButton
94
- label={t("signInWithGoogle")}
95
- onClick={startGoogleLogin}
60
+ label="Entrar com Google"
61
+ onClick={() => undefined}
96
62
  />
97
63
  </div>
98
64
  }
99
- width="content"
100
- layer="above"
101
- linkTreatment="underlined"
102
- linkWeight="semibold"
103
- mobileNavigation={{
104
- menuLabel: t("openNavigation"),
105
- navigationLabel: t("mobileNavigation"),
106
- actions: <MobileAccountActions />,
107
- }}
65
+ brand={<a aria-label="Início" href="/">Marca</a>}
66
+ items={[
67
+ {href: "/products", label: "Produtos"},
68
+ {href: "/about", label: "Sobre"},
69
+ ]}
70
+ navigationLabel="Navegação principal"
108
71
  />
109
72
  );
110
73
  }
111
74
  ```
112
75
 
113
- | Prop | Opções | Uso nos projetos existentes |
114
- | --- | --- | --- |
115
- | `width` | `content` (padrão), `wide` | domain-website / curriculo |
116
- | `layer` | `standard` (padrão), `above` | curriculo / domain-website |
117
- | `linkTreatment` | `plain` (padrão), `underlined` | curriculo / domain-website |
118
- | `linkWeight` | `semibold` (padrão), `bold` | domain-website / curriculo |
119
- | `mobileNavigation` | ausente ou objeto de disclosure | curriculo / domain-website |
120
-
121
- `brand` e `actions` são slots e, junto da navegação desktop, permanecem como
122
- regiões irmãs no container — o mesmo layout dos dois headers fonte. O objeto
123
- `mobileNavigation` reproduz o menu do domain (botão azul de 48px e painel
124
- elevado); ele fica ausente no perfil do currículo. `renderLink` recebe `href`,
125
- `children` e `className`, permitindo usar `next/link`, `next-intl/link` ou
126
- outro roteador sem acoplar o pacote a ele.
127
-
128
- ### Controles de header
76
+ `Navbar` mantém marca, itens de navegação e ações como regiões independentes.
77
+ Use `renderLink` para integrar um roteador próprio sem acoplar o pacote a uma
78
+ biblioteca de navegação. Os itens podem ser adicionados ou removidos, e
79
+ `actions` e `mobileNavigation.actions` aceitam qualquer nó React.
129
80
 
130
- `GoogleSignInButton` é o único tratamento visual para entrar com Google e
131
- segue o botão do `domain-website` em todos os usos. `label` é obrigatório e
132
- deve receber a string já traduzida pelo consumidor; também aceita atributos
133
- comportamentais nativos de botão (`onClick`, `disabled`, `aria-*`). Não aceita
134
- variante nem `className`; autenticação e estado de carregamento são do
135
- consumidor.
136
-
137
- `Selector` é um disclosure genérico para uma coleção controlada. Cada opção
138
- tem apenas `value` estável e `label` como contrato-base; o consumidor pode
139
- acrescentar campos ao tipar sua própria coleção, como `languageTag`, descrição
140
- ou ícone. `value` e `onValueChange` são controlados pelo consumidor: ao clicar
141
- em uma opção, o componente informa o novo valor e fecha o disclosure, mas não
142
- navega nem atualiza estado por conta própria.
81
+ | Prop | Opções | Finalidade |
82
+ | --- | --- | --- |
83
+ | `width` | `content` (padrão), `wide` | Largura máxima da navegação |
84
+ | `layer` | `standard` (padrão), `above` | Camada de empilhamento |
85
+ | `linkTreatment` | `plain` (padrão), `underlined` | Aparência dos links |
86
+ | `linkWeight` | `semibold` (padrão), `bold` | Peso dos links |
87
+ | `mobileNavigation` | ausente ou objeto de disclosure | Navegação móvel opcional |
143
88
 
144
- `renderOption` recebe `option`, `isSelected` e `select`; ele renderiza somente
145
- o conteúdo interno do botão que o pacote controla. `renderTrigger` recebe
146
- `selectedOption` e `value` para personalizar o conteúdo do gatilho. Dessa
147
- forma, atributos como `lang` ficam no conteúdo do aplicativo consumidor, não
148
- no tipo genérico do pacote. O componente preserva botões com `aria-pressed`,
149
- `select-none`, as densidades `standard` (padrão), `compact` e
150
- `compact-mobile` e `whiteSurface`. Sem `triggerAriaLabel`, o nome acessível do
151
- gatilho vem do conteúdo selecionado visível; use essa prop quando o consumidor
152
- precisar de um nome mais descritivo e atualize-o junto do valor controlado.
89
+ `GoogleSignInButton` recebe uma `label` obrigatória e os atributos nativos de
90
+ um botão, como `onClick`, `disabled` e `aria-*`. Autenticação, estado de
91
+ carregamento e traduções pertencem ao aplicativo que o utiliza.
153
92
 
154
- `Navbar` não recebe props de idioma nem de autenticação. `items`, `actions` e
155
- `mobileNavigation.actions` são os pontos de composição para qualquer controle
156
- ou CTA do produto. No Storybook, `Navigation/Navbar` inclui o playground de
157
- referência com o logo oficial fixo; seus args serializáveis controlam itens,
158
- seletor, botão Google e menu móvel. O seletor e o botão também possuem stories
159
- próprias em `Controls/Selector` e `Controls/GoogleSignInButton`.
93
+ `Selector` é controlado: `value` determina a opção visível e
94
+ `onValueChange` informa uma nova escolha. As opções exigem somente `value` e
95
+ `label`, mas podem ser estendidas com outros campos e apresentadas por
96
+ `renderOption` e `renderTrigger`. O componente não navega nem muda estado por
97
+ conta própria.
160
98
 
161
- ## Desenvolvimento e testes
99
+ ## Desenvolvimento
162
100
 
163
101
  ```sh
164
102
  pnpm lint
@@ -172,90 +110,21 @@ pnpm build-storybook
172
110
  pnpm pack:check
173
111
  ```
174
112
 
175
- `pnpm lint` aplica o padrão Google para TypeScript e React; `pnpm lint:fix`
176
- corrige automaticamente o que for seguro, e `pnpm format` é seu atalho. O
177
- Husky executa `pnpm lint` e `pnpm test` antes de cada commit local, portanto
178
- ambos devem passar antes de o histórico ser criado.
179
-
180
- `pnpm test` roda testes de contrato em Vitest + Testing Library. As stories
181
- exibem o playground configurável da navbar, o seletor genérico e o botão
182
- Google, incluindo o SVG oficial somente como asset de demonstração do
183
- Storybook. Elas são verificadas em Chromium por `pnpm test:stories`, com os
184
- addons oficiais de acessibilidade e Vitest.
185
-
186
- Em uma máquina Linux nova, instale as dependências do Chromium uma vez antes
187
- do teste de stories:
113
+ Husky executa `pnpm lint` e `pnpm test` antes de cada commit. As stories são
114
+ verificadas em Chromium por `pnpm test:stories`; em uma máquina Linux nova,
115
+ instale as dependências uma vez:
188
116
 
189
117
  ```sh
190
118
  pnpm exec playwright install chromium
191
119
  sudo pnpm exec playwright install-deps chromium
192
120
  ```
193
121
 
194
- ## Organização do código
122
+ Cada componente fica em `src/components/<nome>/`, junto dos seus testes e
123
+ stories. `src/index.ts` é o ponto de entrada público, e testes transversais
124
+ ficam em `test/`.
195
125
 
196
- Cada componente é uma unidade em `src/components/<nome>/`: implementação,
197
- testes e story (quando houver) ficam juntos. `src/index.ts` é o único ponto de
198
- entrada público; mudar a organização interna não altera como os aplicativos
199
- importam `@1e99/components`. Testes transversais ficam em `test/`.
126
+ ## Licença
200
127
 
201
- ## Publicação
202
-
203
- O destino recomendado é o npm público, como `@1e99/components`: ele evita
204
- configuração de registry e token em cada consumidor. GitHub Packages é uma
205
- alternativa para distribuição privada, mas exige registry e token GitHub nos
206
- aplicativos.
207
-
208
- `.github/workflows/ci.yml` valida pull requests e pushes para `main` com lint,
209
- tipos, testes, build, Storybook e prévia do tarball. `.github/workflows/publish.yml`
210
- repete essas verificações e publica somente quando uma tag `vX.Y.Z` corresponde
211
- exatamente à versão em `package.json` e aponta para um commit ancestral de
212
- `main`. Os actions usados nos workflows estão fixados em commits revisáveis.
213
-
214
- ### Primeiro release pelo CI
215
-
216
- O bootstrap usa temporariamente o secret de repositório `NPM_TOKEN`. Crie-o no
217
- npm como token granular, com **Bypass two-factor authentication**, permissão
218
- **Read and write (publish and stage)** limitada ao escopo `@1e99` e expiração
219
- curta. Salve-o em GitHub → `1e99/components` → Settings → Secrets and variables
220
- → Actions como `NPM_TOKEN`. Nunca grave ou compartilhe esse valor por chat,
221
- issues, commits ou logs.
222
-
223
- Como `@1e99/components` ainda não existe antes do primeiro publish, o escopo
224
- inteiro é uma exceção temporária de bootstrap: revogue o token assim que OIDC
225
- for configurado. O npm anuncia a remoção do publish direto com tokens granular
226
- de bypass de 2FA em janeiro de 2027; se o primeiro release for adiado além
227
- disso, use o fluxo de staged publishing do npm em vez de manter um token.
228
-
229
- Antes de criar a tag pública, confirme a política de licença: o pacote está
230
- como `UNLICENSED` e essa decisão precisa de aprovação explícita para publicação
231
- pública.
232
-
233
- Envie os commits e a tag correspondente à versão:
234
-
235
- ```sh
236
- git push origin main
237
- git tag v0.1.0
238
- git push origin v0.1.0
239
- ```
240
-
241
- O workflow recusa a tag se `package.json` não estiver em `0.1.0` e publica com
242
- `publishConfig.access: public` se todas as verificações passarem.
243
- Configure também regras do GitHub para exigir o CI antes de merge em `main` e
244
- restringir a criação e atualização de tags `v*` a mantenedores autorizados.
245
-
246
- ### Migrar para Trusted Publishing
247
-
248
- Depois que a primeira release for publicada, habilite 2FA na conta npm e abra
249
- as configurações de `@1e99/components` no npm. Em **Trusted Publisher**, escolha
250
- GitHub Actions e informe organização `1e99`, repositório `components` e arquivo
251
- de workflow `publish.yml`; permita publicação direta com `npm publish`.
252
-
253
- Em seguida, apague o secret `NPM_TOKEN` do GitHub e revogue o token no npm. As
254
- tags futuras usarão OIDC com credenciais efêmeras do GitHub Actions e gerarão
255
- provenance automaticamente. Para uma próxima versão, atualize a versão e envie
256
- a tag criada pelo gerenciador:
257
-
258
- ```sh
259
- pnpm version patch
260
- git push origin main --follow-tags
261
- ```
128
+ Este pacote é licenciado sob a [MIT License](LICENSE). Você pode usar, copiar,
129
+ modificar, distribuir e sublicenciar o software, inclusive comercialmente,
130
+ desde que preserve o aviso de copyright e a licença.
package/dist/index.js CHANGED
@@ -17,13 +17,11 @@ function GoogleSignInButton({ label, ...buttonProps }) {
17
17
  }
18
18
 
19
19
  // src/components/selector/selector.tsx
20
+ import { useCallback, useEffect, useState } from "react";
20
21
  import { LuChevronDown } from "react-icons/lu";
21
22
  import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
22
23
  var standardTriggerClassName = "flex w-full cursor-pointer list-none select-none items-center justify-between rounded-control border border-border-subtle py-control-y text-label font-semibold text-content-primary shadow-card transition-colors duration-200 ease-brand hover:bg-brand-cloud focus-visible:outline-none focus-visible:shadow-focus [&::-webkit-details-marker]:hidden";
23
24
  var optionClassName = "flex min-h-12 w-full select-none items-center rounded-control px-3 text-label font-semibold text-content-secondary transition-colors duration-200 ease-brand hover:bg-brand-cloud hover:text-content-primary focus-visible:outline-none focus-visible:shadow-focus";
24
- function closeSelectorDisclosure(element) {
25
- element.closest("details")?.removeAttribute("open");
26
- }
27
25
  function getNavigationLayout(density) {
28
26
  if (density === "compact") {
29
27
  return { trigger: "h-12 gap-2", wrapper: "w-auto" };
@@ -66,46 +64,70 @@ function Selector({
66
64
  value,
67
65
  whiteSurface = false
68
66
  }) {
67
+ const [selectorElement, setSelectorElement] = useState(null);
69
68
  const selectedOption = options.find((option) => option.value === value);
70
69
  const layout = getNavigationLayout(density);
71
70
  const surfaceClassName = whiteSurface ? "bg-white" : "bg-surface-raised";
72
- return /* @__PURE__ */ jsxs2("details", { className: `selector-disclosure group relative ${layout.wrapper}`, children: [
73
- /* @__PURE__ */ jsx2(
74
- SelectorTrigger,
75
- {
76
- densityClassName: layout.trigger,
77
- renderTrigger,
78
- selectedOption,
79
- triggerAriaLabel,
80
- value
81
- }
82
- ),
83
- /* @__PURE__ */ jsx2(
84
- "ul",
85
- {
86
- "aria-label": label,
87
- className: `mt-2 w-full overflow-hidden rounded-control border border-border-subtle ${surfaceClassName} p-1 shadow-float md:absolute md:top-full md:right-0 md:z-60 md:min-w-44`,
88
- children: options.map((option) => {
89
- const isSelected = option.value === value;
90
- const select = () => onValueChange(option.value);
91
- const optionContent = renderOption?.({ isSelected, option, select }) ?? option.label;
92
- return /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(
93
- "button",
94
- {
95
- "aria-pressed": isSelected,
96
- className: `${optionClassName} text-left ${isSelected ? "bg-brand-cloud text-content-primary" : ""}`,
97
- onClick: (event) => {
98
- select();
99
- closeSelectorDisclosure(event.currentTarget);
100
- },
101
- type: "button",
102
- children: optionContent
103
- }
104
- ) }, option.value);
105
- })
71
+ useEffect(() => {
72
+ if (!selectorElement) {
73
+ return;
74
+ }
75
+ const closeWhenPointerStartsOutside = (event) => {
76
+ if (!selectorElement.open || !(event.target instanceof Node) || selectorElement.contains(event.target)) {
77
+ return;
106
78
  }
107
- )
108
- ] });
79
+ selectorElement.removeAttribute("open");
80
+ };
81
+ document.addEventListener("pointerdown", closeWhenPointerStartsOutside, true);
82
+ return () => {
83
+ document.removeEventListener("pointerdown", closeWhenPointerStartsOutside, true);
84
+ };
85
+ }, [selectorElement]);
86
+ const selectOption = useCallback((option) => {
87
+ selectorElement?.removeAttribute("open");
88
+ onValueChange(option.value);
89
+ }, [onValueChange, selectorElement]);
90
+ return /* @__PURE__ */ jsxs2(
91
+ "details",
92
+ {
93
+ ref: setSelectorElement,
94
+ className: `selector-disclosure group relative ${layout.wrapper}`,
95
+ children: [
96
+ /* @__PURE__ */ jsx2(
97
+ SelectorTrigger,
98
+ {
99
+ densityClassName: layout.trigger,
100
+ renderTrigger,
101
+ selectedOption,
102
+ triggerAriaLabel,
103
+ value
104
+ }
105
+ ),
106
+ /* @__PURE__ */ jsx2(
107
+ "ul",
108
+ {
109
+ "aria-label": label,
110
+ className: `mt-2 w-full overflow-hidden rounded-control border border-border-subtle ${surfaceClassName} p-1 shadow-float md:absolute md:top-full md:right-0 md:z-60 md:min-w-44`,
111
+ children: options.map((option) => {
112
+ const isSelected = option.value === value;
113
+ const select = () => selectOption(option);
114
+ const optionContent = renderOption?.({ isSelected, option, select }) ?? option.label;
115
+ return /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsx2(
116
+ "button",
117
+ {
118
+ "aria-pressed": isSelected,
119
+ className: `${optionClassName} text-left ${isSelected ? "bg-brand-cloud text-content-primary" : ""}`,
120
+ onClick: select,
121
+ type: "button",
122
+ children: optionContent
123
+ }
124
+ ) }, option.value);
125
+ })
126
+ }
127
+ )
128
+ ]
129
+ }
130
+ );
109
131
  }
110
132
 
111
133
  // src/components/navbar/navbar.tsx
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/google-sign-in-button/google-sign-in-button.tsx","../src/components/selector/selector.tsx","../src/components/navbar/navbar.tsx"],"sourcesContent":["import type {ButtonHTMLAttributes, ReactNode} from 'react';\nimport {FcGoogle} from 'react-icons/fc';\n\nexport type GoogleSignInButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'children' | 'className' | 'type'\n> & {\n label: ReactNode;\n};\n\nconst buttonClassName =\n 'inline-flex min-h-12 w-full cursor-pointer items-center justify-center gap-3 rounded-control border border-border-subtle bg-surface-raised px-control-x py-control-y text-label font-semibold whitespace-nowrap text-content-primary shadow-card transition-[translate,scale,background-color] duration-200 ease-brand hover:-translate-y-0.5 hover:bg-brand-cloud active:scale-[0.97] focus-visible:outline-none focus-visible:shadow-focus disabled:cursor-wait disabled:opacity-70 motion-reduce:transform-none';\n\nexport function GoogleSignInButton(\n {label, ...buttonProps}: GoogleSignInButtonProps,\n) {\n return (\n <button {...buttonProps} className={buttonClassName} type=\"button\">\n <FcGoogle\n aria-hidden=\"true\"\n className=\"size-5 shrink-0\"\n data-testid=\"google-mark\"\n />\n {label}\n </button>\n );\n}\n","import {type ReactNode} from 'react';\nimport {LuChevronDown} from 'react-icons/lu';\n\nexport type SelectorOption = {\n label: ReactNode;\n value: string;\n};\n\nexport type SelectorDensity = 'compact' | 'compact-mobile' | 'standard';\n\nexport type SelectorRenderOptionContext<TOption extends SelectorOption> = {\n isSelected: boolean;\n option: TOption;\n select: () => void;\n};\n\nexport type SelectorRenderTriggerContext<TOption extends SelectorOption> = {\n selectedOption: TOption | undefined;\n value: string;\n};\n\ntype SelectorBaseProps = {\n label: string;\n triggerAriaLabel?: string;\n value: string;\n};\n\nexport type SelectorProps<TOption extends SelectorOption = SelectorOption> =\n SelectorBaseProps & {\n density?: SelectorDensity;\n onValueChange: (value: string) => void;\n options: readonly TOption[];\n renderOption?: (context: SelectorRenderOptionContext<TOption>) => ReactNode;\n renderTrigger?: (\n context: SelectorRenderTriggerContext<TOption>,\n ) => ReactNode;\n whiteSurface?: boolean;\n };\n\nconst standardTriggerClassName =\n 'flex w-full cursor-pointer list-none select-none items-center justify-between rounded-control border border-border-subtle py-control-y text-label font-semibold text-content-primary shadow-card transition-colors duration-200 ease-brand hover:bg-brand-cloud focus-visible:outline-none focus-visible:shadow-focus [&::-webkit-details-marker]:hidden';\n\nconst optionClassName =\n 'flex min-h-12 w-full select-none items-center rounded-control px-3 text-label font-semibold text-content-secondary transition-colors duration-200 ease-brand hover:bg-brand-cloud hover:text-content-primary focus-visible:outline-none focus-visible:shadow-focus';\n\nfunction closeSelectorDisclosure(element: HTMLElement) {\n element.closest('details')?.removeAttribute('open');\n}\n\nfunction getNavigationLayout(density: SelectorDensity) {\n if (density === 'compact') {\n return {trigger: 'h-12 gap-2', wrapper: 'w-auto'};\n }\n\n if (density === 'compact-mobile') {\n return {\n trigger: 'h-12 gap-2 px-3 md:gap-4 md:px-4 md:min-w-32',\n wrapper: 'w-auto',\n };\n }\n\n return {trigger: 'min-h-12 gap-4 px-4 md:min-w-32', wrapper: 'w-full md:w-auto'};\n}\n\nfunction SelectorTrigger<TOption extends SelectorOption>({\n densityClassName,\n renderTrigger,\n selectedOption,\n triggerAriaLabel,\n value,\n}: {\n densityClassName: string;\n renderTrigger?: (context: SelectorRenderTriggerContext<TOption>) => ReactNode;\n selectedOption: TOption | undefined;\n triggerAriaLabel?: string;\n value: string;\n}) {\n const triggerContent = renderTrigger?.({selectedOption, value}) ??\n selectedOption?.label ?? value;\n\n return (\n <summary aria-label={triggerAriaLabel} className={`${standardTriggerClassName} ${densityClassName}`}>\n <span>{triggerContent}</span>\n <LuChevronDown\n aria-hidden=\"true\"\n className=\"selector-disclosure-arrow size-5 shrink-0 text-content-secondary transition-transform duration-200 ease-brand motion-reduce:transition-none group-open:rotate-180\"\n />\n </summary>\n );\n}\n\nexport function Selector<TOption extends SelectorOption = SelectorOption>({\n density = 'standard',\n label,\n onValueChange,\n options,\n renderOption,\n renderTrigger,\n triggerAriaLabel,\n value,\n whiteSurface = false,\n}: SelectorProps<TOption>) {\n const selectedOption = options.find((option) => option.value === value);\n const layout = getNavigationLayout(density);\n const surfaceClassName = whiteSurface ? 'bg-white' : 'bg-surface-raised';\n\n return (\n <details className={`selector-disclosure group relative ${layout.wrapper}`}>\n <SelectorTrigger\n densityClassName={layout.trigger}\n renderTrigger={renderTrigger}\n selectedOption={selectedOption}\n triggerAriaLabel={triggerAriaLabel}\n value={value}\n />\n <ul\n aria-label={label}\n className={`mt-2 w-full overflow-hidden rounded-control border border-border-subtle ${surfaceClassName} p-1 shadow-float md:absolute md:top-full md:right-0 md:z-60 md:min-w-44`}\n >\n {options.map((option) => {\n const isSelected = option.value === value;\n const select = () => onValueChange(option.value);\n const optionContent = renderOption?.({isSelected, option, select}) ??\n option.label;\n\n return (\n <li key={option.value}>\n <button\n aria-pressed={isSelected}\n className={`${optionClassName} text-left ${isSelected ? 'bg-brand-cloud text-content-primary' : ''}`}\n onClick={(event) => {\n select();\n closeSelectorDisclosure(event.currentTarget);\n }}\n type=\"button\"\n >\n {optionContent}\n </button>\n </li>\n );\n })}\n </ul>\n </details>\n );\n}\n","import {Fragment, type ReactNode} from 'react';\nimport {LuMenu} from 'react-icons/lu';\n\nexport type NavbarItem = {\n href: string;\n label: ReactNode;\n};\n\nexport type NavbarLinkRenderer = (props: {\n children: ReactNode;\n className: string;\n href: string;\n}) => ReactNode;\n\nexport type NavbarWidth = 'content' | 'wide';\nexport type NavbarLinkTreatment = 'plain' | 'underlined';\nexport type NavbarLinkWeight = 'bold' | 'semibold';\nexport type NavbarLayer = 'above' | 'standard';\nexport type NavbarMobileNavigation = {\n actions?: ReactNode;\n menuLabel: string;\n navigationLabel: string;\n};\n\nexport type NavbarProps = {\n actions?: ReactNode;\n brand: ReactNode;\n items: readonly NavbarItem[];\n layer?: NavbarLayer;\n linkTreatment?: NavbarLinkTreatment;\n linkWeight?: NavbarLinkWeight;\n mobileNavigation?: NavbarMobileNavigation;\n navigationLabel: string;\n renderLink?: NavbarLinkRenderer;\n width?: NavbarWidth;\n};\n\nconst baseLinkClassName =\n 'text-label text-content-secondary transition-colors duration-200 ease-brand hover:text-content-primary focus-visible:rounded-control focus-visible:outline-none focus-visible:shadow-focus';\n\nconst underlinedLinkClassName =\n 'relative after:absolute after:-bottom-1.5 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-action-primary after:transition-transform after:duration-200 after:ease-brand hover:after:scale-x-100 focus-visible:after:scale-x-100';\n\nconst mobileLinkClassName =\n 'flex min-h-12 items-center rounded-control px-3 text-label font-semibold text-content-secondary transition-colors duration-200 ease-brand hover:text-content-primary focus-visible:outline-none focus-visible:shadow-focus';\n\nexport function Navbar({\n actions,\n brand,\n items,\n layer = 'standard',\n linkTreatment = 'plain',\n linkWeight = 'semibold',\n mobileNavigation,\n navigationLabel,\n renderLink,\n width = 'content',\n}: NavbarProps) {\n const linkClassName = [\n baseLinkClassName,\n linkWeight === 'bold' ? 'font-bold' : 'font-semibold',\n linkTreatment === 'underlined' ? underlinedLinkClassName : '',\n ]\n .filter(Boolean)\n .join(' ');\n const containerWidthClassName = width === 'wide' ? 'max-w-7xl' : 'max-w-5xl';\n const layerClassName = layer === 'above' ? 'z-50' : 'z-40';\n const renderLinks = (className: string) =>\n items.map((item) => (\n <Fragment key={item.href}>\n {renderLink ? (\n renderLink({\n children: item.label,\n className,\n href: item.href,\n })\n ) : (\n <a className={className} href={item.href}>\n {item.label}\n </a>\n )}\n </Fragment>\n ));\n\n return (\n <header\n className={`sticky top-0 ${layerClassName} border-b border-border-subtle bg-surface-page/90 text-label font-semibold backdrop-blur-lg`}\n >\n <div\n className={`mx-auto flex min-h-20 ${containerWidthClassName} items-center justify-between gap-4 px-shell`}\n >\n <div className=\"shrink-0\">{brand}</div>\n <nav aria-label={navigationLabel} className=\"hidden items-center gap-6 lg:flex\">\n {renderLinks(linkClassName)}\n </nav>\n {actions}\n {mobileNavigation ? (\n <details className=\"relative md:hidden\">\n <summary\n aria-label={mobileNavigation.menuLabel}\n className=\"flex size-12 cursor-pointer list-none items-center justify-center rounded-control bg-action-primary text-content-inverse shadow-card transition-[translate,scale,background-color] duration-200 ease-brand active:scale-[0.97] focus-visible:outline-none focus-visible:shadow-focus [&::-webkit-details-marker]:hidden\"\n >\n <span className=\"sr-only\">{mobileNavigation.menuLabel}</span>\n <LuMenu aria-hidden=\"true\" className=\"size-6\" />\n </summary>\n <nav\n aria-label={mobileNavigation.navigationLabel}\n className=\"absolute top-14 right-0 w-72 rounded-card border border-border-subtle bg-surface-raised p-3 shadow-float\"\n >\n {renderLinks(mobileLinkClassName)}\n {mobileNavigation.actions ? (\n <div className=\"mt-2 space-y-3 border-t border-border-subtle pt-3\">\n {mobileNavigation.actions}\n </div>\n ) : null}\n </nav>\n </details>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/components/google-sign-in-button/google-sign-in-button.tsx","../src/components/selector/selector.tsx","../src/components/navbar/navbar.tsx"],"sourcesContent":["import type {ButtonHTMLAttributes, ReactNode} from 'react';\nimport {FcGoogle} from 'react-icons/fc';\n\nexport type GoogleSignInButtonProps = Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'children' | 'className' | 'type'\n> & {\n label: ReactNode;\n};\n\nconst buttonClassName =\n 'inline-flex min-h-12 w-full cursor-pointer items-center justify-center gap-3 rounded-control border border-border-subtle bg-surface-raised px-control-x py-control-y text-label font-semibold whitespace-nowrap text-content-primary shadow-card transition-[translate,scale,background-color] duration-200 ease-brand hover:-translate-y-0.5 hover:bg-brand-cloud active:scale-[0.97] focus-visible:outline-none focus-visible:shadow-focus disabled:cursor-wait disabled:opacity-70 motion-reduce:transform-none';\n\nexport function GoogleSignInButton(\n {label, ...buttonProps}: GoogleSignInButtonProps,\n) {\n return (\n <button {...buttonProps} className={buttonClassName} type=\"button\">\n <FcGoogle\n aria-hidden=\"true\"\n className=\"size-5 shrink-0\"\n data-testid=\"google-mark\"\n />\n {label}\n </button>\n );\n}\n","import {type ReactNode, useCallback, useEffect, useState} from 'react';\nimport {LuChevronDown} from 'react-icons/lu';\n\nexport type SelectorOption = {\n label: ReactNode;\n value: string;\n};\n\nexport type SelectorDensity = 'compact' | 'compact-mobile' | 'standard';\n\nexport type SelectorRenderOptionContext<TOption extends SelectorOption> = {\n isSelected: boolean;\n option: TOption;\n select: () => void;\n};\n\nexport type SelectorRenderTriggerContext<TOption extends SelectorOption> = {\n selectedOption: TOption | undefined;\n value: string;\n};\n\ntype SelectorBaseProps = {\n label: string;\n triggerAriaLabel?: string;\n value: string;\n};\n\nexport type SelectorProps<TOption extends SelectorOption = SelectorOption> =\n SelectorBaseProps & {\n density?: SelectorDensity;\n onValueChange: (value: string) => void;\n options: readonly TOption[];\n renderOption?: (context: SelectorRenderOptionContext<TOption>) => ReactNode;\n renderTrigger?: (\n context: SelectorRenderTriggerContext<TOption>,\n ) => ReactNode;\n whiteSurface?: boolean;\n };\n\nconst standardTriggerClassName =\n 'flex w-full cursor-pointer list-none select-none items-center justify-between rounded-control border border-border-subtle py-control-y text-label font-semibold text-content-primary shadow-card transition-colors duration-200 ease-brand hover:bg-brand-cloud focus-visible:outline-none focus-visible:shadow-focus [&::-webkit-details-marker]:hidden';\n\nconst optionClassName =\n 'flex min-h-12 w-full select-none items-center rounded-control px-3 text-label font-semibold text-content-secondary transition-colors duration-200 ease-brand hover:bg-brand-cloud hover:text-content-primary focus-visible:outline-none focus-visible:shadow-focus';\n\nfunction getNavigationLayout(density: SelectorDensity) {\n if (density === 'compact') {\n return {trigger: 'h-12 gap-2', wrapper: 'w-auto'};\n }\n\n if (density === 'compact-mobile') {\n return {\n trigger: 'h-12 gap-2 px-3 md:gap-4 md:px-4 md:min-w-32',\n wrapper: 'w-auto',\n };\n }\n\n return {trigger: 'min-h-12 gap-4 px-4 md:min-w-32', wrapper: 'w-full md:w-auto'};\n}\n\nfunction SelectorTrigger<TOption extends SelectorOption>({\n densityClassName,\n renderTrigger,\n selectedOption,\n triggerAriaLabel,\n value,\n}: {\n densityClassName: string;\n renderTrigger?: (context: SelectorRenderTriggerContext<TOption>) => ReactNode;\n selectedOption: TOption | undefined;\n triggerAriaLabel?: string;\n value: string;\n}) {\n const triggerContent = renderTrigger?.({selectedOption, value}) ??\n selectedOption?.label ?? value;\n\n return (\n <summary aria-label={triggerAriaLabel} className={`${standardTriggerClassName} ${densityClassName}`}>\n <span>{triggerContent}</span>\n <LuChevronDown\n aria-hidden=\"true\"\n className=\"selector-disclosure-arrow size-5 shrink-0 text-content-secondary transition-transform duration-200 ease-brand motion-reduce:transition-none group-open:rotate-180\"\n />\n </summary>\n );\n}\n\nexport function Selector<TOption extends SelectorOption = SelectorOption>({\n density = 'standard',\n label,\n onValueChange,\n options,\n renderOption,\n renderTrigger,\n triggerAriaLabel,\n value,\n whiteSurface = false,\n}: SelectorProps<TOption>) {\n const [selectorElement, setSelectorElement] =\n useState<HTMLDetailsElement | null>(null);\n const selectedOption = options.find((option) => option.value === value);\n const layout = getNavigationLayout(density);\n const surfaceClassName = whiteSurface ? 'bg-white' : 'bg-surface-raised';\n\n useEffect(() => {\n if (!selectorElement) {\n return;\n }\n\n const closeWhenPointerStartsOutside = (event: PointerEvent) => {\n if (\n !selectorElement.open ||\n !(event.target instanceof Node) ||\n selectorElement.contains(event.target)\n ) {\n return;\n }\n\n selectorElement.removeAttribute('open');\n };\n\n document.addEventListener('pointerdown', closeWhenPointerStartsOutside, true);\n\n return () => {\n document.removeEventListener('pointerdown', closeWhenPointerStartsOutside, true);\n };\n }, [selectorElement]);\n\n const selectOption = useCallback((option: TOption) => {\n selectorElement?.removeAttribute('open');\n onValueChange(option.value);\n }, [onValueChange, selectorElement]);\n\n return (\n <details\n ref={setSelectorElement}\n className={`selector-disclosure group relative ${layout.wrapper}`}\n >\n <SelectorTrigger\n densityClassName={layout.trigger}\n renderTrigger={renderTrigger}\n selectedOption={selectedOption}\n triggerAriaLabel={triggerAriaLabel}\n value={value}\n />\n <ul\n aria-label={label}\n className={`mt-2 w-full overflow-hidden rounded-control border border-border-subtle ${surfaceClassName} p-1 shadow-float md:absolute md:top-full md:right-0 md:z-60 md:min-w-44`}\n >\n {options.map((option) => {\n const isSelected = option.value === value;\n const select = () => selectOption(option);\n const optionContent = renderOption?.({isSelected, option, select}) ??\n option.label;\n\n return (\n <li key={option.value}>\n <button\n aria-pressed={isSelected}\n className={`${optionClassName} text-left ${isSelected ? 'bg-brand-cloud text-content-primary' : ''}`}\n onClick={select}\n type=\"button\"\n >\n {optionContent}\n </button>\n </li>\n );\n })}\n </ul>\n </details>\n );\n}\n","import {Fragment, type ReactNode} from 'react';\nimport {LuMenu} from 'react-icons/lu';\n\nexport type NavbarItem = {\n href: string;\n label: ReactNode;\n};\n\nexport type NavbarLinkRenderer = (props: {\n children: ReactNode;\n className: string;\n href: string;\n}) => ReactNode;\n\nexport type NavbarWidth = 'content' | 'wide';\nexport type NavbarLinkTreatment = 'plain' | 'underlined';\nexport type NavbarLinkWeight = 'bold' | 'semibold';\nexport type NavbarLayer = 'above' | 'standard';\nexport type NavbarMobileNavigation = {\n actions?: ReactNode;\n menuLabel: string;\n navigationLabel: string;\n};\n\nexport type NavbarProps = {\n actions?: ReactNode;\n brand: ReactNode;\n items: readonly NavbarItem[];\n layer?: NavbarLayer;\n linkTreatment?: NavbarLinkTreatment;\n linkWeight?: NavbarLinkWeight;\n mobileNavigation?: NavbarMobileNavigation;\n navigationLabel: string;\n renderLink?: NavbarLinkRenderer;\n width?: NavbarWidth;\n};\n\nconst baseLinkClassName =\n 'text-label text-content-secondary transition-colors duration-200 ease-brand hover:text-content-primary focus-visible:rounded-control focus-visible:outline-none focus-visible:shadow-focus';\n\nconst underlinedLinkClassName =\n 'relative after:absolute after:-bottom-1.5 after:left-0 after:h-0.5 after:w-full after:origin-left after:scale-x-0 after:bg-action-primary after:transition-transform after:duration-200 after:ease-brand hover:after:scale-x-100 focus-visible:after:scale-x-100';\n\nconst mobileLinkClassName =\n 'flex min-h-12 items-center rounded-control px-3 text-label font-semibold text-content-secondary transition-colors duration-200 ease-brand hover:text-content-primary focus-visible:outline-none focus-visible:shadow-focus';\n\nexport function Navbar({\n actions,\n brand,\n items,\n layer = 'standard',\n linkTreatment = 'plain',\n linkWeight = 'semibold',\n mobileNavigation,\n navigationLabel,\n renderLink,\n width = 'content',\n}: NavbarProps) {\n const linkClassName = [\n baseLinkClassName,\n linkWeight === 'bold' ? 'font-bold' : 'font-semibold',\n linkTreatment === 'underlined' ? underlinedLinkClassName : '',\n ]\n .filter(Boolean)\n .join(' ');\n const containerWidthClassName = width === 'wide' ? 'max-w-7xl' : 'max-w-5xl';\n const layerClassName = layer === 'above' ? 'z-50' : 'z-40';\n const renderLinks = (className: string) =>\n items.map((item) => (\n <Fragment key={item.href}>\n {renderLink ? (\n renderLink({\n children: item.label,\n className,\n href: item.href,\n })\n ) : (\n <a className={className} href={item.href}>\n {item.label}\n </a>\n )}\n </Fragment>\n ));\n\n return (\n <header\n className={`sticky top-0 ${layerClassName} border-b border-border-subtle bg-surface-page/90 text-label font-semibold backdrop-blur-lg`}\n >\n <div\n className={`mx-auto flex min-h-20 ${containerWidthClassName} items-center justify-between gap-4 px-shell`}\n >\n <div className=\"shrink-0\">{brand}</div>\n <nav aria-label={navigationLabel} className=\"hidden items-center gap-6 lg:flex\">\n {renderLinks(linkClassName)}\n </nav>\n {actions}\n {mobileNavigation ? (\n <details className=\"relative md:hidden\">\n <summary\n aria-label={mobileNavigation.menuLabel}\n className=\"flex size-12 cursor-pointer list-none items-center justify-center rounded-control bg-action-primary text-content-inverse shadow-card transition-[translate,scale,background-color] duration-200 ease-brand active:scale-[0.97] focus-visible:outline-none focus-visible:shadow-focus [&::-webkit-details-marker]:hidden\"\n >\n <span className=\"sr-only\">{mobileNavigation.menuLabel}</span>\n <LuMenu aria-hidden=\"true\" className=\"size-6\" />\n </summary>\n <nav\n aria-label={mobileNavigation.navigationLabel}\n className=\"absolute top-14 right-0 w-72 rounded-card border border-border-subtle bg-surface-raised p-3 shadow-float\"\n >\n {renderLinks(mobileLinkClassName)}\n {mobileNavigation.actions ? (\n <div className=\"mt-2 space-y-3 border-t border-border-subtle pt-3\">\n {mobileNavigation.actions}\n </div>\n ) : null}\n </nav>\n </details>\n ) : null}\n </div>\n </header>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs"]}
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@1e99/components",
3
- "version": "0.1.0",
4
- "description": "Shared React components for 1e99 applications.",
3
+ "version": "0.1.2",
4
+ "description": "Reusable React components built with Tailwind CSS.",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/1e99/components.git"
8
8
  },
9
- "license": "UNLICENSED",
9
+ "license": "MIT",
10
10
  "type": "module",
11
11
  "sideEffects": false,
12
12
  "main": "./dist/index.js",
@@ -19,6 +19,7 @@
19
19
  },
20
20
  "files": [
21
21
  "dist",
22
+ "LICENSE",
22
23
  "README.md"
23
24
  ],
24
25
  "scripts": {