@softize/opus 12.2.1 → 12.4.0
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/CHANGELOG.md +42 -0
- package/bin/cli.mjs +2 -2
- package/bin/lib/copy.mjs +4 -0
- package/docs/elevation-scale.md +2 -0
- package/docs/radius-scale.md +1 -0
- package/docs/relative-unit-scale.md +55 -0
- package/docs/releasing.md +2 -2
- package/package.json +3 -3
- package/registry/skills/build-opus-ui/SKILL.md +8 -4
- package/registry/skills/build-opus-ui/references/ui-patterns.md +2 -0
- package/registry/templates/app/package.json +1 -1
- package/src/ui/components/patterns/dock.tsx +182 -0
- package/src/ui/components/patterns/shell-nav.tsx +1 -0
- package/src/ui/components/patterns/sidebar.tsx +56 -13
- package/src/ui/components/primitives/accordion.tsx +1 -1
- package/src/ui/components/primitives/button.tsx +4 -4
- package/src/ui/components/primitives/calendar.tsx +2 -2
- package/src/ui/components/primitives/chat.tsx +1 -1
- package/src/ui/components/primitives/checkbox.tsx +1 -1
- package/src/ui/components/primitives/command.tsx +1 -1
- package/src/ui/components/primitives/dialog.tsx +1 -1
- package/src/ui/components/primitives/dot.tsx +39 -0
- package/src/ui/components/primitives/input-group.tsx +4 -4
- package/src/ui/components/primitives/input-otp.tsx +1 -1
- package/src/ui/components/primitives/input.tsx +4 -5
- package/src/ui/components/primitives/item.tsx +1 -1
- package/src/ui/components/primitives/radio-group.tsx +1 -1
- package/src/ui/components/primitives/scroll-area.tsx +1 -1
- package/src/ui/components/primitives/select.tsx +7 -4
- package/src/ui/components/primitives/switch.tsx +1 -1
- package/src/ui/components/primitives/table.tsx +14 -4
- package/src/ui/components/primitives/tabs.tsx +3 -3
- package/src/ui/components/primitives/textarea.tsx +1 -1
- package/src/ui/components/primitives/toggle.tsx +1 -1
- package/src/ui/components/primitives/tooltip.tsx +1 -1
- package/src/ui/components/primitives/truncate.tsx +9 -20
- package/src/ui/docs/DocBrowser.tsx +1 -1
- package/src/ui/docs/content/button.md +2 -2
- package/src/ui/docs/content/customization.md +5 -0
- package/src/ui/docs/content/dock.md +69 -0
- package/src/ui/docs/content/dot.md +17 -0
- package/src/ui/docs/content/select.md +1 -0
- package/src/ui/docs/content/sidebar.md +21 -0
- package/src/ui/docs/content/table.md +25 -24
- package/src/ui/docs/content/tabs.md +1 -1
- package/src/ui/docs/content/tokens.md +9 -9
- package/src/ui/docs/content/truncate.md +26 -0
- package/src/ui/docs/doc.tsx +1 -1
- package/src/ui/docs/registry.tsx +4 -0
- package/src/ui/lib/overflow.ts +34 -0
- package/src/ui/meta.ts +16 -4
- package/src/ui/react.tsx +12 -1
- package/src/ui/theme.css +27 -24
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,48 @@ Depois de qualquer bump, rode os gates (`typecheck` · `test` · `opus check` ·
|
|
|
7
7
|
`opus copy --check` · `base copy check` · `manifest:check`) — eles apontam o que a
|
|
8
8
|
mudança cobra do seu código.
|
|
9
9
|
|
|
10
|
+
## 12.4.0 — 2026-08-24
|
|
11
|
+
|
|
12
|
+
Superfícies de trabalho — canvas, editor, preview — ganham os dois lugares que faltavam. `Dock`
|
|
13
|
+
ancora as ferramentas à própria superfície, com `DockGroup` separando intenções e `DockAction`
|
|
14
|
+
distinguindo modo de execução; a barra é uma `toolbar` de verdade, com setas e Home/End entre as
|
|
15
|
+
ações. `SurfaceStatus` recebe o que a superfície diz sobre si — salvamento, versão, execução
|
|
16
|
+
percorrida — num canto estável, fora da barra: estado não é ação, e uma toolbar que carrega texto
|
|
17
|
+
vivo deixa de ser navegável como toolbar.
|
|
18
|
+
|
|
19
|
+
O truncamento passa a ter uma segunda forma. `Truncate` aceita `fade`, e a utilitária
|
|
20
|
+
`truncate-fade` esmaece o fim da linha no lugar das reticências. O hook `useOverflowing`, extraído
|
|
21
|
+
do próprio `Truncate`, fica exposto para quem precisa do sinal de corte sem o tooltip — é o que
|
|
22
|
+
mantém a máscara fora do texto que cabe inteiro.
|
|
23
|
+
|
|
24
|
+
`SidebarItem` vira a linha das árvores de navegação: `actions`, `onToggle`/`expanded` e
|
|
25
|
+
`dropPosition` são irmãos do destino, nunca botões aninhados, e o rótulo cortado esmaece até a
|
|
26
|
+
borda. Ele e o item do `ShellNav` também ganham foco visível com o anel do `Button` — antes a
|
|
27
|
+
navegação por teclado caía no anel do navegador, que destoa do tema.
|
|
28
|
+
|
|
29
|
+
Entram ainda `Dot`, para estado compacto onde o contexto já explica o significado, e
|
|
30
|
+
`Table variant="framed"`, que traz para o componente a moldura de datagrid antes montada à mão em
|
|
31
|
+
cada consumidor. `Select` passa a aplicar `className` na raiz do controle em todos os modos.
|
|
32
|
+
|
|
33
|
+
O `opus copy` reconhece `Dock.label`, `DockAction.label` e `DockAction.hint`: componente com texto
|
|
34
|
+
em prop precisa entrar no mapa de papéis, senão a copy sai do inventário em silêncio quando uma
|
|
35
|
+
superfície migra de `aria-label` para prop.
|
|
36
|
+
|
|
37
|
+
Tudo é aditivo e não exige migração. Consumidores que montavam a moldura da tabela à mão podem
|
|
38
|
+
trocar o wrapper por `variant="framed"` quando quiserem.
|
|
39
|
+
|
|
40
|
+
## 12.3.0 — 2026-08-23
|
|
41
|
+
|
|
42
|
+
O Opus deixa de definir `font-size` em `html`. A fonte raiz volta a pertencer ao navegador e à
|
|
43
|
+
aplicação, enquanto texto, espaçamento, arredondamento, foco, elevação e limites dimensionais da
|
|
44
|
+
UI usam uma escala relativa em `rem`. Assim, preferências de acessibilidade e identidades próprias
|
|
45
|
+
conseguem ajustar a densidade do documento sem disputar com o tema da biblioteca.
|
|
46
|
+
|
|
47
|
+
A mudança é visual e não exige migração de API. Sem uma regra do consumidor, `1rem` segue a
|
|
48
|
+
configuração do navegador. Aplicações que desejarem conservar a proporção anterior podem definir
|
|
49
|
+
`html { font-size: 93.75%; }` no próprio CSS. `px` permanece apenas em hairlines e compensações
|
|
50
|
+
geométricas inventariadas por teste.
|
|
51
|
+
|
|
10
52
|
## 12.2.1 — 2026-08-23
|
|
11
53
|
|
|
12
54
|
O cabeçalho de `Page` passa a alinhar `actions` à base do bloco formado por título e descrição.
|
package/bin/cli.mjs
CHANGED
|
@@ -509,8 +509,8 @@ Fundação de UI (só apps web — preset Tailwind + tema + flags do Opus):
|
|
|
509
509
|
tema/tsconfig/dep — avisa o que falta plugar (não edita seus arquivos: sem clobber)
|
|
510
510
|
|
|
511
511
|
Este comando materializa somente os artefatos Opus. O template encadeia
|
|
512
|
-
\`opus setup && base setup\`: a Base
|
|
513
|
-
está habilitado; Maestro continua opcional.
|
|
512
|
+
\`opus setup && base setup\`: a versão da Base registrada em base.json é obrigatória quando
|
|
513
|
+
o gate universal de copy está habilitado; Maestro continua opcional.
|
|
514
514
|
`)
|
|
515
515
|
return
|
|
516
516
|
}
|
package/bin/lib/copy.mjs
CHANGED
|
@@ -130,6 +130,10 @@ const JSX_PROP_ROLES = new Map([
|
|
|
130
130
|
['Alert', new Map([['title', 'title'], ['description', 'message']])],
|
|
131
131
|
['CommandInput', new Map([['placeholder', 'placeholder']])],
|
|
132
132
|
['DataState', new Map([['emptyText', 'empty-state'], ['errorText', 'error']])],
|
|
133
|
+
// A Dock nomeia a barra e cada ação por prop. Sem estas linhas, a copy sairia do inventário
|
|
134
|
+
// exatamente quando uma superfície migra de <Button aria-label> para <DockAction label>.
|
|
135
|
+
['Dock', new Map([['label', 'label']])],
|
|
136
|
+
['DockAction', new Map([['label', 'label'], ['hint', 'description']])],
|
|
133
137
|
['Input', new Map([['placeholder', 'placeholder']])],
|
|
134
138
|
['InputGroupInput', new Map([['placeholder', 'placeholder']])],
|
|
135
139
|
['InputGroupTextarea', new Map([['placeholder', 'placeholder']])],
|
package/docs/elevation-scale.md
CHANGED
|
@@ -20,6 +20,8 @@ uma superfície que precisava apenas de mais presença acabava recebendo um nome
|
|
|
20
20
|
- O Opus declara a composição diretamente em `--shadow-*`, mantendo a progressão do
|
|
21
21
|
Tailwind com camadas mais claras, difusas e spread negativo. Sem variável intermediária,
|
|
22
22
|
modifiers nativos como `shadow-xl/30` continuam funcionando.
|
|
23
|
+
- Deslocamento, desfoque e spread usam `rem`, portanto a elevação acompanha a escala raiz
|
|
24
|
+
escolhida pelo documento.
|
|
23
25
|
- Componentes escolhem a intensidade pelo contexto: `sm` para repouso, `md` para
|
|
24
26
|
flutuantes e `lg` para modais ou destaques fortes. Essa associação é default, não uma
|
|
25
27
|
restrição semântica.
|
package/docs/radius-scale.md
CHANGED
|
@@ -18,6 +18,7 @@ voltaria a acoplar forma e tipo de componente, problema removido na versão 10.0
|
|
|
18
18
|
|
|
19
19
|
- `--radius` passa a `0.75rem`. Aplicações podem sobrescrever essa base para ajustar a identidade
|
|
20
20
|
inteira sem forkar componentes.
|
|
21
|
+
- Os offsets da escala também usam `rem`, acompanhando a fonte raiz escolhida pelo documento.
|
|
21
22
|
- `rounded-xs` até `rounded-2xl` formam uma progressão derivada da mesma base. `rounded-full`
|
|
22
23
|
continua reservado a círculos e variantes explicitamente pill.
|
|
23
24
|
- Componentes escolhem o degrau pela escala visual. Em geral, detalhes e itens internos usam
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Escala relativa e fonte raiz
|
|
2
|
+
|
|
3
|
+
> **Status: decidido (ago/2026).** O documento define a fonte raiz; o Opus organiza sua
|
|
4
|
+
> escala visual em `rem` e não altera essa escolha.
|
|
5
|
+
|
|
6
|
+
## Contexto
|
|
7
|
+
|
|
8
|
+
O tema do Opus fixava `font-size: 15px` em `html`. Isso tornava a densidade uniforme, mas
|
|
9
|
+
também fazia uma biblioteca decidir a tipografia raiz de toda a aplicação. A regra alcançava
|
|
10
|
+
conteúdo que não pertencia ao Opus, reduzia a preferência do navegador e obrigava o consumidor
|
|
11
|
+
a desfazer o tema antes de escolher sua própria escala.
|
|
12
|
+
|
|
13
|
+
Parte das medidas visuais ainda usava `px`. Radius, elevação, foco, limites de conteúdo e texto
|
|
14
|
+
compacto deixavam de acompanhar a raiz mesmo quando a aplicação assumia essa responsabilidade.
|
|
15
|
+
|
|
16
|
+
## Decisão
|
|
17
|
+
|
|
18
|
+
- O tema do Opus não declara `font-size` em `html` nem em `:root`. O navegador e a aplicação
|
|
19
|
+
definem a fonte raiz.
|
|
20
|
+
- Medidas que pertencem à identidade e à escala da interface usam `rem`, diretamente ou pela
|
|
21
|
+
escala do Tailwind, que também é relativa à raiz.
|
|
22
|
+
- Aplicações ajustam a densidade no próprio CSS. Para conservar a proporção que antes resultava
|
|
23
|
+
de uma raiz de 15 sobre a base usual de 16, podem usar `html { font-size: 93.75%; }`.
|
|
24
|
+
- `px` fica restrito a encaixes técnicos que não devem crescer com a tipografia: espessura de
|
|
25
|
+
hairline, compensação exata de uma borda, ajuste óptico preso a esse traço e o raio efetivamente
|
|
26
|
+
infinito do scrollbar nativo. Cada ocorrência integra a allowlist do teste de contrato.
|
|
27
|
+
- Valores históricos no changelog podem continuar em `px`, pois descrevem versões anteriores;
|
|
28
|
+
eles não definem o contrato vigente.
|
|
29
|
+
|
|
30
|
+
## Consequências
|
|
31
|
+
|
|
32
|
+
- Sem uma regra da aplicação, `1rem` segue a configuração do navegador. A UI pode ficar maior
|
|
33
|
+
para consumidores que recebiam os antigos 15px; essa mudança é visual e intencional.
|
|
34
|
+
- Preferências de acessibilidade e identidades próprias deixam de disputar com um override da
|
|
35
|
+
biblioteca.
|
|
36
|
+
- Alterar a raiz na aplicação escala texto, espaçamento, radius, foco, elevação e limites
|
|
37
|
+
dimensionais do Opus como um conjunto.
|
|
38
|
+
- Hairlines e compensações de borda permanecem nítidas e geometricamente estáveis.
|
|
39
|
+
|
|
40
|
+
## Alternativas descartadas
|
|
41
|
+
|
|
42
|
+
- **Manter 15px como default:** conserva a densidade anterior, mas mantém a biblioteca como dona
|
|
43
|
+
do documento inteiro.
|
|
44
|
+
- **Declarar `font-size: 100%`:** costuma reproduzir o default, mas ainda é uma opinião redundante
|
|
45
|
+
e pode sobrescrever uma decisão anterior da aplicação.
|
|
46
|
+
- **Converter também hairlines para `rem`:** torna a regra absoluta, mas permite que bordas e
|
|
47
|
+
compensações deixem de coincidir quando a raiz muda.
|
|
48
|
+
|
|
49
|
+
## Verificação
|
|
50
|
+
|
|
51
|
+
- Um teste de contrato confirma que o tema não define a fonte raiz.
|
|
52
|
+
- O mesmo teste inventaria `px` no código vivo da UI e reprova qualquer ocorrência fora da
|
|
53
|
+
allowlist técnica.
|
|
54
|
+
- Testes de radius e elevação validam as fórmulas em `rem`.
|
|
55
|
+
- A documentação de tokens e a skill `build-opus-ui` orientam a responsabilidade do consumidor.
|
package/docs/releasing.md
CHANGED
|
@@ -53,7 +53,7 @@ pra testar publicação seria a guarda atrapalhando quem está experimentando.
|
|
|
53
53
|
|
|
54
54
|
**Gate de qualidade**: depois do bump e da materialização, mas antes de publicar, roda
|
|
55
55
|
`pnpm typecheck` + `pnpm test` + `pnpm copy:check` + `base copy check` e **aborta a release
|
|
56
|
-
se qualquer um falhar**. O Opus declara `@softize/base ^2.
|
|
56
|
+
se qualquer um falhar**. O Opus declara `@softize/base ^2.1.0` em `dependencies`, pois usa
|
|
57
57
|
suas APIs públicas de filesystem em runtime, e materializa os artefatos Base no próprio repo;
|
|
58
58
|
a release não baixa uma política ad hoc. Como o
|
|
59
59
|
Opus **ship source** (`.ts`, sem build), essa é a última barreira antes do tarball — sem ela,
|
|
@@ -63,7 +63,7 @@ um `tsc` vermelho, inventário desatualizado ou violação da política vaza pro
|
|
|
63
63
|
**Smoke do esqueleto**: depois do bump e antes do publish, o `release.sh` gera um app com
|
|
64
64
|
`opus create`, instala o **tarball exato** que vai ser publicado e roda os gates dele
|
|
65
65
|
(typecheck · test · `opus check` · `opus copy --check` · `base copy check` · manifest ·
|
|
66
|
-
build). O template exige `@softize/base ^2.
|
|
66
|
+
build). O template exige `@softize/base ^2.1.0`; publique a Base compatível antes do Opus.
|
|
67
67
|
O `minimumReleaseAgeExclude` do template inclui os dois pacotes, e o smoke executa os
|
|
68
68
|
fragmentos de pre-push para provar que o layout pnpm instalado resolve ambos os CLIs.
|
|
69
69
|
É o que pega o que typecheck+test não
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softize/opus",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.4.0",
|
|
4
4
|
"description": "End-to-end action protocol for TypeScript. Single package with subpath exports (core + adapters).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -201,7 +201,6 @@
|
|
|
201
201
|
},
|
|
202
202
|
"dependencies": {
|
|
203
203
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
204
|
-
"@softize/base": "^2.0.0",
|
|
205
204
|
"@radix-ui/react-checkbox": "^1.1.3",
|
|
206
205
|
"@radix-ui/react-dialog": "^1.1.4",
|
|
207
206
|
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
|
@@ -211,6 +210,7 @@
|
|
|
211
210
|
"@radix-ui/react-slot": "^1.1.1",
|
|
212
211
|
"@radix-ui/react-tabs": "^1.1.2",
|
|
213
212
|
"@radix-ui/react-tooltip": "^1.1.6",
|
|
213
|
+
"@softize/base": "^2.1.0",
|
|
214
214
|
"@tailwindcss/typography": "^0.5.20",
|
|
215
215
|
"@types/markdown-it": "^14.1.2",
|
|
216
216
|
"class-variance-authority": "^0.7.1",
|
|
@@ -234,11 +234,11 @@
|
|
|
234
234
|
"zod-to-json-schema": "^3.23.0"
|
|
235
235
|
},
|
|
236
236
|
"peerDependencies": {
|
|
237
|
-
"@opentelemetry/api": "^1.9.0",
|
|
238
237
|
"@anthropic-ai/sdk": ">=0.35.0",
|
|
239
238
|
"@aws-sdk/client-s3": "^3.0.0",
|
|
240
239
|
"@aws-sdk/s3-request-presigner": "^3.0.0",
|
|
241
240
|
"@hookform/resolvers": "^3.0.0",
|
|
241
|
+
"@opentelemetry/api": "^1.9.0",
|
|
242
242
|
"@tanstack/react-query": "^5.0.0",
|
|
243
243
|
"bullmq": "^5.0.0",
|
|
244
244
|
"fastify": "^5.0.0",
|
|
@@ -19,18 +19,22 @@ existentes, mantendo navegação observável e componentes reutilizáveis sem la
|
|
|
19
19
|
o produto precisa de deep link, back/forward ou refresh.
|
|
20
20
|
4. Manter margem e posicionamento no consumidor; componente reutilizável controla apenas
|
|
21
21
|
seu interior.
|
|
22
|
-
5.
|
|
23
|
-
|
|
22
|
+
5. Não definir a fonte raiz em uma biblioteca ou componente. O navegador e a aplicação são
|
|
23
|
+
responsáveis por `font-size` em `html`; medidas escaláveis da UI usam `rem` ou a escala
|
|
24
|
+
relativa do Tailwind. Reservar `px` a hairlines e compensações presas à geometria da borda,
|
|
25
|
+
com justificativa e cobertura explícitas.
|
|
26
|
+
6. Evoluir um pattern compartilhado apenas quando a recorrência e o contrato estiverem claros.
|
|
27
|
+
7. Tratar tokens de superfície como pares indivisíveis no mesmo fragmento de classes:
|
|
24
28
|
`bg-card text-card-foreground` e `bg-popover text-popover-foreground`. Não depender da
|
|
25
29
|
igualdade atual com `--foreground`, porque o app pode sobrescrever cada par.
|
|
26
|
-
|
|
30
|
+
8. Usar diretamente a escala `rounded-*`; não criar radius por nome de componente quando
|
|
27
31
|
`rounded-xs` a `rounded-2xl` já expressam a forma. Escolher o degrau pela escala visual:
|
|
28
32
|
detalhes e itens internos tendem a `xs`/`sm`, controles e flutuantes compactos a `md`,
|
|
29
33
|
molduras estruturais a `lg`, e superfícies principais ou modais a `xl`. Essa associação
|
|
30
34
|
orienta o default, não cria uma restrição semântica. Em aninhamento, evitar moldura dupla e
|
|
31
35
|
reduzir o raio interno; em grupos conectados, remover os raios das arestas internas. Tamanho
|
|
32
36
|
e forma permanecem eixos separados; usar `shape="pill"` quando a pílula for intencional.
|
|
33
|
-
|
|
37
|
+
9. Testar estados de loading, vazio, erro, sucesso, permissão e interação relevante.
|
|
34
38
|
|
|
35
39
|
## Verificação
|
|
36
40
|
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
operação, não a uma tela isolada.
|
|
6
6
|
- URL representa estado que precisa sobreviver a refresh, deep link ou histórico.
|
|
7
7
|
- Componentes compartilhados não impõem margem externa; páginas e shells compõem layout.
|
|
8
|
+
- A fonte raiz pertence ao navegador e à aplicação. Medidas escaláveis usam `rem` ou a escala
|
|
9
|
+
relativa do Tailwind; `px` fica restrito a hairlines e compensações ligadas a essas bordas.
|
|
8
10
|
- Superfície semântica e foreground são um par local (`bg-card text-card-foreground`,
|
|
9
11
|
`bg-popover text-popover-foreground`); herança do foreground global não substitui o par.
|
|
10
12
|
- Forma usa a escala `rounded-*`; nome de componente não cria uma segunda escala de radius.
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <Dock /> — a barra de ferramentas que flutua sobre uma superfície de trabalho.
|
|
3
|
+
*
|
|
4
|
+
* Canvas, editor e preview têm o mesmo problema: as ferramentas não cabem num cabeçalho
|
|
5
|
+
* sem empilhar mais uma faixa de chrome sobre a trilha que a aplicação já desenha. A Dock
|
|
6
|
+
* resolve isso ancorando as ações à própria superfície, agrupadas e sempre visíveis.
|
|
7
|
+
*
|
|
8
|
+
* O componente é dono da moldura, dos separadores entre grupos, da forma dos botões e da
|
|
9
|
+
* semântica de toolbar (`role="toolbar"` com navegação por setas). QUAIS ferramentas
|
|
10
|
+
* existem e o que cada uma faz continua sendo da aplicação.
|
|
11
|
+
*
|
|
12
|
+
* Requer `<TooltipProvider>` na raiz do app — cada ação nomeia-se por tooltip.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import * as React from 'react'
|
|
16
|
+
import { cn } from '../../lib/cn.ts'
|
|
17
|
+
import { Button } from '../primitives/button.tsx'
|
|
18
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../primitives/tooltip.tsx'
|
|
19
|
+
|
|
20
|
+
const POSITION = {
|
|
21
|
+
bottom: 'bottom-3 left-1/2 -translate-x-1/2',
|
|
22
|
+
'bottom-left': 'bottom-3 left-3',
|
|
23
|
+
'bottom-right': 'bottom-3 right-3',
|
|
24
|
+
} as const
|
|
25
|
+
|
|
26
|
+
export interface DockProps {
|
|
27
|
+
/** Aresta da superfície onde a barra se ancora. */
|
|
28
|
+
position?: keyof typeof POSITION
|
|
29
|
+
/** Nome acessível da barra; é o que a tecnologia assistiva anuncia ao entrar nela. */
|
|
30
|
+
label: string
|
|
31
|
+
className?: string
|
|
32
|
+
children: React.ReactNode
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Barra flutuante ancorada à superfície. O contêiner precisa ser `relative`. */
|
|
36
|
+
export function Dock({ position = 'bottom', label, className, children }: DockProps): React.ReactElement {
|
|
37
|
+
const ref = React.useRef<HTMLDivElement>(null)
|
|
38
|
+
|
|
39
|
+
// Navegação de toolbar: as setas andam entre as ações, e Home/End vão às pontas. Sem isso
|
|
40
|
+
// uma barra com dez ícones cobra dez Tabs de quem navega por teclado.
|
|
41
|
+
const onKeyDown = (event: React.KeyboardEvent<HTMLDivElement>): void => {
|
|
42
|
+
const keys = ['ArrowRight', 'ArrowLeft', 'Home', 'End']
|
|
43
|
+
if (!keys.includes(event.key) || ref.current === null) return
|
|
44
|
+
const items = Array.from(ref.current.querySelectorAll<HTMLButtonElement>('button:not([disabled])'))
|
|
45
|
+
if (items.length === 0) return
|
|
46
|
+
const current = items.indexOf(document.activeElement as HTMLButtonElement)
|
|
47
|
+
if (current === -1) return
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
const next =
|
|
50
|
+
event.key === 'Home'
|
|
51
|
+
? 0
|
|
52
|
+
: event.key === 'End'
|
|
53
|
+
? items.length - 1
|
|
54
|
+
: event.key === 'ArrowRight'
|
|
55
|
+
? (current + 1) % items.length
|
|
56
|
+
: (current - 1 + items.length) % items.length
|
|
57
|
+
items[next]?.focus()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
ref={ref}
|
|
63
|
+
data-slot="dock"
|
|
64
|
+
role="toolbar"
|
|
65
|
+
aria-label={label}
|
|
66
|
+
aria-orientation="horizontal"
|
|
67
|
+
onKeyDown={onKeyDown}
|
|
68
|
+
className={cn(
|
|
69
|
+
'absolute z-10 flex max-w-[calc(100%-1.5rem)] flex-row items-center gap-1.5 overflow-x-auto',
|
|
70
|
+
'rounded-2xl border border-border bg-background/95 p-1.5 shadow-lg backdrop-blur',
|
|
71
|
+
POSITION[position],
|
|
72
|
+
className,
|
|
73
|
+
)}
|
|
74
|
+
>
|
|
75
|
+
{children}
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Grupo de ações afins. A divisória entre grupos é do componente, não do consumidor. */
|
|
81
|
+
export function DockGroup({ className, children }: { className?: string; children: React.ReactNode }): React.ReactElement {
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
data-slot="dock-group"
|
|
85
|
+
className={cn(
|
|
86
|
+
'flex shrink-0 flex-row items-center gap-1 border-l border-border pl-1.5',
|
|
87
|
+
'first:border-l-0 first:pl-0',
|
|
88
|
+
className,
|
|
89
|
+
)}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface DockActionProps {
|
|
97
|
+
icon: React.ReactNode
|
|
98
|
+
/** Nome da ação: vai para o tooltip e para o nome acessível. */
|
|
99
|
+
label: string
|
|
100
|
+
/** Presente quando a ação liga um modo; ausente quando ela apenas executa. */
|
|
101
|
+
pressed?: boolean
|
|
102
|
+
disabled?: boolean
|
|
103
|
+
/** Conteúdo do tooltip quando ele precisa dizer mais que o rótulo. */
|
|
104
|
+
hint?: React.ReactNode
|
|
105
|
+
className?: string
|
|
106
|
+
onClick: () => void
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Ação de ícone da Dock. Com `pressed`, comunica modo ligado em vez de execução. */
|
|
110
|
+
export function DockAction({ icon, label, pressed, disabled = false, hint, className, onClick }: DockActionProps): React.ReactElement {
|
|
111
|
+
return (
|
|
112
|
+
<Tooltip>
|
|
113
|
+
<TooltipTrigger asChild>
|
|
114
|
+
<Button
|
|
115
|
+
data-slot="dock-action"
|
|
116
|
+
size="icon"
|
|
117
|
+
variant={pressed === true ? 'default' : 'ghost'}
|
|
118
|
+
aria-label={label}
|
|
119
|
+
aria-pressed={pressed}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
onClick={onClick}
|
|
122
|
+
className={cn('size-9 shrink-0 rounded-xl', className)}
|
|
123
|
+
>
|
|
124
|
+
{icon}
|
|
125
|
+
</Button>
|
|
126
|
+
</TooltipTrigger>
|
|
127
|
+
<TooltipContent>{hint ?? label}</TooltipContent>
|
|
128
|
+
</Tooltip>
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const STATUS_POSITION = {
|
|
133
|
+
'top-right': 'top-3 right-3',
|
|
134
|
+
'top-left': 'top-3 left-3',
|
|
135
|
+
} as const
|
|
136
|
+
|
|
137
|
+
export interface SurfaceStatusProps {
|
|
138
|
+
/** Canto da superfície onde o estado se ancora. */
|
|
139
|
+
position?: keyof typeof STATUS_POSITION
|
|
140
|
+
/** Ações do recurso aberto — em geral o mesmo menu que a linha dele tem na navegação. */
|
|
141
|
+
actions?: React.ReactNode
|
|
142
|
+
className?: string
|
|
143
|
+
children?: React.ReactNode
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* O que a superfície diz sobre SI — salvamento, versão, execução percorrida — flutuando no canto,
|
|
148
|
+
* separado das ferramentas. Fica fora da Dock de propósito: estado não é ação, e uma toolbar que
|
|
149
|
+
* carrega texto vivo deixa de ser navegável como toolbar.
|
|
150
|
+
*
|
|
151
|
+
* A região é `role="status"` com `aria-live="polite"`: a mudança é anunciada sem roubar o foco.
|
|
152
|
+
* Mensagem de sucesso é transitória por natureza; falha permanece até o estado mudar. Quem decide
|
|
153
|
+
* isso é a aplicação, que conhece o ciclo — o componente só reserva o lugar.
|
|
154
|
+
*/
|
|
155
|
+
export function SurfaceStatus({ position = 'top-right', actions, className, children }: SurfaceStatusProps): React.ReactElement {
|
|
156
|
+
const hasActions = actions !== undefined && actions !== null
|
|
157
|
+
return (
|
|
158
|
+
<div
|
|
159
|
+
data-slot="surface-status"
|
|
160
|
+
className={cn(
|
|
161
|
+
'pointer-events-none absolute z-10 flex max-w-[calc(100%-1.5rem)] flex-row items-center gap-2',
|
|
162
|
+
'rounded-full border border-border bg-background/95 py-1.5 pl-3 text-xs text-muted-foreground shadow-sm backdrop-blur',
|
|
163
|
+
hasActions ? 'pr-1' : 'pr-3',
|
|
164
|
+
STATUS_POSITION[position],
|
|
165
|
+
className,
|
|
166
|
+
)}
|
|
167
|
+
>
|
|
168
|
+
{/* Só o texto é região viva: anunciar o menu a cada mudança de estado seria ruído. */}
|
|
169
|
+
<span role="status" aria-live="polite" className="flex min-w-0 flex-row items-center gap-2">
|
|
170
|
+
{children}
|
|
171
|
+
</span>
|
|
172
|
+
{hasActions && (
|
|
173
|
+
<span
|
|
174
|
+
data-slot="surface-status-actions"
|
|
175
|
+
className="pointer-events-auto flex shrink-0 items-center [&_button]:size-6 [&_button]:rounded-full [&_svg]:size-3.5"
|
|
176
|
+
>
|
|
177
|
+
{actions}
|
|
178
|
+
</span>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
@@ -74,6 +74,7 @@ function renderItem(item: ShellNavItem, activeId: string | undefined, onSelect:
|
|
|
74
74
|
className={cn(
|
|
75
75
|
'flex w-full items-center gap-2.5 rounded-md py-1.5 text-left text-sm transition-colors',
|
|
76
76
|
'disabled:pointer-events-none disabled:opacity-40',
|
|
77
|
+
'outline-none focus-visible:ring-2 focus-visible:ring-ring/50',
|
|
77
78
|
railed ? 'justify-center px-0' : 'px-2.5',
|
|
78
79
|
active ? 'bg-muted font-medium text-foreground' : 'text-foreground/80 hover:bg-muted/60',
|
|
79
80
|
)}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { createContext, useContext, type ReactNode } from 'react'
|
|
1
|
+
import { createContext, useContext, type HTMLAttributes, type ReactNode } from 'react'
|
|
2
|
+
import { ChevronRight } from 'lucide-react'
|
|
3
|
+
import { useOverflowing } from '../../lib/overflow.ts'
|
|
2
4
|
import { cn } from '../../lib/cn.ts'
|
|
3
5
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../primitives/tooltip.tsx'
|
|
4
6
|
|
|
@@ -50,22 +52,58 @@ export interface SidebarItemProps {
|
|
|
50
52
|
badge?: ReactNode
|
|
51
53
|
active?: boolean
|
|
52
54
|
disabled?: boolean
|
|
55
|
+
/** Controles irmãos do alvo de navegação (ex.: menu de ações). */
|
|
56
|
+
actions?: ReactNode
|
|
57
|
+
/** Presente quando a linha desdobra filhos; o controle fica fora do botão principal. */
|
|
58
|
+
onToggle?: () => void
|
|
59
|
+
expanded?: boolean
|
|
60
|
+
/** Posição indicada durante drag-and-drop; bordas inserem, centro recebe dentro. */
|
|
61
|
+
dropPosition?: 'before' | 'inside' | 'after'
|
|
62
|
+
/** Props de drag-and-drop aplicadas à linha, sem acoplar a um driver. */
|
|
63
|
+
dragProps?: HTMLAttributes<HTMLDivElement>
|
|
64
|
+
/** Complemento do tooltip no rail recolhido. */
|
|
65
|
+
tooltipHint?: ReactNode
|
|
53
66
|
className?: string
|
|
54
67
|
onClick: () => void
|
|
55
68
|
}
|
|
56
69
|
|
|
57
|
-
/**
|
|
58
|
-
export function SidebarItem({ label, icon, badge, active = false, disabled = false, className, onClick }: SidebarItemProps): React.ReactElement {
|
|
70
|
+
/** Linha única de navegação: o alvo principal e seus controles são irmãos, nunca botões aninhados. */
|
|
71
|
+
export function SidebarItem({ label, icon, badge, active = false, disabled = false, actions, onToggle, expanded = false, dropPosition, dragProps, tooltipHint, className, onClick }: SidebarItemProps): React.ReactElement {
|
|
59
72
|
const collapsed = useSidebarCollapsed()
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
const hasActions = actions !== undefined && actions !== null
|
|
74
|
+
// O rótulo cortado esmaece em vez de terminar em reticências; a medição do transbordo
|
|
75
|
+
// mantém a máscara fora do rótulo que cabe inteiro na linha.
|
|
76
|
+
const { ref: labelRef, overflowing: labelCut } = useOverflowing<HTMLSpanElement>(label)
|
|
77
|
+
if (collapsed) {
|
|
78
|
+
const button = (
|
|
79
|
+
<button type="button" disabled={disabled} aria-current={active ? 'page' : undefined} onClick={onClick} className={cn('mx-auto grid size-9 place-items-center rounded-md transition-colors disabled:pointer-events-none disabled:opacity-40', 'outline-none focus-visible:ring-2 focus-visible:ring-ring/50', active ? 'bg-muted font-medium text-foreground' : 'text-foreground/80 hover:bg-muted/60', className)}>
|
|
80
|
+
{icon}
|
|
81
|
+
</button>
|
|
82
|
+
)
|
|
83
|
+
return <div {...dragProps}><Tooltip><TooltipTrigger asChild>{button}</TooltipTrigger><TooltipContent side="right">{label}{tooltipHint}</TooltipContent></Tooltip></div>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div data-slot="sidebar-item" className={cn('group/sidebar-item relative flex items-center rounded-md transition-colors', disabled ? 'text-muted-foreground/50 opacity-40' : 'hover:bg-muted/60', active && 'bg-muted', dropPosition === 'inside' && 'bg-primary/5 ring-2 ring-inset ring-primary/60', className)} {...dragProps}>
|
|
88
|
+
{(dropPosition === 'before' || dropPosition === 'after') && <span data-slot="sidebar-item-drop-indicator" className={cn('pointer-events-none absolute inset-x-1 z-20 h-0.5 rounded-full bg-primary before:absolute before:-left-1 before:top-1/2 before:size-2 before:-translate-y-1/2 before:rounded-full before:border-2 before:border-primary before:bg-background', dropPosition === 'before' ? '-top-px' : '-bottom-px')} />}
|
|
89
|
+
{icon !== undefined && (
|
|
90
|
+
<span data-slot="sidebar-item-leading" className="pointer-events-none absolute left-2.5 top-1/2 z-10 grid size-4 -translate-y-1/2 place-items-center">
|
|
91
|
+
<span className={cn('absolute inset-0 flex items-center justify-center transition-opacity', onToggle !== undefined && 'group-hover/sidebar-item:opacity-0 group-has-[[data-slot=sidebar-item-toggle]:focus-visible]/sidebar-item:opacity-0')}>{icon}</span>
|
|
92
|
+
{onToggle !== undefined && (
|
|
93
|
+
<button type="button" data-slot="sidebar-item-toggle" onClick={onToggle} aria-label={expanded ? `Fechar ${label}` : `Abrir ${label}`} aria-expanded={expanded} className="pointer-events-none absolute inset-0 grid place-items-center rounded-sm text-muted-foreground opacity-0 transition-[color,opacity] outline-none hover:text-foreground group-hover/sidebar-item:pointer-events-auto group-hover/sidebar-item:opacity-100 focus-visible:pointer-events-auto focus-visible:opacity-100 focus-visible:ring-2 focus-visible:ring-ring/50">
|
|
94
|
+
<ChevronRight className={cn('size-3 transition-transform', expanded && 'rotate-90')} />
|
|
95
|
+
</button>
|
|
96
|
+
)}
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
<button type="button" disabled={disabled} aria-current={active ? 'page' : undefined} onClick={onClick} className={cn('flex min-w-0 flex-1 items-center gap-2.5 rounded-md px-2.5 py-1.5 text-left text-sm disabled:pointer-events-none', 'outline-none focus-visible:ring-2 focus-visible:ring-ring/50', disabled ? 'text-muted-foreground/50' : active ? 'font-medium text-foreground' : 'text-foreground/80')}>
|
|
100
|
+
{icon !== undefined && <span data-slot="sidebar-item-leading-space" aria-hidden className="size-4 shrink-0" />}
|
|
101
|
+
<span ref={labelRef} data-slot="sidebar-item-label" className={cn('min-w-0 flex-1 overflow-hidden whitespace-nowrap group-has-[[data-slot=dot]]/sidebar-item:mr-[1.1875rem]', labelCut && 'truncate-fade')}>{label}</span>
|
|
102
|
+
{badge !== undefined && <span data-slot="sidebar-item-badge" className={cn('shrink-0 transition-opacity has-[[data-slot=dot]]:absolute has-[[data-slot=dot]]:right-1 has-[[data-slot=dot]]:top-1/2 has-[[data-slot=dot]]:z-10 has-[[data-slot=dot]]:grid has-[[data-slot=dot]]:size-6 has-[[data-slot=dot]]:-translate-y-1/2 has-[[data-slot=dot]]:place-items-center', hasActions && 'group-hover/sidebar-item:has-[[data-slot=dot]]:opacity-0')}>{badge}</span>}
|
|
103
|
+
</button>
|
|
104
|
+
{hasActions && <div data-slot="sidebar-item-actions" className="pointer-events-none absolute right-1 top-1/2 z-10 flex -translate-y-1/2 items-center opacity-0 transition-opacity group-hover/sidebar-item:pointer-events-auto group-hover/sidebar-item:opacity-100 has-[:focus-visible]:pointer-events-auto has-[:focus-visible]:opacity-100 has-[[data-state=open]]:pointer-events-auto has-[[data-state=open]]:opacity-100 [&_button]:grid [&_button]:size-6 [&_button]:place-items-center [&_button]:p-0 [&_svg]:size-3.5">{actions}</div>}
|
|
105
|
+
</div>
|
|
66
106
|
)
|
|
67
|
-
if (!collapsed) return button
|
|
68
|
-
return <Tooltip><TooltipTrigger asChild>{button}</TooltipTrigger><TooltipContent side="right">{label}</TooltipContent></Tooltip>
|
|
69
107
|
}
|
|
70
108
|
|
|
71
109
|
export interface SidebarNavGroup {
|
|
@@ -86,6 +124,11 @@ const subFilled = (subgroup: SidebarNavSubgroup): boolean => subgroup.items.leng
|
|
|
86
124
|
const groupFilled = (group: SidebarNavGroup): boolean =>
|
|
87
125
|
(group.items?.length ?? 0) > 0 || (group.subgroups?.some(subFilled) ?? false)
|
|
88
126
|
|
|
127
|
+
/** Rótulo discreto de um grupo de navegação, compartilhado por listas e árvores. */
|
|
128
|
+
export function SidebarGroupLabel({ className, children }: { className?: string; children: ReactNode }): React.ReactElement {
|
|
129
|
+
return <div data-slot="sidebar-group-label" className={cn('px-2.5 pb-1 text-sm font-medium text-muted-foreground/70', className)}>{children}</div>
|
|
130
|
+
}
|
|
131
|
+
|
|
89
132
|
/** Navegação controlada para Sidebar. Header/footer continuam slots explícitos do pai. */
|
|
90
133
|
export function SidebarNav({ groups, activeId, onSelect, navLabel = 'Navegação', className }: { groups: SidebarNavGroup[]; activeId?: string; onSelect: (id: string) => void; navLabel?: string; className?: string }): React.ReactElement {
|
|
91
134
|
const collapsed = useSidebarCollapsed()
|
|
@@ -101,10 +144,10 @@ export function SidebarNav({ groups, activeId, onSelect, navLabel = 'Navegação
|
|
|
101
144
|
const body = (
|
|
102
145
|
<nav aria-label={navLabel} data-slot="sidebar-nav" className={cn('space-y-4 p-2', className)}>
|
|
103
146
|
{groups.map((group, gi) => !groupFilled(group) ? null : <div key={gi} className="space-y-0.5">
|
|
104
|
-
{!collapsed && titled(group.label) && <
|
|
147
|
+
{!collapsed && titled(group.label) && <SidebarGroupLabel>{group.label}</SidebarGroupLabel>}
|
|
105
148
|
{group.items?.map((item) => renderItem(item))}
|
|
106
149
|
{group.subgroups?.map((subgroup, si) => !subFilled(subgroup) ? null : <div key={si} className="space-y-0.5">
|
|
107
|
-
{!collapsed && titled(subgroup.label) && <div className="pb-0.5 pl-4 pr-2.5 pt-1.5 text-[
|
|
150
|
+
{!collapsed && titled(subgroup.label) && <div className="pb-0.5 pl-4 pr-2.5 pt-1.5 text-[0.6875rem] font-medium text-muted-foreground/50">{subgroup.label}</div>}
|
|
108
151
|
{subgroup.items.map((item) => renderItem(item, titled(subgroup.label)))}
|
|
109
152
|
</div>)}
|
|
110
153
|
</div>)}
|
|
@@ -33,7 +33,7 @@ function AccordionTrigger({
|
|
|
33
33
|
<AccordionPrimitive.Trigger
|
|
34
34
|
data-slot="accordion-trigger"
|
|
35
35
|
className={cn(
|
|
36
|
-
"flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-[
|
|
36
|
+
"flex flex-1 items-start justify-between gap-4 rounded-md py-4 text-left text-sm font-medium transition-all outline-none hover:underline focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 [&[data-state=open]>svg]:rotate-180",
|
|
37
37
|
className
|
|
38
38
|
)}
|
|
39
39
|
{...props}
|
|
@@ -31,11 +31,11 @@ export const buttonVariants = cva(
|
|
|
31
31
|
sm: 'h-8 gap-1.5 px-3 has-[>svg]:px-2.5',
|
|
32
32
|
lg: 'h-10 px-6 has-[>svg]:px-4',
|
|
33
33
|
icon: 'size-9',
|
|
34
|
-
// Ícone na escala `sm` (
|
|
35
|
-
//
|
|
36
|
-
//
|
|
34
|
+
// Ícone na escala `sm` (2rem) — o par do `sm` para fileiras densas, onde os irmãos
|
|
35
|
+
// também usam `sm`. Sem ele, um botão de ícone ao lado desses controles ficava
|
|
36
|
+
// 0.25rem mais alto ou virava override local.
|
|
37
37
|
'icon-sm': 'size-8',
|
|
38
|
-
// Ícone
|
|
38
|
+
// Ícone menor (1.5rem) — a ação dentro de um campo (clear/mostrar senha no
|
|
39
39
|
// `trailing` do Input/Select), que precisa caber folgado num h-9. É o mesmo tamanho
|
|
40
40
|
// do `icon-xs` do InputGroupButton; sem ele, o exemplo virava `className="size-6"`.
|
|
41
41
|
'icon-xs': 'size-6',
|
|
@@ -72,7 +72,7 @@ function Calendar({
|
|
|
72
72
|
defaultClassNames.dropdowns
|
|
73
73
|
),
|
|
74
74
|
dropdown_root: cn(
|
|
75
|
-
"relative rounded-md border border-input has-focus:border-ring has-focus:ring-[
|
|
75
|
+
"relative rounded-md border border-input has-focus:border-ring has-focus:ring-[0.1875rem] has-focus:ring-ring/50",
|
|
76
76
|
defaultClassNames.dropdown_root
|
|
77
77
|
),
|
|
78
78
|
dropdown: cn(
|
|
@@ -206,7 +206,7 @@ function CalendarDayButton({
|
|
|
206
206
|
data-range-end={modifiers.range_end}
|
|
207
207
|
data-range-middle={modifiers.range_middle}
|
|
208
208
|
className={cn(
|
|
209
|
-
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[
|
|
209
|
+
"flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[0.1875rem] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-md data-[range-end=true]:rounded-r-md data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:rounded-md data-[range-start=true]:rounded-l-md data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-accent-foreground [&>span]:text-xs [&>span]:opacity-70",
|
|
210
210
|
defaultClassNames.day,
|
|
211
211
|
className
|
|
212
212
|
)}
|
|
@@ -136,7 +136,7 @@ const ChatTranscript = React.memo(function ChatTranscript({
|
|
|
136
136
|
className="flex min-h-0 flex-1 flex-col gap-3 overflow-y-auto p-4"
|
|
137
137
|
>
|
|
138
138
|
{items.length === 0 && !indicatorVisible && greeting !== undefined && (
|
|
139
|
-
<div data-slot="chat-empty" className="m-auto max-w-[
|
|
139
|
+
<div data-slot="chat-empty" className="m-auto max-w-[17.5rem] text-center text-muted-foreground">
|
|
140
140
|
<div className="mb-2 text-4xl">✦</div>
|
|
141
141
|
<p className="text-sm leading-relaxed">{greeting}</p>
|
|
142
142
|
</div>
|
|
@@ -12,7 +12,7 @@ function Checkbox({
|
|
|
12
12
|
<CheckboxPrimitive.Root
|
|
13
13
|
data-slot="checkbox"
|
|
14
14
|
className={cn(
|
|
15
|
-
"peer size-4 shrink-0 rounded-[
|
|
15
|
+
"peer size-4 shrink-0 rounded-[0.25rem] border border-input transition-shadow outline-none focus-visible:border-ring focus-visible:ring-[0.1875rem] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:bg-input/30 dark:aria-invalid:ring-destructive/40 dark:data-[state=checked]:bg-primary",
|
|
16
16
|
className
|
|
17
17
|
)}
|
|
18
18
|
{...props}
|
|
@@ -88,7 +88,7 @@ function CommandList({
|
|
|
88
88
|
<CommandPrimitive.List
|
|
89
89
|
data-slot="command-list"
|
|
90
90
|
className={cn(
|
|
91
|
-
"max-h-[
|
|
91
|
+
"max-h-[18.75rem] scroll-py-1 overflow-x-hidden overflow-y-auto",
|
|
92
92
|
className
|
|
93
93
|
)}
|
|
94
94
|
{...props}
|