mirror of
https://github.com/pocket-id/pocket-id.git
synced 2026-10-06 09:49:04 +02:00
chore: add vite-plus as node toolchain (#1806)
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pocket-id",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm",
|
||||
"image": "ghcr.io/voidzero-dev/vite-plus:1.0.0",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/go:1": {
|
||||
"version": "1.27"
|
||||
@@ -12,12 +12,14 @@
|
||||
"golang.go",
|
||||
"svelte.svelte-vscode",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"esbenp.prettier-vscode"
|
||||
"oxc.oxc-vscode",
|
||||
"VoidZero.vite-plus-extension-pack"
|
||||
]
|
||||
}
|
||||
},
|
||||
"containerEnv": {
|
||||
"HOST": "0.0.0.0"
|
||||
},
|
||||
"postCreateCommand": "npm install --prefix frontend && cd backend && go mod download"
|
||||
"remoteUser": "vp",
|
||||
"postCreateCommand": "vp install && cd backend && go mod download"
|
||||
}
|
||||
|
||||
@@ -27,8 +27,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
|
||||
- name: Setup Vite+
|
||||
uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: true
|
||||
run-install: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
|
||||
@@ -49,8 +49,12 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
|
||||
- name: Setup Vite+
|
||||
uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: true
|
||||
run-install: false
|
||||
|
||||
- name: Set up Depot CLI
|
||||
uses: depot/setup-action@91bc8495a33ebfc504ffc89e5674379ccf23c29c # v1.7.2
|
||||
@@ -136,12 +140,11 @@ jobs:
|
||||
run: docker load < /tmp/francis-image.tar
|
||||
|
||||
- name: Install test dependencies
|
||||
run: pnpm --filter pocket-id-tests install --frozen-lockfile
|
||||
run: vp -C tests install --frozen-lockfile
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: ./tests
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
run: pnpm exec playwright install --with-deps chromium
|
||||
run: vp -C tests exec playwright install --with-deps chromium
|
||||
|
||||
- name: Run Docker containers
|
||||
working-directory: ./tests/setup
|
||||
@@ -184,8 +187,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Run Playwright tests
|
||||
working-directory: ./tests
|
||||
run: pnpm exec playwright test
|
||||
run: vp -C tests exec playwright test
|
||||
|
||||
- name: Upload Test Report
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
|
||||
@@ -68,13 +68,17 @@ jobs:
|
||||
echo "tag=v$version" >> "$GITHUB_OUTPUT"
|
||||
echo "Preparing release v$version from $GITHUB_SHA." >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
- name: Setup Vite+
|
||||
if: steps.version.outputs.tag != ''
|
||||
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
|
||||
uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: true
|
||||
run-install: false
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.version.outputs.tag != ''
|
||||
run: pnpm install --frozen-lockfile
|
||||
run: vp install --frozen-lockfile
|
||||
|
||||
- name: Update version and changelog
|
||||
if: steps.version.outputs.tag != ''
|
||||
@@ -86,7 +90,7 @@ jobs:
|
||||
printf '%s\n' "$VERSION" > .version
|
||||
jq --arg version "$VERSION" '.version = $version' frontend/package.json > "$RUNNER_TEMP/package.json"
|
||||
mv "$RUNNER_TEMP/package.json" frontend/package.json
|
||||
pnpm --dir frontend exec prettier --write package.json
|
||||
vp fmt frontend/package.json
|
||||
git cliff --prepend CHANGELOG.md --tag "$RELEASE_TAG" --unreleased
|
||||
|
||||
- name: Create bot app token
|
||||
@@ -166,8 +170,12 @@ jobs:
|
||||
gh release create "$RELEASE_TAG" --verify-tag --title "$RELEASE_TAG" --notes-file "$RUNNER_TEMP/release-notes.md" --draft
|
||||
fi
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
|
||||
- name: Setup Vite+
|
||||
uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: true
|
||||
run-install: false
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
|
||||
@@ -35,19 +35,21 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Node.js and pnpm
|
||||
uses: pnpm/setup@fbda4c85fc2e1e08721cd8763afea8f48d60f024 # v3.0.0
|
||||
- name: Setup Vite+
|
||||
uses: voidzero-dev/setup-vp@3754dd7dbdb32bd8f6d28b6043de13ad3a75f21f # v1.21.1
|
||||
with:
|
||||
node-version-file: .node-version
|
||||
cache: true
|
||||
run-install: false
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm --filter pocket-id-frontend install --frozen-lockfile
|
||||
run: vp -C frontend install --frozen-lockfile
|
||||
|
||||
- name: Build Pocket ID Frontend
|
||||
working-directory: frontend
|
||||
run: pnpm --filter pocket-id-frontend build
|
||||
run: vp -C frontend run build
|
||||
|
||||
- name: Add svelte-check problem matcher
|
||||
run: echo "::add-matcher::.github/svelte-check-matcher.json"
|
||||
|
||||
- name: Run svelte-check
|
||||
working-directory: frontend
|
||||
run: pnpm --filter pocket-id-frontend check
|
||||
run: vp -C frontend run check
|
||||
|
||||
+4
-1
@@ -65,4 +65,7 @@ dist/
|
||||
|
||||
/backend/go.work
|
||||
/backend/go.work.sum
|
||||
.deepsec/
|
||||
.deepsec/
|
||||
# Vite+ generated git hook dispatcher
|
||||
.vite-hooks/_
|
||||
.vitest/
|
||||
|
||||
+2
-2
@@ -7,8 +7,8 @@ project_name: pocket-id
|
||||
|
||||
before:
|
||||
hooks:
|
||||
- pnpm install --frozen-lockfile
|
||||
- pnpm -C frontend build
|
||||
- vp install --frozen-lockfile
|
||||
- vp -C frontend run build
|
||||
- mkdir -p .tmp
|
||||
- mv backend/frontend/dist/cyclonedx/frontend.cdx.json .tmp/frontend.cdx.json
|
||||
- rm -r backend/frontend/dist/cyclonedx
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
vp staged
|
||||
Vendored
+2
-1
@@ -3,6 +3,7 @@
|
||||
"golang.go",
|
||||
"svelte.svelte-vscode",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"esbenp.prettier-vscode"
|
||||
"oxc.oxc-vscode",
|
||||
"VoidZero.vite-plus-extension-pack"
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -20,7 +20,7 @@
|
||||
"request": "launch",
|
||||
"envFile": "${workspaceFolder}/frontend/.env",
|
||||
"cwd": "${workspaceFolder}/frontend",
|
||||
"runtimeExecutable": "npm",
|
||||
"runtimeExecutable": "vp",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
|
||||
Vendored
+5
-1
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"go.buildTags": "e2etest unit",
|
||||
"prettier.documentSelectors": ["**/*.svelte"]
|
||||
"editor.defaultFormatter": "oxc.oxc-vscode",
|
||||
"oxc.fmt.disableNestedConfig": true,
|
||||
"[svelte]": {
|
||||
"editor.defaultFormatter": "oxc.oxc-vscode"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ golangci-lint run # lint (config: backend/.golangci.yml - includes build tags)
|
||||
|
||||
# frontend/ (or root)
|
||||
pnpm check # svelte-check — the ONLY frontend type gate (no unit tests exist)
|
||||
pnpm lint # prettier --check && eslint (note: not enforced by CI)
|
||||
pnpm format # prettier --write — REQUIRED before opening a PR
|
||||
pnpm lint # vp check — oxlint + oxfmt check via Vite+ (note: not enforced by CI)
|
||||
pnpm format # vp fmt — REQUIRED before opening a PR
|
||||
```
|
||||
|
||||
End-to-end (needs Docker; **stop any local backend on `:1411` first** — see gotchas):
|
||||
|
||||
+13
-14
@@ -1,4 +1,4 @@
|
||||
# syntax=docker/dockerfile:1.7
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
# This file uses multi-stage builds to build the application from source, including the front-end
|
||||
|
||||
@@ -6,21 +6,20 @@
|
||||
ARG BUILD_TAGS=""
|
||||
|
||||
# Stage 1: Build Frontend
|
||||
FROM node:24-alpine AS frontend-builder
|
||||
RUN corepack enable
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/voidzero-dev/vite-plus:1.0.0 AS frontend-builder
|
||||
ENV CI=true
|
||||
WORKDIR /app
|
||||
|
||||
WORKDIR /build
|
||||
COPY --chown=vp:vp package.json pnpm-workspace.yaml pnpm-lock.yaml .node-version ./
|
||||
COPY --chown=vp:vp --parents frontend/package.json tests/package.json email-templates/package.json ./
|
||||
RUN --mount=type=cache,id=pocket-id-pnpm,target=/home/vp/.local/share/pnpm/store,sharing=locked,uid=1000,gid=1000 \
|
||||
vp -C frontend install --frozen-lockfile --prefer-offline
|
||||
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml ./
|
||||
COPY frontend/package.json ./frontend/
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store \
|
||||
pnpm --filter pocket-id-frontend install --frozen-lockfile
|
||||
COPY --chown=vp:vp ./frontend ./frontend/
|
||||
|
||||
COPY ./frontend ./frontend/
|
||||
|
||||
RUN --mount=type=cache,target=/build/frontend/node_modules/.vite \
|
||||
--mount=type=cache,target=/build/frontend/.svelte-kit \
|
||||
BUILD_OUTPUT_PATH=dist pnpm --filter pocket-id-frontend run build
|
||||
RUN --mount=type=cache,target=/app/frontend/node_modules/.vite,uid=1000,gid=1000 \
|
||||
--mount=type=cache,target=/app/frontend/.svelte-kit,uid=1000,gid=1000 \
|
||||
BUILD_OUTPUT_PATH=dist vp -C frontend run build
|
||||
|
||||
# Stage 2: Build Backend
|
||||
FROM golang:1.27.1-alpine AS backend-builder
|
||||
@@ -31,7 +30,7 @@ RUN --mount=type=cache,target=/go/pkg/mod \
|
||||
go mod download
|
||||
|
||||
COPY ./backend ./
|
||||
COPY --from=frontend-builder /build/frontend/dist ./frontend/dist
|
||||
COPY --from=frontend-builder /app/frontend/dist ./frontend/dist
|
||||
COPY .version .version
|
||||
|
||||
WORKDIR /build/cmd
|
||||
|
||||
+45
-49
@@ -1,78 +1,74 @@
|
||||
import { render } from "react-email";
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
import { render } from 'react-email';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
const outputDir = "../backend/resources/email-templates";
|
||||
const outputDir = '../backend/resources/email-templates';
|
||||
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
function getTemplateName(filename: string): string {
|
||||
return filename.replace(".tsx", "");
|
||||
return filename.replace('.tsx', '');
|
||||
}
|
||||
|
||||
async function buildTemplateFile(
|
||||
Component: any,
|
||||
templateName: string,
|
||||
isPlainText: boolean
|
||||
) {
|
||||
const rendered = await render(
|
||||
Component(Component.TemplateProps),
|
||||
isPlainText ? { plainText: true } : {},
|
||||
);
|
||||
async function buildTemplateFile(Component: any, templateName: string, isPlainText: boolean) {
|
||||
const rendered = await render(
|
||||
Component(Component.TemplateProps),
|
||||
isPlainText ? { plainText: true } : {}
|
||||
);
|
||||
|
||||
// Normalize quotes
|
||||
const normalized = rendered.replace(/"/g, '"');
|
||||
// Normalize quotes
|
||||
const normalized = rendered.replace(/"/g, '"');
|
||||
|
||||
const goTemplate = `{{define "root"}}${normalized}{{end}}`;
|
||||
const suffix = isPlainText ? "_text.tmpl" : "_html.tmpl";
|
||||
const templatePath = path.join(outputDir, `${templateName}${suffix}`);
|
||||
const goTemplate = `{{define "root"}}${normalized}{{end}}`;
|
||||
const suffix = isPlainText ? '_text.tmpl' : '_html.tmpl';
|
||||
const templatePath = path.join(outputDir, `${templateName}${suffix}`);
|
||||
|
||||
fs.writeFileSync(templatePath, goTemplate);
|
||||
fs.writeFileSync(templatePath, goTemplate);
|
||||
}
|
||||
|
||||
async function discoverAndBuildTemplates() {
|
||||
console.log("Discovering and building email templates...");
|
||||
console.log('Discovering and building email templates...');
|
||||
|
||||
const emailsDir = "./emails";
|
||||
const files = fs.readdirSync(emailsDir);
|
||||
const emailsDir = './emails';
|
||||
const files = fs.readdirSync(emailsDir);
|
||||
|
||||
for (const file of files) {
|
||||
if (!file.endsWith(".tsx")) continue;
|
||||
for (const file of files) {
|
||||
if (!file.endsWith('.tsx')) continue;
|
||||
|
||||
const templateName = getTemplateName(file);
|
||||
const modulePath = `./${emailsDir}/${file}`;
|
||||
const templateName = getTemplateName(file);
|
||||
const modulePath = `./${emailsDir}/${file}`;
|
||||
|
||||
console.log(`Building ${templateName}...`);
|
||||
console.log(`Building ${templateName}...`);
|
||||
|
||||
try {
|
||||
const module = await import(modulePath);
|
||||
const Component = module.default || module[Object.keys(module)[0]];
|
||||
try {
|
||||
const module = await import(modulePath);
|
||||
const Component = module.default || module[Object.keys(module)[0]];
|
||||
|
||||
if (!Component) {
|
||||
console.error(`✗ No component found in ${file}`);
|
||||
continue;
|
||||
}
|
||||
if (!Component) {
|
||||
console.error(`✗ No component found in ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Component.TemplateProps) {
|
||||
console.error(`✗ No TemplateProps found in ${file}`);
|
||||
continue;
|
||||
}
|
||||
if (!Component.TemplateProps) {
|
||||
console.error(`✗ No TemplateProps found in ${file}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
await buildTemplateFile(Component, templateName, false); // HTML
|
||||
await buildTemplateFile(Component, templateName, true); // Text
|
||||
await buildTemplateFile(Component, templateName, false); // HTML
|
||||
await buildTemplateFile(Component, templateName, true); // Text
|
||||
|
||||
console.log(`✓ Built ${templateName}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ Error building ${templateName}:`, error);
|
||||
}
|
||||
}
|
||||
console.log(`✓ Built ${templateName}`);
|
||||
} catch (error) {
|
||||
console.error(`✗ Error building ${templateName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await discoverAndBuildTemplates();
|
||||
console.log("All templates built successfully!");
|
||||
await discoverAndBuildTemplates();
|
||||
console.log('All templates built successfully!');
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
|
||||
@@ -1,119 +1,100 @@
|
||||
import {
|
||||
Body,
|
||||
Column,
|
||||
Container,
|
||||
Head,
|
||||
Html,
|
||||
Img,
|
||||
Link,
|
||||
Preview,
|
||||
Row,
|
||||
Section,
|
||||
} from "react-email";
|
||||
import type { SharedProps } from "../props";
|
||||
import { colors, fonts, radius } from "./theme";
|
||||
import { Body, Column, Container, Head, Html, Img, Link, Preview, Row, Section } from 'react-email';
|
||||
import type { SharedProps } from '../props';
|
||||
import { colors, fonts, radius } from './theme';
|
||||
|
||||
interface BaseTemplateProps extends SharedProps {
|
||||
preview?: string;
|
||||
children: React.ReactNode;
|
||||
preview?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const BaseTemplate = ({
|
||||
logoURL,
|
||||
appName,
|
||||
appURL,
|
||||
preview,
|
||||
children,
|
||||
logoURL,
|
||||
appName,
|
||||
appURL,
|
||||
preview,
|
||||
children
|
||||
}: BaseTemplateProps) => (
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="supported-color-schemes" content="light" />
|
||||
<style dangerouslySetInnerHTML={{ __html: fontFaceCss(appURL) }} />
|
||||
</Head>
|
||||
{preview && <Preview>{preview}</Preview>}
|
||||
<Body style={bodyStyle}>
|
||||
<Container style={containerStyle}>
|
||||
<Section style={headerStyle} data-skip-in-text="true">
|
||||
<Row>
|
||||
<Column style={logoColumnStyle}>
|
||||
<Link href={appURL}>
|
||||
<Img
|
||||
src={logoURL}
|
||||
width="28"
|
||||
height="28"
|
||||
alt={appName}
|
||||
style={logoStyle}
|
||||
/>
|
||||
</Link>
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href={appURL} style={appNameStyle}>
|
||||
{appName}
|
||||
</Link>
|
||||
</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
<Html lang="en">
|
||||
<Head>
|
||||
<meta name="color-scheme" content="light" />
|
||||
<meta name="supported-color-schemes" content="light" />
|
||||
<style dangerouslySetInnerHTML={{ __html: fontFaceCss(appURL) }} />
|
||||
</Head>
|
||||
{preview && <Preview>{preview}</Preview>}
|
||||
<Body style={bodyStyle}>
|
||||
<Container style={containerStyle}>
|
||||
<Section style={headerStyle} data-skip-in-text="true">
|
||||
<Row>
|
||||
<Column style={logoColumnStyle}>
|
||||
<Link href={appURL}>
|
||||
<Img src={logoURL} width="28" height="28" alt={appName} style={logoStyle} />
|
||||
</Link>
|
||||
</Column>
|
||||
<Column>
|
||||
<Link href={appURL} style={appNameStyle}>
|
||||
{appName}
|
||||
</Link>
|
||||
</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<Row>
|
||||
<Column style={cardStyle}>{children}</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
<Section>
|
||||
<Row>
|
||||
<Column style={cardStyle}>{children}</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
</Container>
|
||||
</Body>
|
||||
</Html>
|
||||
);
|
||||
|
||||
// The heading font is self-hosted by the app, so it loads from the same origin as the logo and works without third-party requests
|
||||
// Clients without web font support fall back to the serif stack declared on the headings
|
||||
const fontFaceCss = (appURL: string) =>
|
||||
`@font-face{font-family:'Gloock';font-style:normal;font-weight:400;mso-font-alt:'Georgia';src:url(${appURL}/fonts/Gloock-Regular.woff) format('woff');}`;
|
||||
`@font-face{font-family:'Gloock';font-style:normal;font-weight:400;mso-font-alt:'Georgia';src:url(${appURL}/fonts/Gloock-Regular.woff) format('woff');}`;
|
||||
|
||||
const bodyStyle = {
|
||||
margin: 0,
|
||||
padding: "32px 16px",
|
||||
backgroundColor: colors.background,
|
||||
fontFamily: fonts.sans,
|
||||
color: colors.text,
|
||||
margin: 0,
|
||||
padding: '32px 16px',
|
||||
backgroundColor: colors.background,
|
||||
fontFamily: fonts.sans,
|
||||
color: colors.text
|
||||
};
|
||||
|
||||
const containerStyle = {
|
||||
width: "100%",
|
||||
maxWidth: "480px",
|
||||
margin: "0 auto",
|
||||
width: '100%',
|
||||
maxWidth: '480px',
|
||||
margin: '0 auto'
|
||||
};
|
||||
|
||||
const headerStyle = {
|
||||
marginBottom: "20px",
|
||||
marginBottom: '20px'
|
||||
};
|
||||
|
||||
const logoColumnStyle = {
|
||||
width: "36px",
|
||||
verticalAlign: "middle",
|
||||
width: '36px',
|
||||
verticalAlign: 'middle'
|
||||
};
|
||||
|
||||
const logoStyle = {
|
||||
display: "block",
|
||||
width: "28px",
|
||||
height: "28px",
|
||||
borderRadius: "6px",
|
||||
display: 'block',
|
||||
width: '28px',
|
||||
height: '28px',
|
||||
borderRadius: '6px'
|
||||
};
|
||||
|
||||
const appNameStyle = {
|
||||
fontFamily: fonts.serif,
|
||||
fontSize: "20px",
|
||||
lineHeight: "28px",
|
||||
color: colors.foreground,
|
||||
textDecoration: "none",
|
||||
fontFamily: fonts.serif,
|
||||
fontSize: '20px',
|
||||
lineHeight: '28px',
|
||||
color: colors.foreground,
|
||||
textDecoration: 'none'
|
||||
};
|
||||
|
||||
const cardStyle = {
|
||||
backgroundColor: colors.card,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.card,
|
||||
padding: "32px",
|
||||
textAlign: "left" as const,
|
||||
backgroundColor: colors.card,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.card,
|
||||
padding: '32px',
|
||||
textAlign: 'left' as const
|
||||
};
|
||||
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
import { Button as EmailButton, Section } from "react-email";
|
||||
import { colors, fonts, radius } from "./theme";
|
||||
import { Button as EmailButton, Section } from 'react-email';
|
||||
import { colors, fonts, radius } from './theme';
|
||||
|
||||
interface ButtonProps {
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
href: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Button = ({ href, children }: ButtonProps) => (
|
||||
<Section style={containerStyle}>
|
||||
<EmailButton href={href} style={buttonStyle}>
|
||||
{children}
|
||||
</EmailButton>
|
||||
</Section>
|
||||
<Section style={containerStyle}>
|
||||
<EmailButton href={href} style={buttonStyle}>
|
||||
{children}
|
||||
</EmailButton>
|
||||
</Section>
|
||||
);
|
||||
|
||||
const containerStyle = {
|
||||
margin: "24px 0 8px 0",
|
||||
textAlign: "center" as const,
|
||||
margin: '24px 0 8px 0',
|
||||
textAlign: 'center' as const
|
||||
};
|
||||
|
||||
const buttonStyle = {
|
||||
display: "inline-block",
|
||||
padding: "12px 28px",
|
||||
backgroundColor: colors.primary,
|
||||
color: colors.primaryForeground,
|
||||
borderRadius: radius.pill,
|
||||
fontFamily: fonts.sans,
|
||||
fontSize: "14px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 500,
|
||||
textDecoration: "none",
|
||||
display: 'inline-block',
|
||||
padding: '12px 28px',
|
||||
backgroundColor: colors.primary,
|
||||
color: colors.primaryForeground,
|
||||
borderRadius: radius.pill,
|
||||
fontFamily: fonts.sans,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 500,
|
||||
textDecoration: 'none'
|
||||
};
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
import { Heading } from "react-email";
|
||||
import { colors, fonts } from "./theme";
|
||||
import { Heading } from 'react-email';
|
||||
import { colors, fonts } from './theme';
|
||||
|
||||
interface CardHeaderProps {
|
||||
title: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export default function CardHeader({ title }: CardHeaderProps) {
|
||||
return (
|
||||
<Heading as="h1" style={titleStyle}>
|
||||
{title}
|
||||
</Heading>
|
||||
);
|
||||
return (
|
||||
<Heading as="h1" style={titleStyle}>
|
||||
{title}
|
||||
</Heading>
|
||||
);
|
||||
}
|
||||
|
||||
const titleStyle = {
|
||||
margin: "0 0 16px 0",
|
||||
fontFamily: fonts.serif,
|
||||
fontSize: "26px",
|
||||
lineHeight: "32px",
|
||||
fontWeight: 400,
|
||||
letterSpacing: "-0.01em",
|
||||
color: colors.foreground,
|
||||
margin: '0 0 16px 0',
|
||||
fontFamily: fonts.serif,
|
||||
fontSize: '26px',
|
||||
lineHeight: '32px',
|
||||
fontWeight: 400,
|
||||
letterSpacing: '-0.01em',
|
||||
color: colors.foreground
|
||||
};
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
import { Column, Row, Section, Text } from "react-email";
|
||||
import { colors, fonts, radius } from "./theme";
|
||||
import { Column, Row, Section, Text } from 'react-email';
|
||||
import { colors, fonts, radius } from './theme';
|
||||
|
||||
interface CodeBoxProps {
|
||||
code: string;
|
||||
code: string;
|
||||
}
|
||||
|
||||
export const CodeBox = ({ code }: CodeBoxProps) => (
|
||||
<Section style={boxStyle}>
|
||||
<Row>
|
||||
<Column style={cellStyle}>
|
||||
<Text style={codeStyle}>{code}</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
<Section style={boxStyle}>
|
||||
<Row>
|
||||
<Column style={cellStyle}>
|
||||
<Text style={codeStyle}>{code}</Text>
|
||||
</Column>
|
||||
</Row>
|
||||
</Section>
|
||||
);
|
||||
|
||||
const boxStyle = {
|
||||
margin: "8px 0 0 0",
|
||||
backgroundColor: colors.muted,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.box,
|
||||
margin: '8px 0 0 0',
|
||||
backgroundColor: colors.muted,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.box
|
||||
};
|
||||
|
||||
const cellStyle = {
|
||||
padding: "20px 16px",
|
||||
textAlign: "center" as const,
|
||||
padding: '20px 16px',
|
||||
textAlign: 'center' as const
|
||||
};
|
||||
|
||||
const codeStyle = {
|
||||
margin: 0,
|
||||
fontFamily: fonts.mono,
|
||||
fontSize: "32px",
|
||||
lineHeight: "40px",
|
||||
fontWeight: 600,
|
||||
letterSpacing: "8px",
|
||||
paddingLeft: "8px",
|
||||
color: colors.foreground,
|
||||
margin: 0,
|
||||
fontFamily: fonts.mono,
|
||||
fontSize: '32px',
|
||||
lineHeight: '40px',
|
||||
fontWeight: 600,
|
||||
letterSpacing: '8px',
|
||||
paddingLeft: '8px',
|
||||
color: colors.foreground
|
||||
};
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
import { Column, Row, Section } from "react-email";
|
||||
import { colors, radius } from "./theme";
|
||||
import { Column, Row, Section } from 'react-email';
|
||||
import { colors, radius } from './theme';
|
||||
|
||||
interface DetailsListProps {
|
||||
items: { label: string; value: string }[];
|
||||
items: { label: string; value: string }[];
|
||||
}
|
||||
|
||||
// A key-value list inside a muted box, used for sign-in details and similar metadata
|
||||
// Each value sits below its label so long values never fight the label for width on narrow screens
|
||||
// Every item is its own Row so the plain-text build separates entries with a blank line
|
||||
export const DetailsList = ({ items }: DetailsListProps) => (
|
||||
<Section style={boxStyle}>
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
return (
|
||||
<Row key={item.label}>
|
||||
<Column style={isLast ? cellStyle : { ...cellStyle, ...dividerStyle }}>
|
||||
<span style={labelStyle}>{item.label}</span>
|
||||
<br />
|
||||
<span style={valueStyle}>{item.value}</span>
|
||||
</Column>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
<Section style={boxStyle}>
|
||||
{items.map((item, index) => {
|
||||
const isLast = index === items.length - 1;
|
||||
return (
|
||||
<Row key={item.label}>
|
||||
<Column style={isLast ? cellStyle : { ...cellStyle, ...dividerStyle }}>
|
||||
<span style={labelStyle}>{item.label}</span>
|
||||
<br />
|
||||
<span style={valueStyle}>{item.value}</span>
|
||||
</Column>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
</Section>
|
||||
);
|
||||
|
||||
const boxStyle = {
|
||||
margin: "8px 0 0 0",
|
||||
backgroundColor: colors.muted,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.box,
|
||||
margin: '8px 0 0 0',
|
||||
backgroundColor: colors.muted,
|
||||
border: `1px solid ${colors.border}`,
|
||||
borderRadius: radius.box
|
||||
};
|
||||
|
||||
const cellStyle = {
|
||||
padding: "12px 16px",
|
||||
padding: '12px 16px'
|
||||
};
|
||||
|
||||
const labelStyle = {
|
||||
fontSize: "12px",
|
||||
lineHeight: "18px",
|
||||
color: colors.mutedForeground,
|
||||
fontSize: '12px',
|
||||
lineHeight: '18px',
|
||||
color: colors.mutedForeground
|
||||
};
|
||||
|
||||
const valueStyle = {
|
||||
fontSize: "14px",
|
||||
lineHeight: "20px",
|
||||
fontWeight: 500,
|
||||
color: colors.foreground,
|
||||
fontSize: '14px',
|
||||
lineHeight: '20px',
|
||||
fontWeight: 500,
|
||||
color: colors.foreground
|
||||
};
|
||||
|
||||
const dividerStyle = {
|
||||
borderBottom: `1px solid ${colors.border}`,
|
||||
borderBottom: `1px solid ${colors.border}`
|
||||
};
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { Text } from "react-email";
|
||||
import { colors } from "./theme";
|
||||
import { Text } from 'react-email';
|
||||
import { colors } from './theme';
|
||||
|
||||
interface TextProps {
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const Paragraph = ({ children, style }: TextProps) => (
|
||||
<Text style={{ ...paragraphStyle, ...style }}>{children}</Text>
|
||||
<Text style={{ ...paragraphStyle, ...style }}>{children}</Text>
|
||||
);
|
||||
|
||||
export const Muted = ({ children, style }: TextProps) => (
|
||||
<Text style={{ ...mutedStyle, ...style }}>{children}</Text>
|
||||
<Text style={{ ...mutedStyle, ...style }}>{children}</Text>
|
||||
);
|
||||
|
||||
const paragraphStyle = {
|
||||
margin: "0 0 16px 0",
|
||||
fontSize: "15px",
|
||||
lineHeight: "24px",
|
||||
color: colors.text,
|
||||
margin: '0 0 16px 0',
|
||||
fontSize: '15px',
|
||||
lineHeight: '24px',
|
||||
color: colors.text
|
||||
};
|
||||
|
||||
const mutedStyle = {
|
||||
margin: "16px 0 0 0",
|
||||
fontSize: "13px",
|
||||
lineHeight: "20px",
|
||||
color: colors.mutedForeground,
|
||||
margin: '16px 0 0 0',
|
||||
fontSize: '13px',
|
||||
lineHeight: '20px',
|
||||
color: colors.mutedForeground
|
||||
};
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Design tokens mirrored from the frontend's app.css (light theme) so emails match the app
|
||||
// Email clients have no CSS variable or oklch support, so the values are the resolved hex equivalents
|
||||
export const colors = {
|
||||
background: "#f5f5f5",
|
||||
card: "#ffffff",
|
||||
border: "#e5e5e5",
|
||||
muted: "#fafafa",
|
||||
foreground: "#0a0a0a",
|
||||
text: "#404040",
|
||||
mutedForeground: "#737373",
|
||||
primary: "#171717",
|
||||
primaryForeground: "#ffffff",
|
||||
background: '#f5f5f5',
|
||||
card: '#ffffff',
|
||||
border: '#e5e5e5',
|
||||
muted: '#fafafa',
|
||||
foreground: '#0a0a0a',
|
||||
text: '#404040',
|
||||
mutedForeground: '#737373',
|
||||
primary: '#171717',
|
||||
primaryForeground: '#ffffff'
|
||||
};
|
||||
|
||||
export const fonts = {
|
||||
sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||
serif: "Gloock, Georgia, 'Times New Roman', serif",
|
||||
mono: "'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace",
|
||||
sans: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif",
|
||||
serif: "Gloock, Georgia, 'Times New Roman', serif",
|
||||
mono: "'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace"
|
||||
};
|
||||
|
||||
export const radius = {
|
||||
card: "24px",
|
||||
box: "16px",
|
||||
pill: "9999px",
|
||||
card: '24px',
|
||||
box: '16px',
|
||||
pill: '9999px'
|
||||
};
|
||||
|
||||
@@ -1,61 +1,50 @@
|
||||
import { BaseTemplate } from "../components/base-template";
|
||||
import CardHeader from "../components/card-header";
|
||||
import { Muted, Paragraph } from "../components/text";
|
||||
import {
|
||||
type SharedProps,
|
||||
sharedPreviewProps,
|
||||
sharedTemplateProps,
|
||||
} from "../props";
|
||||
import { BaseTemplate } from '../components/base-template';
|
||||
import CardHeader from '../components/card-header';
|
||||
import { Muted, Paragraph } from '../components/text';
|
||||
import { type SharedProps, sharedPreviewProps, sharedTemplateProps } from '../props';
|
||||
|
||||
interface ApiKeyExpiringData {
|
||||
name: string;
|
||||
apiKeyName: string;
|
||||
expiresAt: string;
|
||||
name: string;
|
||||
apiKeyName: string;
|
||||
expiresAt: string;
|
||||
}
|
||||
|
||||
interface ApiKeyExpiringEmailProps extends SharedProps {
|
||||
data: ApiKeyExpiringData;
|
||||
data: ApiKeyExpiringData;
|
||||
}
|
||||
|
||||
export const ApiKeyExpiringEmail = ({
|
||||
data,
|
||||
...props
|
||||
}: ApiKeyExpiringEmailProps) => (
|
||||
<BaseTemplate
|
||||
{...props}
|
||||
preview={`Your API key ${data.apiKeyName} expires on ${data.expiresAt}`}
|
||||
>
|
||||
<CardHeader title="API key expiring soon" />
|
||||
<Paragraph>Hello {data.name},</Paragraph>
|
||||
<Paragraph>
|
||||
Your API key <strong>{data.apiKeyName}</strong> will expire on{" "}
|
||||
<strong>{data.expiresAt}</strong>. Anything that uses this key will stop
|
||||
working once it expires.
|
||||
</Paragraph>
|
||||
export const ApiKeyExpiringEmail = ({ data, ...props }: ApiKeyExpiringEmailProps) => (
|
||||
<BaseTemplate {...props} preview={`Your API key ${data.apiKeyName} expires on ${data.expiresAt}`}>
|
||||
<CardHeader title="API key expiring soon" />
|
||||
<Paragraph>Hello {data.name},</Paragraph>
|
||||
<Paragraph>
|
||||
Your API key <strong>{data.apiKeyName}</strong> will expire on{' '}
|
||||
<strong>{data.expiresAt}</strong>. Anything that uses this key will stop working once it
|
||||
expires.
|
||||
</Paragraph>
|
||||
|
||||
<Muted>
|
||||
To keep access, create a new API key in your {props.appName} account
|
||||
settings before then.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
<Muted>
|
||||
To keep access, create a new API key in your {props.appName} account settings before then.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
);
|
||||
|
||||
export default ApiKeyExpiringEmail;
|
||||
|
||||
ApiKeyExpiringEmail.TemplateProps = {
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
name: "{{.Data.Name}}",
|
||||
apiKeyName: "{{.Data.ApiKeyName}}",
|
||||
expiresAt: '{{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}',
|
||||
},
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
name: '{{.Data.Name}}',
|
||||
apiKeyName: '{{.Data.ApiKeyName}}',
|
||||
expiresAt: '{{.Data.ExpiresAt.Format "2006-01-02 15:04:05 MST"}}'
|
||||
}
|
||||
};
|
||||
|
||||
ApiKeyExpiringEmail.PreviewProps = {
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
name: "Elias",
|
||||
apiKeyName: "CI deploy key",
|
||||
expiresAt: "2026-01-30 12:00:00 UTC",
|
||||
},
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
name: 'Elias',
|
||||
apiKeyName: 'CI deploy key',
|
||||
expiresAt: '2026-01-30 12:00:00 UTC'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,72 +1,61 @@
|
||||
import { Link } from "react-email";
|
||||
import { BaseTemplate } from "../components/base-template";
|
||||
import { Button } from "../components/button";
|
||||
import CardHeader from "../components/card-header";
|
||||
import { Muted, Paragraph } from "../components/text";
|
||||
import { colors } from "../components/theme";
|
||||
import {
|
||||
type SharedProps,
|
||||
sharedPreviewProps,
|
||||
sharedTemplateProps,
|
||||
} from "../props";
|
||||
import { Link } from 'react-email';
|
||||
import { BaseTemplate } from '../components/base-template';
|
||||
import { Button } from '../components/button';
|
||||
import CardHeader from '../components/card-header';
|
||||
import { Muted, Paragraph } from '../components/text';
|
||||
import { colors } from '../components/theme';
|
||||
import { type SharedProps, sharedPreviewProps, sharedTemplateProps } from '../props';
|
||||
|
||||
interface EmailVerificationData {
|
||||
userFullName: string;
|
||||
verificationLink: string;
|
||||
userFullName: string;
|
||||
verificationLink: string;
|
||||
}
|
||||
|
||||
interface EmailVerificationProps extends SharedProps {
|
||||
data: EmailVerificationData;
|
||||
data: EmailVerificationData;
|
||||
}
|
||||
|
||||
export const EmailVerification = ({
|
||||
data,
|
||||
...props
|
||||
}: EmailVerificationProps) => (
|
||||
<BaseTemplate
|
||||
{...props}
|
||||
preview={`Confirm the email address for your ${props.appName} account`}
|
||||
>
|
||||
<CardHeader title="Verify your email address" />
|
||||
<Paragraph>Hello {data.userFullName},</Paragraph>
|
||||
<Paragraph>
|
||||
Click the button below to confirm the email address for your{" "}
|
||||
{props.appName} account. This link expires in 24 hours.
|
||||
</Paragraph>
|
||||
export const EmailVerification = ({ data, ...props }: EmailVerificationProps) => (
|
||||
<BaseTemplate {...props} preview={`Confirm the email address for your ${props.appName} account`}>
|
||||
<CardHeader title="Verify your email address" />
|
||||
<Paragraph>Hello {data.userFullName},</Paragraph>
|
||||
<Paragraph>
|
||||
Click the button below to confirm the email address for your {props.appName} account. This
|
||||
link expires in 24 hours.
|
||||
</Paragraph>
|
||||
|
||||
<Button href={data.verificationLink}>Verify email address</Button>
|
||||
<Button href={data.verificationLink}>Verify email address</Button>
|
||||
|
||||
<Muted style={{ marginTop: "32px" }}>
|
||||
Or if you don't like clicking buttons, open this link:
|
||||
<br />
|
||||
<Link href={data.verificationLink} style={linkStyle}>
|
||||
{data.verificationLink}
|
||||
</Link>
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
<Muted style={{ marginTop: '32px' }}>
|
||||
Or if you don't like clicking buttons, open this link:
|
||||
<br />
|
||||
<Link href={data.verificationLink} style={linkStyle}>
|
||||
{data.verificationLink}
|
||||
</Link>
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
);
|
||||
|
||||
export default EmailVerification;
|
||||
|
||||
const linkStyle = {
|
||||
color: colors.mutedForeground,
|
||||
textDecoration: "underline",
|
||||
wordBreak: "break-all" as const,
|
||||
color: colors.mutedForeground,
|
||||
textDecoration: 'underline',
|
||||
wordBreak: 'break-all' as const
|
||||
};
|
||||
|
||||
EmailVerification.TemplateProps = {
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
userFullName: "{{.Data.UserFullName}}",
|
||||
verificationLink: "{{.Data.VerificationLink}}",
|
||||
},
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
userFullName: '{{.Data.UserFullName}}',
|
||||
verificationLink: '{{.Data.VerificationLink}}'
|
||||
}
|
||||
};
|
||||
|
||||
EmailVerification.PreviewProps = {
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
userFullName: "Tim Cook",
|
||||
verificationLink:
|
||||
"https://id.example.com/user/verify-email?code=abcdefg12345",
|
||||
},
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
userFullName: 'Tim Cook',
|
||||
verificationLink: 'https://id.example.com/user/verify-email?code=abcdefg12345'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,70 +1,63 @@
|
||||
import { BaseTemplate } from "../components/base-template";
|
||||
import CardHeader from "../components/card-header";
|
||||
import { DetailsList } from "../components/details-list";
|
||||
import { Muted, Paragraph } from "../components/text";
|
||||
import {
|
||||
type SharedProps,
|
||||
sharedPreviewProps,
|
||||
sharedTemplateProps,
|
||||
} from "../props";
|
||||
import { BaseTemplate } from '../components/base-template';
|
||||
import CardHeader from '../components/card-header';
|
||||
import { DetailsList } from '../components/details-list';
|
||||
import { Muted, Paragraph } from '../components/text';
|
||||
import { type SharedProps, sharedPreviewProps, sharedTemplateProps } from '../props';
|
||||
|
||||
interface SignInData {
|
||||
location: string;
|
||||
ipAddress: string;
|
||||
device: string;
|
||||
dateTime: string;
|
||||
location: string;
|
||||
ipAddress: string;
|
||||
device: string;
|
||||
dateTime: string;
|
||||
}
|
||||
|
||||
interface NewSignInEmailProps extends SharedProps {
|
||||
data: SignInData;
|
||||
data: SignInData;
|
||||
}
|
||||
|
||||
export const NewSignInEmail = ({ data, ...props }: NewSignInEmailProps) => (
|
||||
<BaseTemplate
|
||||
{...props}
|
||||
preview={`A new sign-in to your ${props.appName} account was detected`}
|
||||
>
|
||||
<CardHeader title="New sign-in detected" />
|
||||
<Paragraph>
|
||||
Your {props.appName} account was recently accessed from a new IP address
|
||||
or browser. If this was you, no further action is needed.
|
||||
</Paragraph>
|
||||
<BaseTemplate {...props} preview={`A new sign-in to your ${props.appName} account was detected`}>
|
||||
<CardHeader title="New sign-in detected" />
|
||||
<Paragraph>
|
||||
Your {props.appName} account was recently accessed from a new IP address or browser. If this
|
||||
was you, no further action is needed.
|
||||
</Paragraph>
|
||||
|
||||
<DetailsList
|
||||
items={[
|
||||
{ label: "Approximate location", value: data.location },
|
||||
{ label: "IP address", value: data.ipAddress },
|
||||
{ label: "Device", value: data.device },
|
||||
{ label: "Time", value: data.dateTime },
|
||||
]}
|
||||
/>
|
||||
<DetailsList
|
||||
items={[
|
||||
{ label: 'Approximate location', value: data.location },
|
||||
{ label: 'IP address', value: data.ipAddress },
|
||||
{ label: 'Device', value: data.device },
|
||||
{ label: 'Time', value: data.dateTime }
|
||||
]}
|
||||
/>
|
||||
|
||||
<Muted>
|
||||
If you don't recognize this activity, review the passkeys in your{" "}
|
||||
{props.appName} account settings and remove any you don't recognize.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
<Muted>
|
||||
If you don't recognize this activity, review the passkeys in your {props.appName} account
|
||||
settings and remove any you don't recognize.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
);
|
||||
|
||||
export default NewSignInEmail;
|
||||
|
||||
NewSignInEmail.TemplateProps = {
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
location:
|
||||
"{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}",
|
||||
ipAddress: "{{.Data.IPAddress}}",
|
||||
device: "{{.Data.Device}}",
|
||||
dateTime: '{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}',
|
||||
},
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
location:
|
||||
'{{if and .Data.City .Data.Country}}{{.Data.City}}, {{.Data.Country}}{{else if .Data.Country}}{{.Data.Country}}{{else}}Unknown{{end}}',
|
||||
ipAddress: '{{.Data.IPAddress}}',
|
||||
device: '{{.Data.Device}}',
|
||||
dateTime: '{{.Data.DateTime.Format "January 2, 2006 at 3:04 PM MST"}}'
|
||||
}
|
||||
};
|
||||
|
||||
NewSignInEmail.PreviewProps = {
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
location: "San Francisco, USA",
|
||||
ipAddress: "203.0.113.42",
|
||||
device: "Chrome on macOS",
|
||||
dateTime: "January 2, 2026 at 3:04 PM UTC",
|
||||
},
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
location: 'San Francisco, USA',
|
||||
ipAddress: '203.0.113.42',
|
||||
device: 'Chrome on macOS',
|
||||
dateTime: 'January 2, 2026 at 3:04 PM UTC'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,79 +1,68 @@
|
||||
import { Link } from "react-email";
|
||||
import { BaseTemplate } from "../components/base-template";
|
||||
import { Button } from "../components/button";
|
||||
import CardHeader from "../components/card-header";
|
||||
import { CodeBox } from "../components/code-box";
|
||||
import { Muted, Paragraph } from "../components/text";
|
||||
import { colors } from "../components/theme";
|
||||
import {
|
||||
type SharedProps,
|
||||
sharedPreviewProps,
|
||||
sharedTemplateProps,
|
||||
} from "../props";
|
||||
import { Link } from 'react-email';
|
||||
import { BaseTemplate } from '../components/base-template';
|
||||
import { Button } from '../components/button';
|
||||
import CardHeader from '../components/card-header';
|
||||
import { CodeBox } from '../components/code-box';
|
||||
import { Muted, Paragraph } from '../components/text';
|
||||
import { colors } from '../components/theme';
|
||||
import { type SharedProps, sharedPreviewProps, sharedTemplateProps } from '../props';
|
||||
|
||||
interface OneTimeAccessData {
|
||||
code: string;
|
||||
loginLink: string;
|
||||
buttonCodeLink: string;
|
||||
expirationString: string;
|
||||
code: string;
|
||||
loginLink: string;
|
||||
buttonCodeLink: string;
|
||||
expirationString: string;
|
||||
}
|
||||
|
||||
interface OneTimeAccessEmailProps extends SharedProps {
|
||||
data: OneTimeAccessData;
|
||||
data: OneTimeAccessData;
|
||||
}
|
||||
|
||||
export const OneTimeAccessEmail = ({
|
||||
data,
|
||||
...props
|
||||
}: OneTimeAccessEmailProps) => (
|
||||
<BaseTemplate
|
||||
{...props}
|
||||
preview={`Your ${props.appName} login code is ${data.code}`}
|
||||
>
|
||||
<CardHeader title="Your login code" />
|
||||
<Paragraph>
|
||||
Use the code below to sign in to {props.appName}. It expires in{" "}
|
||||
{data.expirationString}.
|
||||
</Paragraph>
|
||||
export const OneTimeAccessEmail = ({ data, ...props }: OneTimeAccessEmailProps) => (
|
||||
<BaseTemplate {...props} preview={`Your ${props.appName} login code is ${data.code}`}>
|
||||
<CardHeader title="Your login code" />
|
||||
<Paragraph>
|
||||
Use the code below to sign in to {props.appName}. It expires in {data.expirationString}.
|
||||
</Paragraph>
|
||||
|
||||
<CodeBox code={data.code} />
|
||||
<CodeBox code={data.code} />
|
||||
|
||||
<Button href={data.buttonCodeLink}>Sign in</Button>
|
||||
<Button href={data.buttonCodeLink}>Sign in</Button>
|
||||
|
||||
<Muted style={{ marginTop: "32px" }}>
|
||||
Or open{" "}
|
||||
<Link href={data.loginLink} style={linkStyle}>
|
||||
{data.loginLink}
|
||||
</Link>{" "}
|
||||
and enter the code manually.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
<Muted style={{ marginTop: '32px' }}>
|
||||
Or open{' '}
|
||||
<Link href={data.loginLink} style={linkStyle}>
|
||||
{data.loginLink}
|
||||
</Link>{' '}
|
||||
and enter the code manually.
|
||||
</Muted>
|
||||
</BaseTemplate>
|
||||
);
|
||||
|
||||
export default OneTimeAccessEmail;
|
||||
|
||||
const linkStyle = {
|
||||
color: colors.mutedForeground,
|
||||
textDecoration: "underline",
|
||||
wordBreak: "break-all" as const,
|
||||
color: colors.mutedForeground,
|
||||
textDecoration: 'underline',
|
||||
wordBreak: 'break-all' as const
|
||||
};
|
||||
|
||||
OneTimeAccessEmail.TemplateProps = {
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
code: "{{.Data.Code}}",
|
||||
loginLink: "{{.Data.LoginLink}}",
|
||||
buttonCodeLink: "{{.Data.LoginLinkWithCode}}",
|
||||
expirationString: "{{.Data.ExpirationString}}",
|
||||
},
|
||||
...sharedTemplateProps,
|
||||
data: {
|
||||
code: '{{.Data.Code}}',
|
||||
loginLink: '{{.Data.LoginLink}}',
|
||||
buttonCodeLink: '{{.Data.LoginLinkWithCode}}',
|
||||
expirationString: '{{.Data.ExpirationString}}'
|
||||
}
|
||||
};
|
||||
|
||||
OneTimeAccessEmail.PreviewProps = {
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
code: "123456",
|
||||
loginLink: "https://id.example.com/lc",
|
||||
buttonCodeLink: "https://id.example.com/lc/123456",
|
||||
expirationString: "15 minutes",
|
||||
},
|
||||
...sharedPreviewProps,
|
||||
data: {
|
||||
code: '123456',
|
||||
loginLink: 'https://id.example.com/lc',
|
||||
buttonCodeLink: 'https://id.example.com/lc/123456',
|
||||
expirationString: '15 minutes'
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
import { BaseTemplate } from "../components/base-template";
|
||||
import CardHeader from "../components/card-header";
|
||||
import { Paragraph } from "../components/text";
|
||||
import {
|
||||
type SharedProps,
|
||||
sharedPreviewProps,
|
||||
sharedTemplateProps,
|
||||
} from "../props";
|
||||
import { BaseTemplate } from '../components/base-template';
|
||||
import CardHeader from '../components/card-header';
|
||||
import { Paragraph } from '../components/text';
|
||||
import { type SharedProps, sharedPreviewProps, sharedTemplateProps } from '../props';
|
||||
|
||||
export const TestEmail = (props: SharedProps) => (
|
||||
<BaseTemplate {...props} preview="Your email setup is working correctly">
|
||||
<CardHeader title="Test email" />
|
||||
<Paragraph>Your email setup is working correctly!</Paragraph>
|
||||
</BaseTemplate>
|
||||
<BaseTemplate {...props} preview="Your email setup is working correctly">
|
||||
<CardHeader title="Test email" />
|
||||
<Paragraph>Your email setup is working correctly!</Paragraph>
|
||||
</BaseTemplate>
|
||||
);
|
||||
|
||||
export default TestEmail;
|
||||
|
||||
TestEmail.TemplateProps = {
|
||||
...sharedTemplateProps,
|
||||
...sharedTemplateProps
|
||||
};
|
||||
|
||||
TestEmail.PreviewProps = {
|
||||
...sharedPreviewProps,
|
||||
...sharedPreviewProps
|
||||
};
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"name": "pocketid-email-templates",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "tsx build.ts",
|
||||
"build:watch": "tsx watch build.ts",
|
||||
"dev": "email dev --port 3030",
|
||||
"export": "email export"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0",
|
||||
"react-email": "6.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-email/preview-server": "5.2.10",
|
||||
"@react-email/ui": "6.11.0",
|
||||
"@types/node": "^25.9.7",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"tsx": "^4.23.13"
|
||||
}
|
||||
"name": "pocketid-email-templates",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"build": "tsx build.ts",
|
||||
"build:watch": "tsx watch build.ts",
|
||||
"dev": "email dev --port 3030",
|
||||
"export": "email export"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^19.3.0",
|
||||
"react-dom": "^19.3.0",
|
||||
"react-email": "6.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-email/preview-server": "5.2.10",
|
||||
"@react-email/ui": "6.11.0",
|
||||
"@types/node": "^25.9.7",
|
||||
"@types/react": "^19.3.0",
|
||||
"@types/react-dom": "^19.3.0",
|
||||
"tsx": "^4.23.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
export interface SharedProps {
|
||||
logoURL: string;
|
||||
appName: string;
|
||||
appURL: string;
|
||||
logoURL: string;
|
||||
appName: string;
|
||||
appURL: string;
|
||||
}
|
||||
|
||||
export const sharedPreviewProps: SharedProps = {
|
||||
logoURL: "https://pocket-id.org/img/logo.png",
|
||||
appName: "Pocket ID",
|
||||
appURL: "https://id.example.com",
|
||||
logoURL: 'https://pocket-id.org/img/logo.png',
|
||||
appName: 'Pocket ID',
|
||||
appURL: 'https://id.example.com'
|
||||
};
|
||||
|
||||
export const sharedTemplateProps: SharedProps = {
|
||||
logoURL: "{{.LogoURL}}",
|
||||
appName: "{{.AppName}}",
|
||||
appURL: "{{.AppURL}}",
|
||||
logoURL: '{{.LogoURL}}',
|
||||
appName: '{{.AppName}}',
|
||||
appURL: '{{.AppURL}}'
|
||||
};
|
||||
|
||||
@@ -1,25 +1,19 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Package Managers
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
yarn.lock
|
||||
|
||||
# Compiled files
|
||||
.svelte-kit/
|
||||
build/
|
||||
src/lib/paraglide/messages
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100,
|
||||
"plugins": ["prettier-plugin-tailwindcss", "prettier-plugin-svelte"],
|
||||
"overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import js from '@eslint/js';
|
||||
import ts from 'typescript-eslint';
|
||||
import svelte from 'eslint-plugin-svelte';
|
||||
import prettier from 'eslint-config-prettier';
|
||||
import globals from 'globals';
|
||||
|
||||
/** @type {import('eslint').Linter.FlatConfig[]} */
|
||||
export default [
|
||||
js.configs.recommended,
|
||||
...ts.configs.recommended,
|
||||
...svelte.configs['flat/recommended'],
|
||||
prettier,
|
||||
...svelte.configs['flat/prettier'],
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: ts.parser
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
ignores: ['build/', '.svelte-kit/', 'dist/', 'src/lib/paraglide/', 'src/paraglide/']
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
// Pocket ID does not support deployments under a SvelteKit base path
|
||||
'svelte/no-navigation-without-resolve': 'off'
|
||||
}
|
||||
}
|
||||
];
|
||||
+11
-18
@@ -3,15 +3,19 @@
|
||||
"version": "2.17.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"imports": {
|
||||
"#lib/*": "./src/lib/*"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "vite dev --port 3000",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview --port 3000",
|
||||
"dev": "vp dev --port 3000",
|
||||
"build": "vp build",
|
||||
"preview": "vp preview --port 3000",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||
"lint": "prettier --check . && eslint .",
|
||||
"format": "prettier --write ."
|
||||
"lint": "vp lint",
|
||||
"format": "vp fmt",
|
||||
"format:check": "vp fmt --check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@inlang/paraglide-js-svelte": "^1.0.3",
|
||||
@@ -31,7 +35,6 @@
|
||||
"zod": "^4.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^10.0.1",
|
||||
"@inlang/paraglide-js": "^2.18.0",
|
||||
"@inlang/plugin-m-function-matcher": "^2.2.6",
|
||||
"@inlang/plugin-message-format": "^4.4.0",
|
||||
@@ -43,14 +46,7 @@
|
||||
"@types/node": "^25.9.0",
|
||||
"@types/qrcode": "^1.5.6",
|
||||
"bits-ui": "^2.18.1",
|
||||
"eslint": "^10.4.0",
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-svelte": "^3.17.1",
|
||||
"globals": "^17.6.0",
|
||||
"mode-watcher": "^1.1.0",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier-plugin-svelte": "^3.5.2",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"rollup": "^4.63.3",
|
||||
"rollup-plugin-sbom": "4.0.0",
|
||||
"shadcn-svelte": "^1.3.0",
|
||||
@@ -61,10 +57,7 @@
|
||||
"tailwindcss": "^4.3.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.59.4",
|
||||
"vite": "^8.0.16"
|
||||
},
|
||||
"imports": {
|
||||
"#lib/*": "./src/lib/*"
|
||||
"vite": "catalog:",
|
||||
"vite-plus": "catalog:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<button
|
||||
{...props}
|
||||
class={cn(
|
||||
'w-full flex justify-start items-center font-normal h-9 bg-input/50 rounded-full p-3',
|
||||
'bg-input/50 flex h-9 w-full items-center justify-start rounded-full p-3 font-normal',
|
||||
!value && 'text-muted-foreground'
|
||||
)}
|
||||
aria-label={m.select_a_date()}
|
||||
|
||||
@@ -81,9 +81,9 @@
|
||||
variant="outline"
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
class="h-auto min-h-10 w-full bg-input/30!"
|
||||
class="bg-input/30! h-auto min-h-10 w-full"
|
||||
>
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex w-full items-center justify-between">
|
||||
<div class="flex flex-wrap items-center gap-1">
|
||||
{#if selectedItems.length > 0}
|
||||
{#each selectedLabels as label, i (i)}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
{...props}
|
||||
class={restProps.class}
|
||||
>
|
||||
<span class="flex justify-between w-full">
|
||||
<span class="flex w-full justify-between">
|
||||
{items.find((item) => item.value === value)?.label || selectText}
|
||||
<LucideChevronDown class="size-4 shrink-0 opacity-50" />
|
||||
</span>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
|
||||
<div
|
||||
class=" w-full {isAuthPage
|
||||
? 'absolute top-0 z-10 mt-3 lg:mt-8 pr-2 lg:pr-3'
|
||||
: 'pt-3 bg-muted/40 dark:bg-background '}"
|
||||
? 'absolute top-0 z-10 mt-3 pr-2 lg:mt-8 lg:pr-3'
|
||||
: 'bg-muted/40 dark:bg-background pt-3 '}"
|
||||
>
|
||||
<div
|
||||
class="{!isAuthPage
|
||||
@@ -35,8 +35,8 @@
|
||||
{#if !isAuthPage}
|
||||
<a href="/" class="flex items-center transition-opacity hover:opacity-80">
|
||||
<Logo class="size-8" />
|
||||
<Separator orientation="vertical" class="h-5! bg-neutral-600 ml-2 mr-3" />
|
||||
<h1 class="text-2xl font-gloock" data-testid="application-name">
|
||||
<Separator orientation="vertical" class="mr-3 ml-2 h-5! bg-neutral-600" />
|
||||
<h1 class="font-gloock text-2xl" data-testid="application-name">
|
||||
{m.settings()}
|
||||
</h1>
|
||||
</a>
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
<DropdownMenu.Trigger class={buttonVariants({ variant: 'ghost', size: 'icon' })}>
|
||||
<SunIcon
|
||||
class="h-[1.2rem] w-[1.2rem] !transition-all {isDark
|
||||
? '-rotate-90 scale-0'
|
||||
: 'rotate-0 scale-100'}"
|
||||
? 'scale-0 -rotate-90'
|
||||
: 'scale-100 rotate-0'}"
|
||||
/>
|
||||
<MoonIcon
|
||||
class="absolute h-[1.2rem] w-[1.2rem] !transition-all {isDark
|
||||
? 'rotate-0 scale-100'
|
||||
: 'rotate-90 scale-0'}"
|
||||
? 'scale-100 rotate-0'
|
||||
: 'scale-0 rotate-90'}"
|
||||
/>
|
||||
<span class="sr-only">Toggle theme</span>
|
||||
</DropdownMenu.Trigger>
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
: 'justify-center'}"
|
||||
>
|
||||
<div
|
||||
class="relative z-10 flex h-full w-full max-w-[650px] 2xl:max-w-[800px] p-16 {cn(
|
||||
class="relative z-10 flex h-full w-full max-w-[650px] p-16 2xl:max-w-[800px] {cn(
|
||||
showAlternativeSignInMethodButton && 'pb-0'
|
||||
)}"
|
||||
>
|
||||
@@ -88,7 +88,7 @@
|
||||
src={cachedBackgroundImage.getUrl()}
|
||||
class="{cn(
|
||||
animate && 'animate-bg-zoom'
|
||||
)} h-screen object-cover w-[calc(100vw-650px)] 2xl:w-[calc(100vw-800px)]"
|
||||
)} h-screen w-[calc(100vw-650px)] object-cover 2xl:w-[calc(100vw-800px)]"
|
||||
alt={m.login_background()}
|
||||
/>
|
||||
</div>
|
||||
@@ -102,7 +102,7 @@
|
||||
<Card.Root
|
||||
class={{
|
||||
'mx-3 w-full max-w-md': true,
|
||||
'bg-transparent border-0': !backgroundImageExists
|
||||
'border-0 bg-transparent': !backgroundImageExists
|
||||
}}
|
||||
>
|
||||
<Card.CardContent
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
const showProviderIcon = $derived(!!providerIconUrl && !iconFailed);
|
||||
</script>
|
||||
|
||||
<Item.Root variant="transparent" class="hover:bg-muted transition-colors py-3 px-0 sm:px-4">
|
||||
<Item.Root variant="transparent" class="hover:bg-muted px-0 py-3 transition-colors sm:px-4">
|
||||
<Item.Media class="bg-muted text-muted-foreground size-11 rounded-xl">
|
||||
{#if showProviderIcon}
|
||||
<img
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
const SvelteComponent = $derived(icon);
|
||||
</script>
|
||||
|
||||
<Item.Root class="py-1.5 px-0">
|
||||
<Item.Media class="bg-muted !self-center rounded-lg p-2 !translate-y-0 h-full">
|
||||
<Item.Root class="px-0 py-1.5">
|
||||
<Item.Media class="bg-muted h-full !translate-y-0 !self-center rounded-lg p-2">
|
||||
<SvelteComponent class="size-5" />
|
||||
</Item.Media>
|
||||
<Item.Content class="text-start">
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
class="order-dashed"
|
||||
data-testid={`facet-${title.toLowerCase()}-trigger`}
|
||||
>
|
||||
<span class="flex gap-2 items-center">
|
||||
<span class="flex items-center gap-2">
|
||||
<ListFilterIcon />
|
||||
{title}
|
||||
</span>
|
||||
|
||||
@@ -243,7 +243,7 @@
|
||||
class="h-12 w-full justify-start px-4 font-medium hover:bg-transparent"
|
||||
onclick={() => onSort(column.column)}
|
||||
>
|
||||
<span class="flex items-center w-full">
|
||||
<span class="flex w-full items-center">
|
||||
{column.label}
|
||||
<ChevronDown
|
||||
class={cn(
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
data-slot="alert-dialog-content"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 gap-6 rounded-4xl p-6 shadow-xl ring-1 duration-100 data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 outline-none',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 bg-popover text-popover-foreground ring-foreground/5 dark:ring-foreground/10 group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl p-6 shadow-xl ring-1 duration-100 outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-md',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="alert-dialog-overlay"
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm fixed inset-0 z-50',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 z-50 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="alert-description"
|
||||
class={cn(
|
||||
'text-muted-foreground text-sm text-balance md:text-pretty [&_p:not(:last-child)]:mb-4 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',
|
||||
'text-muted-foreground [&_a]:hover:text-foreground text-sm text-balance md:text-pretty [&_a]:underline [&_a]:underline-offset-3 [&_p:not(:last-child)]:mb-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="alert-title"
|
||||
class={cn(
|
||||
'font-medium group-has-[>svg]/alert:col-start-2 [&_a]:hover:text-foreground [&_a]:underline [&_a]:underline-offset-3',
|
||||
'[&_a]:hover:text-foreground font-medium group-has-[>svg]/alert:col-start-2 [&_a]:underline [&_a]:underline-offset-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="avatar-fallback"
|
||||
class={cn(
|
||||
'bg-muted text-muted-foreground rounded-full flex size-full items-center justify-center text-sm group-data-[size=sm]/avatar:text-xs',
|
||||
'bg-muted text-muted-foreground flex size-full items-center justify-center rounded-full text-sm group-data-[size=sm]/avatar:text-xs',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="avatar-group-count"
|
||||
class={cn(
|
||||
'bg-muted text-muted-foreground size-8 rounded-full text-sm group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3 ring-background relative flex shrink-0 items-center justify-center ring-2',
|
||||
'bg-muted text-muted-foreground ring-background relative flex size-8 shrink-0 items-center justify-center rounded-full text-sm ring-2 group-has-data-[size=lg]/avatar-group:size-10 group-has-data-[size=sm]/avatar-group:size-6 [&>svg]:size-4 group-has-data-[size=lg]/avatar-group:[&>svg]:size-5 group-has-data-[size=sm]/avatar-group:[&>svg]:size-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
<AvatarPrimitive.Image
|
||||
bind:ref
|
||||
data-slot="avatar-image"
|
||||
class={cn('rounded-full aspect-square size-full object-cover', className)}
|
||||
class={cn('aspect-square size-full rounded-full object-cover', className)}
|
||||
{...restProps}
|
||||
/>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
data-slot="avatar"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'size-8 rounded-full after:rounded-full data-[size=lg]:size-10 data-[size=sm]:size-6 after:border-border group/avatar relative flex shrink-0 select-none after:absolute after:inset-0 after:border after:mix-blend-darken dark:after:mix-blend-lighten',
|
||||
'after:border-border group/avatar relative flex size-8 shrink-0 rounded-full select-none after:absolute after:inset-0 after:rounded-full after:border after:mix-blend-darken data-[size=lg]:size-10 data-[size=sm]:size-6 dark:after:mix-blend-lighten',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -51,7 +51,7 @@ get along, so we shut typescript up by casting `value` to `never`.
|
||||
{weekdayFormat}
|
||||
{disableDaysOutsideMonth}
|
||||
class={cn(
|
||||
'p-3 [--cell-radius:var(--radius-4xl)] [--cell-size:--spacing(8)] bg-background group/calendar in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',
|
||||
'bg-background group/calendar p-3 [--cell-radius:var(--radius-4xl)] [--cell-size:--spacing(8)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',
|
||||
className
|
||||
)}
|
||||
{locale}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<p
|
||||
bind:this={ref}
|
||||
data-slot="card-description"
|
||||
class={cn('text-muted-foreground text-sm mt-1', className)}
|
||||
class={cn('text-muted-foreground mt-1 text-sm', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="card-footer"
|
||||
class={cn(
|
||||
'rounded-b-4xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4 flex items-center',
|
||||
'flex items-center rounded-b-4xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="card-header"
|
||||
class={cn(
|
||||
'gap-1.5 rounded-t-4xl px-6 group-data-[size=sm]/card:px-4 [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',
|
||||
'group/card-header @container/card-header grid auto-rows-min items-start gap-1.5 rounded-t-4xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
data-slot="card"
|
||||
data-size={size}
|
||||
class={cn(
|
||||
'bg-card text-card-foreground border-foreground/5 dark:border-foreground/10 gap-6 overflow-hidden rounded-4xl py-6 text-sm shadow-sm border-1 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-4xl *:[img:last-child]:rounded-b-4xl group/card flex flex-col',
|
||||
'bg-card text-card-foreground border-foreground/5 dark:border-foreground/10 group/card flex flex-col gap-6 overflow-hidden rounded-4xl border-1 py-6 text-sm shadow-sm has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-4xl *:[img:last-child]:rounded-b-4xl',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
bind:ref
|
||||
data-slot="checkbox"
|
||||
class={cn(
|
||||
'bg-input/90 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 flex size-4 items-center justify-center rounded-[5px] border border-transparent transition-shadow group-has-disabled/field:opacity-50 focus-visible:ring-3 aria-invalid:ring-3 peer relative shrink-0 outline-none after:absolute after:-inset-x-3 after:-inset-y-2 disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'bg-input/90 data-checked:bg-primary data-checked:text-primary-foreground dark:data-checked:bg-primary data-checked:border-primary aria-invalid:aria-checked:border-primary aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 peer relative flex size-4 shrink-0 items-center justify-center rounded-[5px] border border-transparent transition-shadow outline-none group-has-disabled/field:opacity-50 after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:ring-3 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3',
|
||||
className
|
||||
)}
|
||||
bind:checked
|
||||
@@ -27,7 +27,7 @@
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<div
|
||||
data-slot="checkbox-indicator"
|
||||
class="[&>svg]:size-3.5 grid place-content-center text-current transition-none"
|
||||
class="grid place-content-center text-current transition-none [&>svg]:size-3.5"
|
||||
>
|
||||
{#if checked}
|
||||
<CheckIcon />
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
<Dialog.Description>{description}</Dialog.Description>
|
||||
</Dialog.Header>
|
||||
<Dialog.Content
|
||||
class={cn('rounded-4xl! p-0 top-1/3 translate-y-0 overflow-hidden p-0', className)}
|
||||
class={cn('top-1/3 translate-y-0 overflow-hidden rounded-4xl! p-0 p-0', className)}
|
||||
{showCloseButton}
|
||||
{portalProps}
|
||||
>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
bind:ref
|
||||
data-slot="command-group"
|
||||
class={cn(
|
||||
'text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden py-2 px-1 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium',
|
||||
'text-foreground **:[[cmdk-group-heading]]:text-muted-foreground overflow-hidden px-1 py-2 **:[[cmdk-group-heading]]:px-3 **:[[cmdk-group-heading]]:py-2 **:[[cmdk-group-heading]]:text-xs **:[[cmdk-group-heading]]:font-medium',
|
||||
className
|
||||
)}
|
||||
value={value ?? heading ?? `----${useId()}`}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="command-list"
|
||||
class={cn(
|
||||
'no-scrollbar max-h-72 scroll-py-1 outline-none overflow-x-hidden overflow-y-auto',
|
||||
'no-scrollbar max-h-72 scroll-py-1 overflow-x-hidden overflow-y-auto outline-none',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
bind:ref
|
||||
data-slot="command"
|
||||
class={cn(
|
||||
'bg-popover text-popover-foreground rounded-4xl p-1 flex size-full flex-col overflow-hidden',
|
||||
'bg-popover text-popover-foreground flex size-full flex-col overflow-hidden rounded-4xl p-1',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
bind:ref
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/5 dark:ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-6 rounded-4xl p-6 text-sm shadow-xl ring-1 duration-100 sm:max-w-md fixed top-1/2 left-1/2 z-50 w-full -translate-x-1/2 -translate-y-1/2 outline-none',
|
||||
'bg-popover text-popover-foreground data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 ring-foreground/5 dark:ring-foreground/10 fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl p-6 text-sm shadow-xl ring-1 duration-100 outline-none sm:max-w-md',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-footer"
|
||||
class={cn('gap-2 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)}
|
||||
class={cn('flex flex-col-reverse gap-2 gap-2 sm:flex-row sm:justify-end', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="dialog-header"
|
||||
class={cn('gap-1.5 flex flex-col', className)}
|
||||
class={cn('flex flex-col gap-1.5', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="dialog-overlay"
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm fixed inset-0 isolate z-50',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 fixed inset-0 isolate z-50 bg-black/30 duration-100 supports-backdrop-filter:backdrop-blur-sm',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -23,14 +23,14 @@
|
||||
bind:indeterminate
|
||||
data-slot="dropdown-menu-checkbox-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium outline-hidden select-none data-inset:pl-9.5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked, indeterminate })}
|
||||
<span
|
||||
class="absolute right-2 flex items-center justify-center pointer-events-none"
|
||||
class="pointer-events-none absolute right-2 flex items-center justify-center"
|
||||
data-slot="dropdown-menu-checkbox-item-indicator"
|
||||
>
|
||||
{#if indeterminate}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{sideOffset}
|
||||
{align}
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground min-w-48 rounded-3xl p-1.5 shadow-lg ring-1 duration-100 data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 z-50 overflow-x-hidden overflow-y-auto outline-none data-closed:overflow-hidden animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground data-[side=inline-start]:slide-in-from-right-2 data-[side=inline-end]:slide-in-from-left-2 bg-popover/70 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground! relative z-50 min-w-48 animate-none! overflow-x-hidden overflow-y-auto rounded-3xl p-1.5 shadow-lg ring-1 duration-100 outline-none before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 data-closed:overflow-hidden',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2.5 rounded-2xl px-3 py-1.5 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 group/dropdown-menu-item relative flex cursor-pointer items-center outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:text-destructive not-data-[variant=destructive]:focus:**:text-accent-foreground group/dropdown-menu-item relative flex cursor-pointer items-center gap-2.5 rounded-2xl px-3 py-1.5 text-sm font-medium outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 data-inset:pl-9.5 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -15,14 +15,14 @@
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-radio-item"
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 relative flex cursor-default items-center outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground focus:**:text-accent-foreground relative flex cursor-default items-center gap-2.5 rounded-2xl py-2 pr-8 pl-3 text-sm font-medium outline-hidden select-none data-inset:pl-9.5 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{#snippet children({ checked })}
|
||||
<span
|
||||
class="absolute right-2 flex items-center justify-center pointer-events-none"
|
||||
class="pointer-events-none absolute right-2 flex items-center justify-center"
|
||||
data-slot="dropdown-menu-radio-item-indicator"
|
||||
>
|
||||
{#if checked}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
bind:ref
|
||||
data-slot="dropdown-menu-sub-content"
|
||||
class={cn(
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground min-w-36 rounded-3xl p-1.5 shadow-lg ring-1 duration-100 w-auto animate-none! relative bg-popover/70 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground!',
|
||||
'data-open:animate-in data-closed:animate-out data-closed:fade-out-0 data-open:fade-in-0 data-closed:zoom-out-95 data-open:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 ring-foreground/5 dark:ring-foreground/10 text-popover-foreground bg-popover/70 **:data-[slot$=-item]:focus:bg-foreground/10 **:data-[slot$=-item]:data-highlighted:bg-foreground/10 **:data-[slot$=-separator]:bg-foreground/5 **:data-[slot$=-trigger]:focus:bg-foreground/10 **:data-[slot$=-trigger]:aria-expanded:bg-foreground/10! **:data-[variant=destructive]:focus:bg-foreground/10! **:data-[variant=destructive]:text-accent-foreground! **:data-[variant=destructive]:**:text-accent-foreground! relative w-auto min-w-36 animate-none! rounded-3xl p-1.5 shadow-lg ring-1 duration-100 before:pointer-events-none before:absolute before:inset-0 before:-z-1 before:rounded-[inherit] before:backdrop-blur-2xl before:backdrop-saturate-150',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
data-slot="dropdown-menu-sub-trigger"
|
||||
data-inset={inset}
|
||||
class={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground gap-2 rounded-2xl px-3 py-1.5 text-sm font-medium data-inset:pl-9.5 [&_svg:not([class*='size-'])]:size-4 flex cursor-default items-center outline-hidden select-none data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"focus:bg-accent focus:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground flex cursor-default items-center gap-2 rounded-2xl px-3 py-1.5 text-sm font-medium outline-hidden select-none data-inset:pl-9.5 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="empty-content"
|
||||
class={cn(
|
||||
'gap-4 text-sm flex w-full max-w-sm min-w-0 flex-col items-center text-balance',
|
||||
'flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-sm text-balance',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="empty-description"
|
||||
class={cn(
|
||||
'text-sm/relaxed text-sm/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary',
|
||||
'text-muted-foreground [&>a:hover]:text-primary text-sm/relaxed text-sm/relaxed [&>a]:underline [&>a]:underline-offset-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="empty-header"
|
||||
class={cn('gap-2 flex max-w-sm flex-col items-center', className)}
|
||||
class={cn('flex max-w-sm flex-col items-center gap-2', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="empty"
|
||||
class={cn(
|
||||
'gap-4 rounded-2xl border-dashed p-12 flex w-full min-w-0 flex-1 flex-col items-center justify-center text-center text-balance',
|
||||
'flex w-full min-w-0 flex-1 flex-col items-center justify-center gap-4 rounded-2xl border-dashed p-12 text-center text-balance',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="field-content"
|
||||
class={cn('gap-1 group/field-content flex flex-1 flex-col leading-snug', className)}
|
||||
class={cn('group/field-content flex flex-1 flex-col gap-1 leading-snug', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-description"
|
||||
class={cn(
|
||||
'text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance',
|
||||
'text-muted-foreground text-left text-sm leading-normal font-normal group-has-[[data-orientation=horizontal]]/field:text-balance [[data-variant=legend]+&]:-mt-1.5',
|
||||
'last:mt-0 nth-last-2:-mt-1',
|
||||
'[&>a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4',
|
||||
className
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-group"
|
||||
class={cn(
|
||||
'gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4 group/field-group @container/field-group flex w-full flex-col',
|
||||
'group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
bind:ref
|
||||
data-slot="field-label"
|
||||
class={cn(
|
||||
'has-data-checked:bg-input/30 gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border *:data-[slot=field]:p-4 group/field-label peer/field-label flex w-fit leading-snug',
|
||||
'has-data-checked:bg-input/30 group/field-label peer/field-label flex w-fit gap-2 leading-snug leading-snug group-data-[disabled=true]/field:opacity-50 has-[>[data-slot=field]]:rounded-2xl has-[>[data-slot=field]]:border *:data-[slot=field]:p-4',
|
||||
'has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col',
|
||||
required && "after:text-red-500 after:content-['*']",
|
||||
className
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-separator"
|
||||
data-content={hasContent}
|
||||
class={cn('-my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2 relative', className)}
|
||||
class={cn('relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2', className)}
|
||||
{...restProps}
|
||||
>
|
||||
<Separator class="absolute inset-0 top-1/2" />
|
||||
{#if children}
|
||||
<span
|
||||
class="text-muted-foreground px-2 bg-background relative mx-auto block w-fit"
|
||||
class="text-muted-foreground bg-background relative mx-auto block w-fit px-2"
|
||||
data-slot="field-separator-content"
|
||||
>
|
||||
{@render children()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-set"
|
||||
class={cn(
|
||||
'gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3 flex flex-col',
|
||||
'flex flex-col gap-6 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="field-label"
|
||||
class={cn(
|
||||
'gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50 flex w-fit items-center leading-snug',
|
||||
'flex w-fit items-center gap-2 text-sm leading-snug leading-snug font-medium group-data-[disabled=true]/field:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
bind:ref
|
||||
data-slot="input-group-control"
|
||||
class={cn(
|
||||
'rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1',
|
||||
'flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<span
|
||||
bind:this={ref}
|
||||
class={cn(
|
||||
"text-muted-foreground gap-2 text-sm [&_svg:not([class*='size-'])]:size-4 flex items-center [&_svg]:pointer-events-none",
|
||||
"text-muted-foreground flex items-center gap-2 text-sm [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
bind:ref
|
||||
data-slot="input-group-control"
|
||||
class={cn(
|
||||
'rounded-none border-0 bg-transparent py-2.5 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent flex-1 resize-none',
|
||||
'flex-1 resize-none rounded-none border-0 bg-transparent py-2.5 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent',
|
||||
className
|
||||
)}
|
||||
bind:value
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
class={cn(
|
||||
'group/input-group bg-input/50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 h-9 rounded-4xl border border-transparent transition-[color,box-shadow,background-color] in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-3xl has-data-[align=block-start]:rounded-3xl has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 has-[textarea]:rounded-2xl has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5 relative flex w-full min-w-0 items-center outline-none has-[>textarea]:h-auto',
|
||||
'group/input-group bg-input/50 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/30 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 relative flex h-9 w-full min-w-0 items-center rounded-4xl border border-transparent transition-[color,box-shadow,background-color] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-data-[align=block-end]:rounded-3xl has-data-[align=block-start]:rounded-3xl has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-3 has-[textarea]:rounded-2xl has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="input-otp-group"
|
||||
class={cn(
|
||||
'gap-1.5 has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive rounded-3xl has-aria-invalid:ring-3 flex items-center',
|
||||
'has-aria-invalid:ring-destructive/20 dark:has-aria-invalid:ring-destructive/40 has-aria-invalid:border-destructive flex items-center gap-1.5 rounded-3xl has-aria-invalid:ring-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="input-otp-separator"
|
||||
role="separator"
|
||||
class={cn("[&_svg:not([class*='size-'])]:size-4 flex items-center", className)}
|
||||
class={cn("flex items-center [&_svg:not([class*='size-'])]:size-4", className)}
|
||||
{...restProps}
|
||||
>
|
||||
{#if children}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
bind:ref
|
||||
data-slot="input-otp-slot"
|
||||
class={cn(
|
||||
'w-10 h-13 rounded-sm bg-input/50 border-input data-active:border-ring data-active:ring-ring/30 data-active:aria-invalid:ring-destructive/20 dark:data-active:aria-invalid:ring-destructive/40 aria-invalid:border-destructive border text-sm transition-all outline-none data-active:ring-3 relative flex items-center justify-center data-active:z-10',
|
||||
'bg-input/50 border-input data-active:border-ring data-active:ring-ring/30 data-active:aria-invalid:ring-destructive/20 dark:data-active:aria-invalid:ring-destructive/40 aria-invalid:border-destructive relative flex h-13 w-10 items-center justify-center rounded-sm border text-sm transition-all outline-none data-active:z-10 data-active:ring-3',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
@@ -25,9 +25,7 @@
|
||||
<div
|
||||
class="cn-input-otp-caret pointer-events-none absolute inset-0 flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="animate-caret-blink bg-foreground h-4 w-px duration-1000 bg-foreground h-4 w-px"
|
||||
></div>
|
||||
<div class="animate-caret-blink bg-foreground bg-foreground h-4 w-px duration-1000"></div>
|
||||
</div>
|
||||
{/if}
|
||||
</InputOTPPrimitive.Cell>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
data-lpignore="true"
|
||||
data-1p-ignore="true"
|
||||
class={cn(
|
||||
'cn-input-otp-input gap-2 flex items-center disabled:cursor-not-allowed has-disabled:opacity-50',
|
||||
'cn-input-otp-input flex items-center gap-2 disabled:cursor-not-allowed has-disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 file:text-foreground placeholder:text-muted-foreground h-9 w-full min-w-0 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm',
|
||||
className
|
||||
)}
|
||||
type="file"
|
||||
@@ -38,7 +38,7 @@
|
||||
bind:this={ref}
|
||||
data-slot={dataSlot}
|
||||
class={cn(
|
||||
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 h-9 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] file:h-7 file:text-sm file:font-medium focus-visible:ring-3 aria-invalid:ring-3 md:text-sm file:text-foreground placeholder:text-muted-foreground w-full min-w-0 outline-none file:inline-flex file:border-0 file:bg-transparent disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50',
|
||||
'bg-input/50 focus-visible:border-ring focus-visible:ring-ring/30 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 file:text-foreground placeholder:text-muted-foreground h-9 w-full min-w-0 rounded-3xl border border-transparent px-3 py-1 text-base transition-[color,box-shadow,background-color] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-3 disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-3 md:text-sm',
|
||||
className
|
||||
)}
|
||||
{type}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="item-actions"
|
||||
class={cn('gap-2 flex items-center', className)}
|
||||
class={cn('flex items-center gap-2', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="item-content"
|
||||
class={cn(
|
||||
'gap-1 group-data-[size=xs]/item:gap-0.5 flex flex-1 flex-col [&+[data-slot=item-content]]:flex-none',
|
||||
'flex flex-1 flex-col gap-1 group-data-[size=xs]/item:gap-0.5 [&+[data-slot=item-content]]:flex-none',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="item-description"
|
||||
class={cn(
|
||||
'text-muted-foreground text-left text-sm [&>a:hover]:text-primary line-clamp-2 font-normal [&>a]:underline [&>a]:underline-offset-4',
|
||||
'text-muted-foreground [&>a:hover]:text-primary line-clamp-2 text-left text-sm font-normal [&>a]:underline [&>a]:underline-offset-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="item-footer"
|
||||
class={cn('gap-2 flex basis-full items-center justify-between', className)}
|
||||
class={cn('flex basis-full items-center justify-between gap-2', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
role="list"
|
||||
data-slot="item-group"
|
||||
class={cn(
|
||||
'gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2 group/item-group flex w-full flex-col',
|
||||
'group/item-group flex w-full flex-col gap-4 has-data-[size=sm]:gap-2.5 has-data-[size=xs]:gap-2',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<div
|
||||
bind:this={ref}
|
||||
data-slot="item-header"
|
||||
class={cn('gap-2 flex basis-full items-center justify-between', className)}
|
||||
class={cn('flex basis-full items-center justify-between gap-2', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:this={ref}
|
||||
data-slot="item-title"
|
||||
class={cn(
|
||||
'gap-2 text-sm leading-snug font-semibold underline-offset-4 line-clamp-1 flex w-fit items-center',
|
||||
'line-clamp-1 flex w-fit items-center gap-2 text-sm leading-snug font-semibold underline-offset-4',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
bind:ref
|
||||
data-slot="label"
|
||||
class={cn(
|
||||
'gap-2 text-sm leading-none font-medium group-data-[disabled=true]:opacity-50 peer-disabled:opacity-50 flex items-center select-none group-data-[disabled=true]:pointer-events-none peer-disabled:cursor-not-allowed',
|
||||
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ul
|
||||
bind:this={ref}
|
||||
data-slot="pagination-content"
|
||||
class={cn('gap-1 flex items-center', className)}
|
||||
class={cn('flex items-center gap-1', className)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user