@1e99/components 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +129 -2
- package/dist/index.d.ts +71 -0
- package/dist/index.js +189 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -4
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,3 +1,130 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @1e99/components
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Componentes React reutilizáveis, estilizados com Tailwind CSS.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
pnpm add @1e99/components
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
O pacote declara `react`, `react-dom`, `react-icons` e `tailwindcss` como peer
|
|
12
|
+
dependencies.
|
|
13
|
+
|
|
14
|
+
### Tailwind CSS 4
|
|
15
|
+
|
|
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:
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
@import "tailwindcss";
|
|
22
|
+
@source "../../node_modules/@1e99/components/dist";
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Os componentes usam tokens Tailwind do tema do aplicativo, como
|
|
26
|
+
`surface-page`, `content-primary`, `content-secondary`, `border-subtle`,
|
|
27
|
+
`action-primary`, `surface-raised`, `content-inverse`, `shadow-card`,
|
|
28
|
+
`shadow-float`, `shadow-focus`, `ease-brand`, `shell`, `control` e `card`.
|
|
29
|
+
O pacote não cria tokens nem importa CSS global.
|
|
30
|
+
|
|
31
|
+
## Uso
|
|
32
|
+
|
|
33
|
+
```tsx
|
|
34
|
+
import {useState} from "react";
|
|
35
|
+
import {
|
|
36
|
+
GoogleSignInButton,
|
|
37
|
+
Navbar,
|
|
38
|
+
Selector,
|
|
39
|
+
} from "@1e99/components";
|
|
40
|
+
|
|
41
|
+
const languageOptions = [
|
|
42
|
+
{label: "Português", value: "pt"},
|
|
43
|
+
{label: "English", value: "en"},
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
function Header() {
|
|
47
|
+
const [language, setLanguage] = useState("pt");
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<Navbar
|
|
51
|
+
actions={
|
|
52
|
+
<div className="hidden items-center gap-3 md:flex">
|
|
53
|
+
<Selector
|
|
54
|
+
label="Idioma"
|
|
55
|
+
onValueChange={setLanguage}
|
|
56
|
+
options={languageOptions}
|
|
57
|
+
value={language}
|
|
58
|
+
/>
|
|
59
|
+
<GoogleSignInButton
|
|
60
|
+
label="Entrar com Google"
|
|
61
|
+
onClick={() => undefined}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
}
|
|
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"
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
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.
|
|
80
|
+
|
|
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 |
|
|
88
|
+
|
|
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.
|
|
92
|
+
|
|
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.
|
|
98
|
+
|
|
99
|
+
## Desenvolvimento
|
|
100
|
+
|
|
101
|
+
```sh
|
|
102
|
+
pnpm lint
|
|
103
|
+
pnpm lint:fix
|
|
104
|
+
pnpm format
|
|
105
|
+
pnpm storybook
|
|
106
|
+
pnpm test
|
|
107
|
+
pnpm test:stories
|
|
108
|
+
pnpm build
|
|
109
|
+
pnpm build-storybook
|
|
110
|
+
pnpm pack:check
|
|
111
|
+
```
|
|
112
|
+
|
|
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:
|
|
116
|
+
|
|
117
|
+
```sh
|
|
118
|
+
pnpm exec playwright install chromium
|
|
119
|
+
sudo pnpm exec playwright install-deps chromium
|
|
120
|
+
```
|
|
121
|
+
|
|
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/`.
|
|
125
|
+
|
|
126
|
+
## Licença
|
|
127
|
+
|
|
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.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ButtonHTMLAttributes, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
type GoogleSignInButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'children' | 'className' | 'type'> & {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
};
|
|
7
|
+
declare function GoogleSignInButton({ label, ...buttonProps }: GoogleSignInButtonProps): react.JSX.Element;
|
|
8
|
+
|
|
9
|
+
type SelectorOption = {
|
|
10
|
+
label: ReactNode;
|
|
11
|
+
value: string;
|
|
12
|
+
};
|
|
13
|
+
type SelectorDensity = 'compact' | 'compact-mobile' | 'standard';
|
|
14
|
+
type SelectorRenderOptionContext<TOption extends SelectorOption> = {
|
|
15
|
+
isSelected: boolean;
|
|
16
|
+
option: TOption;
|
|
17
|
+
select: () => void;
|
|
18
|
+
};
|
|
19
|
+
type SelectorRenderTriggerContext<TOption extends SelectorOption> = {
|
|
20
|
+
selectedOption: TOption | undefined;
|
|
21
|
+
value: string;
|
|
22
|
+
};
|
|
23
|
+
type SelectorBaseProps = {
|
|
24
|
+
label: string;
|
|
25
|
+
triggerAriaLabel?: string;
|
|
26
|
+
value: string;
|
|
27
|
+
};
|
|
28
|
+
type SelectorProps<TOption extends SelectorOption = SelectorOption> = SelectorBaseProps & {
|
|
29
|
+
density?: SelectorDensity;
|
|
30
|
+
onValueChange: (value: string) => void;
|
|
31
|
+
options: readonly TOption[];
|
|
32
|
+
renderOption?: (context: SelectorRenderOptionContext<TOption>) => ReactNode;
|
|
33
|
+
renderTrigger?: (context: SelectorRenderTriggerContext<TOption>) => ReactNode;
|
|
34
|
+
whiteSurface?: boolean;
|
|
35
|
+
};
|
|
36
|
+
declare function Selector<TOption extends SelectorOption = SelectorOption>({ density, label, onValueChange, options, renderOption, renderTrigger, triggerAriaLabel, value, whiteSurface, }: SelectorProps<TOption>): react.JSX.Element;
|
|
37
|
+
|
|
38
|
+
type NavbarItem = {
|
|
39
|
+
href: string;
|
|
40
|
+
label: ReactNode;
|
|
41
|
+
};
|
|
42
|
+
type NavbarLinkRenderer = (props: {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
className: string;
|
|
45
|
+
href: string;
|
|
46
|
+
}) => ReactNode;
|
|
47
|
+
type NavbarWidth = 'content' | 'wide';
|
|
48
|
+
type NavbarLinkTreatment = 'plain' | 'underlined';
|
|
49
|
+
type NavbarLinkWeight = 'bold' | 'semibold';
|
|
50
|
+
type NavbarLayer = 'above' | 'standard';
|
|
51
|
+
type NavbarMobileNavigation = {
|
|
52
|
+
actions?: ReactNode;
|
|
53
|
+
menuLabel: string;
|
|
54
|
+
navigationLabel: string;
|
|
55
|
+
};
|
|
56
|
+
type NavbarProps = {
|
|
57
|
+
actions?: ReactNode;
|
|
58
|
+
brand: ReactNode;
|
|
59
|
+
items: readonly NavbarItem[];
|
|
60
|
+
layer?: NavbarLayer;
|
|
61
|
+
linkTreatment?: NavbarLinkTreatment;
|
|
62
|
+
linkWeight?: NavbarLinkWeight;
|
|
63
|
+
mobileNavigation?: NavbarMobileNavigation;
|
|
64
|
+
navigationLabel: string;
|
|
65
|
+
renderLink?: NavbarLinkRenderer;
|
|
66
|
+
width?: NavbarWidth;
|
|
67
|
+
};
|
|
68
|
+
declare function Navbar({ actions, brand, items, layer, linkTreatment, linkWeight, mobileNavigation, navigationLabel, renderLink, width, }: NavbarProps): react.JSX.Element;
|
|
69
|
+
|
|
70
|
+
export { GoogleSignInButton, Navbar, Selector };
|
|
71
|
+
export type { GoogleSignInButtonProps, NavbarItem, NavbarLayer, NavbarLinkRenderer, NavbarLinkTreatment, NavbarLinkWeight, NavbarMobileNavigation, NavbarProps, NavbarWidth, SelectorDensity, SelectorOption, SelectorProps, SelectorRenderOptionContext, SelectorRenderTriggerContext };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/components/google-sign-in-button/google-sign-in-button.tsx
|
|
2
|
+
import { FcGoogle } from "react-icons/fc";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
var buttonClassName = "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";
|
|
5
|
+
function GoogleSignInButton({ label, ...buttonProps }) {
|
|
6
|
+
return /* @__PURE__ */ jsxs("button", { ...buttonProps, className: buttonClassName, type: "button", children: [
|
|
7
|
+
/* @__PURE__ */ jsx(
|
|
8
|
+
FcGoogle,
|
|
9
|
+
{
|
|
10
|
+
"aria-hidden": "true",
|
|
11
|
+
className: "size-5 shrink-0",
|
|
12
|
+
"data-testid": "google-mark"
|
|
13
|
+
}
|
|
14
|
+
),
|
|
15
|
+
label
|
|
16
|
+
] });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/components/selector/selector.tsx
|
|
20
|
+
import { LuChevronDown } from "react-icons/lu";
|
|
21
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
22
|
+
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
|
+
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
|
+
function getNavigationLayout(density) {
|
|
28
|
+
if (density === "compact") {
|
|
29
|
+
return { trigger: "h-12 gap-2", wrapper: "w-auto" };
|
|
30
|
+
}
|
|
31
|
+
if (density === "compact-mobile") {
|
|
32
|
+
return {
|
|
33
|
+
trigger: "h-12 gap-2 px-3 md:gap-4 md:px-4 md:min-w-32",
|
|
34
|
+
wrapper: "w-auto"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return { trigger: "min-h-12 gap-4 px-4 md:min-w-32", wrapper: "w-full md:w-auto" };
|
|
38
|
+
}
|
|
39
|
+
function SelectorTrigger({
|
|
40
|
+
densityClassName,
|
|
41
|
+
renderTrigger,
|
|
42
|
+
selectedOption,
|
|
43
|
+
triggerAriaLabel,
|
|
44
|
+
value
|
|
45
|
+
}) {
|
|
46
|
+
const triggerContent = renderTrigger?.({ selectedOption, value }) ?? selectedOption?.label ?? value;
|
|
47
|
+
return /* @__PURE__ */ jsxs2("summary", { "aria-label": triggerAriaLabel, className: `${standardTriggerClassName} ${densityClassName}`, children: [
|
|
48
|
+
/* @__PURE__ */ jsx2("span", { children: triggerContent }),
|
|
49
|
+
/* @__PURE__ */ jsx2(
|
|
50
|
+
LuChevronDown,
|
|
51
|
+
{
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
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"
|
|
54
|
+
}
|
|
55
|
+
)
|
|
56
|
+
] });
|
|
57
|
+
}
|
|
58
|
+
function Selector({
|
|
59
|
+
density = "standard",
|
|
60
|
+
label,
|
|
61
|
+
onValueChange,
|
|
62
|
+
options,
|
|
63
|
+
renderOption,
|
|
64
|
+
renderTrigger,
|
|
65
|
+
triggerAriaLabel,
|
|
66
|
+
value,
|
|
67
|
+
whiteSurface = false
|
|
68
|
+
}) {
|
|
69
|
+
const selectedOption = options.find((option) => option.value === value);
|
|
70
|
+
const layout = getNavigationLayout(density);
|
|
71
|
+
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
|
+
})
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
] });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/components/navbar/navbar.tsx
|
|
112
|
+
import { Fragment } from "react";
|
|
113
|
+
import { LuMenu } from "react-icons/lu";
|
|
114
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
115
|
+
var baseLinkClassName = "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";
|
|
116
|
+
var underlinedLinkClassName = "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";
|
|
117
|
+
var mobileLinkClassName = "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";
|
|
118
|
+
function Navbar({
|
|
119
|
+
actions,
|
|
120
|
+
brand,
|
|
121
|
+
items,
|
|
122
|
+
layer = "standard",
|
|
123
|
+
linkTreatment = "plain",
|
|
124
|
+
linkWeight = "semibold",
|
|
125
|
+
mobileNavigation,
|
|
126
|
+
navigationLabel,
|
|
127
|
+
renderLink,
|
|
128
|
+
width = "content"
|
|
129
|
+
}) {
|
|
130
|
+
const linkClassName = [
|
|
131
|
+
baseLinkClassName,
|
|
132
|
+
linkWeight === "bold" ? "font-bold" : "font-semibold",
|
|
133
|
+
linkTreatment === "underlined" ? underlinedLinkClassName : ""
|
|
134
|
+
].filter(Boolean).join(" ");
|
|
135
|
+
const containerWidthClassName = width === "wide" ? "max-w-7xl" : "max-w-5xl";
|
|
136
|
+
const layerClassName = layer === "above" ? "z-50" : "z-40";
|
|
137
|
+
const renderLinks = (className) => items.map((item) => /* @__PURE__ */ jsx3(Fragment, { children: renderLink ? renderLink({
|
|
138
|
+
children: item.label,
|
|
139
|
+
className,
|
|
140
|
+
href: item.href
|
|
141
|
+
}) : /* @__PURE__ */ jsx3("a", { className, href: item.href, children: item.label }) }, item.href));
|
|
142
|
+
return /* @__PURE__ */ jsx3(
|
|
143
|
+
"header",
|
|
144
|
+
{
|
|
145
|
+
className: `sticky top-0 ${layerClassName} border-b border-border-subtle bg-surface-page/90 text-label font-semibold backdrop-blur-lg`,
|
|
146
|
+
children: /* @__PURE__ */ jsxs3(
|
|
147
|
+
"div",
|
|
148
|
+
{
|
|
149
|
+
className: `mx-auto flex min-h-20 ${containerWidthClassName} items-center justify-between gap-4 px-shell`,
|
|
150
|
+
children: [
|
|
151
|
+
/* @__PURE__ */ jsx3("div", { className: "shrink-0", children: brand }),
|
|
152
|
+
/* @__PURE__ */ jsx3("nav", { "aria-label": navigationLabel, className: "hidden items-center gap-6 lg:flex", children: renderLinks(linkClassName) }),
|
|
153
|
+
actions,
|
|
154
|
+
mobileNavigation ? /* @__PURE__ */ jsxs3("details", { className: "relative md:hidden", children: [
|
|
155
|
+
/* @__PURE__ */ jsxs3(
|
|
156
|
+
"summary",
|
|
157
|
+
{
|
|
158
|
+
"aria-label": mobileNavigation.menuLabel,
|
|
159
|
+
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",
|
|
160
|
+
children: [
|
|
161
|
+
/* @__PURE__ */ jsx3("span", { className: "sr-only", children: mobileNavigation.menuLabel }),
|
|
162
|
+
/* @__PURE__ */ jsx3(LuMenu, { "aria-hidden": "true", className: "size-6" })
|
|
163
|
+
]
|
|
164
|
+
}
|
|
165
|
+
),
|
|
166
|
+
/* @__PURE__ */ jsxs3(
|
|
167
|
+
"nav",
|
|
168
|
+
{
|
|
169
|
+
"aria-label": mobileNavigation.navigationLabel,
|
|
170
|
+
className: "absolute top-14 right-0 w-72 rounded-card border border-border-subtle bg-surface-raised p-3 shadow-float",
|
|
171
|
+
children: [
|
|
172
|
+
renderLinks(mobileLinkClassName),
|
|
173
|
+
mobileNavigation.actions ? /* @__PURE__ */ jsx3("div", { className: "mt-2 space-y-3 border-t border-border-subtle pt-3", children: mobileNavigation.actions }) : null
|
|
174
|
+
]
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
] }) : null
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
GoogleSignInButton,
|
|
186
|
+
Navbar,
|
|
187
|
+
Selector
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,82 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1e99/components",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Reusable React components built with Tailwind CSS.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/1e99/components.git"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"type": "module",
|
|
11
|
+
"sideEffects": false,
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"import": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsup",
|
|
27
|
+
"format": "pnpm lint:fix",
|
|
28
|
+
"lint": "eslint . --max-warnings 0",
|
|
29
|
+
"lint:fix": "eslint . --fix --max-warnings 0",
|
|
30
|
+
"prepare": "husky",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"test": "vitest run --project unit",
|
|
33
|
+
"test:watch": "vitest --project unit",
|
|
34
|
+
"storybook": "storybook dev -p 6006",
|
|
35
|
+
"build-storybook": "storybook build",
|
|
36
|
+
"test:stories": "vitest run --project storybook",
|
|
37
|
+
"pack:check": "pnpm build && npm pack --dry-run"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0",
|
|
42
|
+
"react-icons": "^5.7.0",
|
|
43
|
+
"tailwindcss": "^4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@eslint/js": "^9.39.5",
|
|
47
|
+
"@storybook/addon-a11y": "^10.6.3",
|
|
48
|
+
"@storybook/addon-vitest": "^10.6.3",
|
|
49
|
+
"@storybook/react-vite": "^10.6.3",
|
|
50
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
51
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
52
|
+
"@testing-library/react": "^16.3.2",
|
|
53
|
+
"@testing-library/user-event": "^14.6.1",
|
|
54
|
+
"@types/node": "26.6.5",
|
|
55
|
+
"@types/react": "^19.2.14",
|
|
56
|
+
"@types/react-dom": "^19.2.3",
|
|
57
|
+
"@vitejs/plugin-react": "^5.1.4",
|
|
58
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
59
|
+
"eslint": "^9.39.5",
|
|
60
|
+
"eslint-config-google": "^0.14.0",
|
|
61
|
+
"eslint-plugin-react": "^7.37.5",
|
|
62
|
+
"eslint-plugin-react-hooks": "^7.1.1",
|
|
63
|
+
"globals": "^16.5.0",
|
|
64
|
+
"husky": "^9.1.7",
|
|
65
|
+
"jsdom": "^28.0.0",
|
|
66
|
+
"playwright": "1.64.1",
|
|
67
|
+
"react": "^19.2.4",
|
|
68
|
+
"react-dom": "^19.2.4",
|
|
69
|
+
"react-icons": "5.7.0",
|
|
70
|
+
"storybook": "^10.6.3",
|
|
71
|
+
"tailwindcss": "^4.3.3",
|
|
72
|
+
"tsup": "^9.0.0",
|
|
73
|
+
"typescript": "^5.9.3",
|
|
74
|
+
"typescript-eslint": "^8.72.0",
|
|
75
|
+
"vite": "^8.3.5",
|
|
76
|
+
"vitest": "^5.0.5"
|
|
77
|
+
},
|
|
78
|
+
"packageManager": "pnpm@12.9.1",
|
|
79
|
+
"publishConfig": {
|
|
80
|
+
"access": "public"
|
|
81
|
+
}
|
|
82
|
+
}
|