fix: apply ui: prefix to all packages/ui components and automate it
This commit is contained in:
@@ -249,7 +249,10 @@ When adding a new component to this package, follow these steps:
|
||||
- `pnpm build` - Build the package
|
||||
- `pnpm dev` - Watch mode for CSS generation
|
||||
- `pnpm format` - Format code with Prettier
|
||||
- `pnpm prefix:list` - List files that need the `ui:` prefix (dry-run)
|
||||
- `pnpm prefix:list` - List files that need the `ui:` prefix (dry-run, exit 0)
|
||||
- `pnpm prefix:check` - Fail if any file under `src/` needs the `ui:` prefix
|
||||
- `pnpm prefix:check:staged` - Fail if **staged** `packages/ui` files need the prefix (pre-commit)
|
||||
- `pnpm prefix:check:changed` - Fail if **changed** `packages/ui` files need the prefix (CI; set `UI_PREFIX_GIT_BASE`)
|
||||
- `pnpm prefix` - Add `ui:` prefix to all Tailwind classes and format
|
||||
|
||||
### Configuration
|
||||
|
||||
+314
-57
@@ -2,25 +2,46 @@
|
||||
|
||||
/**
|
||||
* Script to add 'ui:' prefix to Tailwind classes in packages/ui
|
||||
* Usage: node add-ui-prefix.cjs [--dry-run]
|
||||
* Usage:
|
||||
* node add-ui-prefix.cjs # fix all files
|
||||
* node add-ui-prefix.cjs --dry-run # list fixes without writing (exit 0)
|
||||
* node add-ui-prefix.cjs --check # fail if fixes are needed (exit 1)
|
||||
* node add-ui-prefix.cjs --check --staged # check staged packages/ui files only
|
||||
* node add-ui-prefix.cjs --check --changed # check changed packages/ui files vs base ref
|
||||
* node add-ui-prefix.cjs --audit # fail on known prefix corruption patterns
|
||||
*
|
||||
* This script will:
|
||||
* - Find all .svelte, .ts, .tsx, .js, .jsx files in packages/ui/src
|
||||
* - Add 'ui:' prefix to Tailwind utility classes
|
||||
* - Skip classes that already have the prefix
|
||||
* - Preserve existing code structure
|
||||
* Env:
|
||||
* UI_PREFIX_GIT_BASE - optional git ref for --changed (default: origin/main)
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
|
||||
const DRY_RUN = process.argv.includes('--dry-run');
|
||||
const CHECK_MODE = process.argv.includes('--check');
|
||||
const AUDIT_MODE = process.argv.includes('--audit');
|
||||
const DRY_RUN = process.argv.includes('--dry-run') || CHECK_MODE;
|
||||
const STAGED_ONLY = process.argv.includes('--staged');
|
||||
const CHANGED_ONLY = process.argv.includes('--changed');
|
||||
const UI_PREFIX = 'ui:';
|
||||
const REPO_ROOT = path.join(__dirname, '../..');
|
||||
const UI_SRC_DIR = path.join(__dirname, 'src');
|
||||
const UI_SOURCE_EXTENSIONS = new Set(['.svelte', '.ts', '.tsx', '.js', '.jsx']);
|
||||
|
||||
// Collapse duplicate ui: markers (e.g. ui:hover:ui:bg-accent → ui:hover:bg-accent)
|
||||
function normalizeUiClass(className) {
|
||||
if (!className.includes(UI_PREFIX)) {
|
||||
return className;
|
||||
}
|
||||
|
||||
const stripped = className.split(UI_PREFIX).filter(Boolean).join('');
|
||||
return `${UI_PREFIX}${stripped}`;
|
||||
}
|
||||
|
||||
// Check if a class looks like a Tailwind utility class
|
||||
function isTailwindClass(className) {
|
||||
// Skip if already has ui: prefix
|
||||
if (className.startsWith(UI_PREFIX)) {
|
||||
// Skip if already prefixed (including malformed mid-chain ui: after a variant)
|
||||
if (className.startsWith(UI_PREFIX) || className.includes(':ui:')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -66,30 +87,126 @@ function isTailwindClass(className) {
|
||||
// Arbitrary variants (like [&_svg]:pointer-events-none, after:absolute, etc.)
|
||||
/^\[.*\]:|^(after|before|first|last|only|nth|marker|placeholder|selection|backdrop):/,
|
||||
// Common utilities
|
||||
/^(rounded|border|outline|shadow|ring|overflow|z-|order-|col-|row-|justify-|items-|content-|self-|place-|object-|cursor-|select-|resize-|appearance-|pointer-events-|visibility-|whitespace-|break-|overflow-|overscroll-|scroll-|will-change-|transform|transition|top|right|bottom|left)/
|
||||
/^(rounded|border|outline|shadow|ring|overflow|z-|order-|col-|row-|justify-|items-|content-|self-|place-|object-|cursor-|select-|resize-|appearance-|pointer-events-|visibility-|whitespace-|break-|overflow-|overscroll-|scroll-|will-change-|transform|transition|top-|right-|bottom-|left-)/
|
||||
];
|
||||
|
||||
return patterns.some((pattern) => pattern.test(className));
|
||||
}
|
||||
|
||||
// Process a single class string
|
||||
function processClassString(classString) {
|
||||
// Split by whitespace and process each class
|
||||
const classes = classString.split(/\s+/).filter((c) => c.trim());
|
||||
const processed = classes.map((cls) => {
|
||||
if (isTailwindClass(cls)) {
|
||||
return `${UI_PREFIX}${cls}`;
|
||||
function extractScopedClassNames(content) {
|
||||
const scoped = new Set();
|
||||
const styleBlocks = content.match(/<style[^>]*>[\s\S]*?<\/style>/gi);
|
||||
|
||||
if (!styleBlocks) {
|
||||
return scoped;
|
||||
}
|
||||
|
||||
for (const block of styleBlocks) {
|
||||
const css = block.replace(/<\/?style[^>]*>/gi, '');
|
||||
|
||||
for (const match of css.matchAll(/\.([a-zA-Z_][\w-]*)/g)) {
|
||||
scoped.add(match[1]);
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
return scoped;
|
||||
}
|
||||
|
||||
function isQuotedJsLiteral(token) {
|
||||
return (
|
||||
(token.startsWith("'") && token.endsWith("'") && token.length > 1) ||
|
||||
(token.startsWith('"') && token.endsWith('"') && token.length > 1)
|
||||
);
|
||||
}
|
||||
|
||||
function resolveClassToken(cls, scopedClasses = new Set()) {
|
||||
if (isQuotedJsLiteral(cls)) {
|
||||
const quote = cls[0];
|
||||
const inner = cls.slice(1, -1);
|
||||
const processed = processClassTokens(inner, scopedClasses);
|
||||
|
||||
return `${quote}${processed}${quote}`;
|
||||
}
|
||||
|
||||
const bareName = cls.startsWith(UI_PREFIX) ? cls.slice(UI_PREFIX.length) : cls;
|
||||
|
||||
// Component-scoped hook classes (defined in <style>) are never Tailwind utilities.
|
||||
if (scopedClasses.has(bareName)) {
|
||||
return bareName;
|
||||
}
|
||||
|
||||
if (cls.includes(UI_PREFIX)) {
|
||||
return normalizeUiClass(cls);
|
||||
}
|
||||
|
||||
if (isTailwindClass(cls)) {
|
||||
return `${UI_PREFIX}${cls}`;
|
||||
}
|
||||
|
||||
return cls;
|
||||
}
|
||||
|
||||
function processClassTokens(classString, scopedClasses = new Set()) {
|
||||
const classes = classString.split(/\s+/).filter((c) => c.trim());
|
||||
return classes.map((cls) => resolveClassToken(cls, scopedClasses)).join(' ');
|
||||
}
|
||||
|
||||
// Process a single class string
|
||||
function processClassString(classString, scopedClasses = new Set()) {
|
||||
return processClassTokens(classString, scopedClasses);
|
||||
}
|
||||
|
||||
function repairCorruptedQuotedPrefixes(content) {
|
||||
return content.replace(/ui:'([^']*)'/g, "'ui:$1'").replace(/ui:"([^"]*)"/g, '"ui:$1"');
|
||||
}
|
||||
|
||||
function processInterpolatedSegments(value, scopedClasses, expressionPattern) {
|
||||
const parts = value.split(expressionPattern);
|
||||
|
||||
return parts.reduce((result, part, index) => {
|
||||
const isExpression = index % 2 === 1;
|
||||
const processed = isExpression
|
||||
? processQuotedClassStrings(part, scopedClasses)
|
||||
: processClassString(part, scopedClasses);
|
||||
|
||||
if (result.length === 0) {
|
||||
return processed;
|
||||
}
|
||||
|
||||
const needsSpace = processed.length > 0 && result.length > 0 && !result.endsWith(' ') && !processed.startsWith(' ');
|
||||
|
||||
return `${result}${needsSpace ? ' ' : ''}${processed}`;
|
||||
}, '');
|
||||
}
|
||||
|
||||
function processClassAttributeValue(value, scopedClasses = new Set()) {
|
||||
return processInterpolatedSegments(value, scopedClasses, /(\{[^}]*\})/g);
|
||||
}
|
||||
|
||||
function processTemplateClassAttributeValue(value, scopedClasses = new Set()) {
|
||||
return processInterpolatedSegments(value, scopedClasses, /(\$\{[^}]*\})/g);
|
||||
}
|
||||
|
||||
function processQuotedClassStrings(content, scopedClasses = new Set()) {
|
||||
let processed = content;
|
||||
|
||||
processed = processed.replace(/'([^']*)'/g, (match, quoted) => {
|
||||
const result = processClassString(quoted, scopedClasses);
|
||||
return result !== quoted ? `'${result}'` : match;
|
||||
});
|
||||
|
||||
return processed.join(' ');
|
||||
processed = processed.replace(/"([^"]*)"/g, (match, quoted) => {
|
||||
const result = processClassString(quoted, scopedClasses);
|
||||
return result !== quoted ? `"${result}"` : match;
|
||||
});
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// Process file content
|
||||
function processFileContent(content) {
|
||||
let modified = content;
|
||||
let changes = 0;
|
||||
function processFileContent(content, scopedClasses = new Set()) {
|
||||
let modified = repairCorruptedQuotedPrefixes(content);
|
||||
let changes = modified !== content ? 1 : 0;
|
||||
|
||||
// Helper to process and count changes
|
||||
function processAndReplace(pattern, replacer) {
|
||||
@@ -104,25 +221,25 @@ function processFileContent(content) {
|
||||
|
||||
// Pattern 1: class="..." or className="..."
|
||||
processAndReplace(/(class|className)="([^"]*)"/g, (match, attr, classes) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processClassAttributeValue(classes, scopedClasses);
|
||||
return processed !== classes ? `${attr}="${processed}"` : match;
|
||||
});
|
||||
|
||||
// Pattern 1b: class='...' or className='...'
|
||||
processAndReplace(/(class|className)='([^']*)'/g, (match, attr, classes) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processClassAttributeValue(classes, scopedClasses);
|
||||
return processed !== classes ? `${attr}='${processed}'` : match;
|
||||
});
|
||||
|
||||
// Pattern 2: class={`...`} or className={`...`}
|
||||
processAndReplace(/(class|className)=\{`([^`]+)`\}/g, (match, attr, classes) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processTemplateClassAttributeValue(classes, scopedClasses);
|
||||
return processed !== classes ? `${attr}={\`${processed}\`}` : match;
|
||||
});
|
||||
|
||||
// Pattern 3: class={'...'} or className={'...'}
|
||||
processAndReplace(/(class|className)=\{['"]([^'"]+)['"]\}/g, (match, attr, classes) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processClassString(classes, scopedClasses);
|
||||
return processed !== classes ? `${attr}={\`${processed}\`}` : match;
|
||||
});
|
||||
|
||||
@@ -176,14 +293,14 @@ function processFileContent(content) {
|
||||
processAndReplace(
|
||||
new RegExp(`(${tailwindFunctionNames.join('|')})\\s*\\(\\s*['"]([^'"]+)['"]\\s*([,)])`, 'g'),
|
||||
(match, fn, classes, separator) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processClassString(classes, scopedClasses);
|
||||
return processed !== classes ? `${fn}("${processed}"${separator}` : match;
|
||||
}
|
||||
);
|
||||
|
||||
// Pattern 7: Template literals in function calls: cn(`...`)
|
||||
processAndReplace(/(cn|tv)\s*\(`([^`]+)`/g, (match, fn, classes) => {
|
||||
const processed = processClassString(classes);
|
||||
const processed = processClassString(classes, scopedClasses);
|
||||
return processed !== classes ? `${fn}(\`${processed}\`` : match;
|
||||
});
|
||||
|
||||
@@ -195,19 +312,19 @@ function processFileContent(content) {
|
||||
|
||||
// Process double-quoted strings
|
||||
processedObj = processedObj.replace(/"([^"]*)"/g, (match, value) => {
|
||||
const processed = processClassString(value);
|
||||
const processed = processClassString(value, scopedClasses);
|
||||
return processed !== value ? `"${processed}"` : match;
|
||||
});
|
||||
|
||||
// Process single-quoted strings
|
||||
processedObj = processedObj.replace(/'([^']*)'/g, (match, value) => {
|
||||
const processed = processClassString(value);
|
||||
const processed = processClassString(value, scopedClasses);
|
||||
return processed !== value ? `'${processed}'` : match;
|
||||
});
|
||||
|
||||
// Process template literals
|
||||
processedObj = processedObj.replace(/`([^`]*)`/g, (match, value) => {
|
||||
const processed = processClassString(value);
|
||||
const processed = processClassString(value, scopedClasses);
|
||||
return processed !== value ? `\`${processed}\`` : match;
|
||||
});
|
||||
|
||||
@@ -217,22 +334,14 @@ function processFileContent(content) {
|
||||
// Pattern 8: class: cn(...) patterns (like in derived props)
|
||||
// Matches: class: cn('string1', 'string2', ...)
|
||||
processAndReplace(/class:\s*cn\s*\(([^)]+)\)/g, (match, args) => {
|
||||
// Use a more sophisticated approach to handle multi-line strings and comments
|
||||
let processedArgs = args;
|
||||
const processedArgs = processQuotedClassStrings(args, scopedClasses);
|
||||
return processedArgs !== args ? `class: cn(${processedArgs})` : match;
|
||||
});
|
||||
|
||||
// Process single-quoted strings: 'content'
|
||||
processedArgs = processedArgs.replace(/'([^']*)'/g, (match, content) => {
|
||||
const processed = processClassString(content);
|
||||
return processed !== content ? `'${processed}'` : match;
|
||||
});
|
||||
|
||||
// Process double-quoted strings: "content"
|
||||
processedArgs = processedArgs.replace(/"([^"]*)"/g, (match, content) => {
|
||||
const processed = processClassString(content);
|
||||
return processed !== content ? `"${processed}"` : match;
|
||||
});
|
||||
|
||||
return `class: cn(${processedArgs})`;
|
||||
// Pattern 8b: class={cn(...)} in Svelte markup (supports embedded quotes in class strings)
|
||||
processAndReplace(/class=\{cn\s*\(([\s\S]*?)\)\}/g, (match, args) => {
|
||||
const processedArgs = processQuotedClassStrings(args, scopedClasses);
|
||||
return processedArgs !== args ? `class={cn(${processedArgs})}` : match;
|
||||
});
|
||||
|
||||
return { content: modified, changes };
|
||||
@@ -242,7 +351,8 @@ function processFileContent(content) {
|
||||
function processFile(filePath) {
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const { content: modified, changes } = processFileContent(content);
|
||||
const scopedClasses = path.extname(filePath) === '.svelte' ? extractScopedClassNames(content) : new Set();
|
||||
const { content: modified, changes } = processFileContent(content, scopedClasses);
|
||||
|
||||
if (changes > 0) {
|
||||
if (DRY_RUN) {
|
||||
@@ -298,22 +408,163 @@ function findFiles(dir, extensions = ['.svelte', '.ts', '.tsx', '.js', '.jsx'])
|
||||
return files;
|
||||
}
|
||||
|
||||
// Main execution
|
||||
function main() {
|
||||
const uiDir = path.join(__dirname);
|
||||
const srcDir = path.join(uiDir, 'src');
|
||||
function runGitPaths(args) {
|
||||
const result = spawnSync('git', args, {
|
||||
cwd: REPO_ROOT,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
|
||||
if (!fs.existsSync(srcDir)) {
|
||||
console.error(`Error: ${srcDir} does not exist`);
|
||||
if (result.status !== 0) {
|
||||
const errorOutput = result.stderr || result.stdout || `git ${args.join(' ')} failed`;
|
||||
throw new Error(errorOutput.trim());
|
||||
}
|
||||
|
||||
return result.stdout.split('\0').filter(Boolean);
|
||||
}
|
||||
|
||||
function isUiSourceFile(relativePath) {
|
||||
if (!relativePath.startsWith('packages/ui/src/')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return UI_SOURCE_EXTENSIONS.has(path.extname(relativePath));
|
||||
}
|
||||
|
||||
function getScopedGitFiles(mode) {
|
||||
let paths = [];
|
||||
|
||||
if (mode === 'staged') {
|
||||
paths = runGitPaths(['diff', '--name-only', '-z', '--cached', '--diff-filter=ACMR']);
|
||||
} else if (mode === 'changed') {
|
||||
const baseRef = process.env.UI_PREFIX_GIT_BASE || 'origin/main';
|
||||
paths = runGitPaths(['diff', '--name-only', '-z', '--diff-filter=ACMR', `${baseRef}...HEAD`]);
|
||||
}
|
||||
|
||||
return paths.filter(isUiSourceFile).map((relativePath) => path.join(REPO_ROOT, relativePath));
|
||||
}
|
||||
|
||||
function resolveFilesToProcess(srcDir) {
|
||||
if (STAGED_ONLY) {
|
||||
return getScopedGitFiles('staged');
|
||||
}
|
||||
|
||||
if (CHANGED_ONLY) {
|
||||
return getScopedGitFiles('changed');
|
||||
}
|
||||
|
||||
return findFiles(srcDir);
|
||||
}
|
||||
|
||||
const AUDIT_PATTERNS = [
|
||||
{
|
||||
id: 'corrupted-quoted-prefix',
|
||||
label: 'ui:\'...\' / ui:"..." (prefix script broke Svelte/JS string literals)',
|
||||
regex: /ui:['"]/
|
||||
},
|
||||
{
|
||||
id: 'double-variant-prefix',
|
||||
label: 'ui:variant:ui:utility (duplicate ui: inside variant chains)',
|
||||
regex: /ui:[a-z0-9_-]+:ui:/
|
||||
},
|
||||
{
|
||||
id: 'inverted-variant-prefix',
|
||||
label: 'variant:ui:utility (ui: after variant instead of at start)',
|
||||
regex: /(?:^|[\s"'`{])(?:hover|focus|active|disabled|dark|sm|md|lg|xl|2xl|group-hover|peer-focus):ui:/
|
||||
}
|
||||
];
|
||||
|
||||
function auditScopedHookMismatches(filePath, content) {
|
||||
const findings = [];
|
||||
|
||||
if (!filePath.endsWith('.svelte')) {
|
||||
return findings;
|
||||
}
|
||||
|
||||
const scoped = extractScopedClassNames(content);
|
||||
|
||||
for (const cls of scoped) {
|
||||
const prefixed = `${UI_PREFIX}${cls}`;
|
||||
const pattern = new RegExp(`class=["'\`][^"'\`]*${prefixed.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`);
|
||||
if (pattern.test(content)) {
|
||||
findings.push({
|
||||
id: 'scoped-hook-prefixed',
|
||||
label: `<style> hook .${cls} prefixed as ${prefixed} in markup`,
|
||||
sample: prefixed
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
}
|
||||
|
||||
function auditFile(filePath) {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const findings = [];
|
||||
|
||||
for (const pattern of AUDIT_PATTERNS) {
|
||||
if (pattern.regex.test(content)) {
|
||||
findings.push({ id: pattern.id, label: pattern.label, sample: content.match(pattern.regex)?.[0] });
|
||||
}
|
||||
}
|
||||
|
||||
return findings.concat(auditScopedHookMismatches(filePath, content));
|
||||
}
|
||||
|
||||
function runAudit() {
|
||||
const files = resolveFilesToProcess(UI_SRC_DIR);
|
||||
let issueCount = 0;
|
||||
|
||||
console.log(`Auditing ${files.length} file(s) in packages/ui/src for prefix corruption...\n`);
|
||||
|
||||
for (const file of files) {
|
||||
const findings = auditFile(file);
|
||||
|
||||
if (findings.length === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
issueCount += findings.length;
|
||||
console.log(file);
|
||||
|
||||
for (const finding of findings) {
|
||||
console.log(` - ${finding.label}${finding.sample ? ` (e.g. ${finding.sample})` : ''}`);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
if (issueCount > 0) {
|
||||
console.error(`❌ Found ${issueCount} prefix corruption issue(s).`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Searching for files in ${srcDir}...`);
|
||||
const files = findFiles(srcDir);
|
||||
console.log(`Found ${files.length} files to process\n`);
|
||||
console.log('✓ No known prefix corruption patterns found.');
|
||||
}
|
||||
|
||||
// Main execution
|
||||
function main() {
|
||||
if (AUDIT_MODE) {
|
||||
runAudit();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(UI_SRC_DIR)) {
|
||||
console.error(`Error: ${UI_SRC_DIR} does not exist`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const scopeLabel = STAGED_ONLY ? 'staged' : CHANGED_ONLY ? 'changed' : 'all';
|
||||
const files = resolveFilesToProcess(UI_SRC_DIR);
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log(`No ${scopeLabel} packages/ui source files to check.`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log(`Checking ${files.length} ${scopeLabel} file(s) in packages/ui/src...\n`);
|
||||
|
||||
if (DRY_RUN) {
|
||||
console.log('DRY RUN MODE - No files will be modified\n');
|
||||
console.log(`${CHECK_MODE ? 'CHECK MODE' : 'DRY RUN MODE'} - No files will be modified\n`);
|
||||
}
|
||||
|
||||
let totalChanges = 0;
|
||||
@@ -328,6 +579,12 @@ function main() {
|
||||
}
|
||||
|
||||
console.log(`\n${DRY_RUN ? 'Would process' : 'Processed'} ${filesChanged} files with ${totalChanges} total changes`);
|
||||
|
||||
if (CHECK_MODE && filesChanged > 0) {
|
||||
console.error('\n❌ packages/ui Tailwind classes must use the ui: prefix.');
|
||||
console.error("Run 'pnpm --filter @cio/ui prefix' to fix, then commit again.");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
"dev": "pnpm generate-css --watch",
|
||||
"format": "prettier --write .",
|
||||
"prefix:list": "node add-ui-prefix.cjs --dry-run",
|
||||
"prefix:check": "node add-ui-prefix.cjs --check",
|
||||
"prefix:check:staged": "node add-ui-prefix.cjs --check --staged",
|
||||
"prefix:check:changed": "node add-ui-prefix.cjs --check --changed",
|
||||
"prefix:audit": "node add-ui-prefix.cjs --audit",
|
||||
"prefix": "node add-ui-prefix.cjs && pnpm format"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -46,12 +46,7 @@
|
||||
// of the other series, this overrides that
|
||||
'ui:[&_.lc-area-path]:opacity-100 ui:[&_.lc-highlight-line]:opacity-100 ui:[&_.lc-highlight-point]:opacity-100 ui:[&_.lc-spline-path]:opacity-100 ui:[&_.lc-text]:text-xs ui:[&_.lc-text-svg]:overflow-visible',
|
||||
|
||||
// We don't want the little tick lines between the axis labels and the chart, so we remove
|
||||
// the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every
|
||||
// chart.
|
||||
'ui:[&_.lc-axis-tick]:stroke-0',
|
||||
|
||||
// We don't want to display the rule on the x/y axis, as there is already going to be
|
||||
// We don't want the little tick lines between the axis labels and the chart, so we remove // the stroke. The alternative is to manually disable `tickMarks` on the x/y axis of every // chart.'ui:[&_.lc-axis-tick]:stroke-0', // We don't want to display the rule on the x/y axis, as there is already going to be
|
||||
// a grid line there and rule ends up overlapping the marks because it is rendered after
|
||||
// the marks
|
||||
'ui:[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 ui:[&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0',
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<div
|
||||
class={cn(
|
||||
'ui:[&>svg]:text-muted-foreground ui:flex ui:w-full ui:flex-wrap ui:items-stretch ui:gap-2 ui:[&>svg]:size-2.5',
|
||||
indicator === 'dot' && 'items-center'
|
||||
indicator === 'dot' && 'ui:items-center'
|
||||
)}
|
||||
>
|
||||
{#if formatter && item.value !== undefined && item.name}
|
||||
|
||||
@@ -26,10 +26,8 @@
|
||||
data-slot="dialog-content"
|
||||
class={cn(
|
||||
// `has-[[data-slot=dialog-footer][data-sticky=true]]:pb-0` drops the
|
||||
// dialog's own bottom padding when a sticky footer is rendered —
|
||||
// the footer's own `py-4` provides the bottom spacing instead, and
|
||||
// it pins flush with the dialog's bottom edge.
|
||||
'ui:bg-background ui:border ui:data-[state=open]:animate-in ui:data-[state=closed]:animate-out ui:data-[state=closed]:fade-out-0 ui:data-[state=open]:fade-in-0 ui:data-[state=closed]:zoom-out-95 ui:data-[state=open]:zoom-in-95 ui:fixed ui:left-[50%] ui:top-[50%] ui:z-200 ui:grid ui:w-full ui:max-w-[calc(100%-2rem)] ui:translate-x-[-50%] ui:translate-y-[-50%] ui:gap-4 ui:rounded-lg ui:p-6 ui:[&:has([data-slot=dialog-footer][data-sticky=true])]:pb-0 ui:shadow-lg ui:duration-200 ui:sm:max-w-lg',
|
||||
// dialog's own ui:bottom padding when a ui:sticky footer is rendered — // the footer's own `py-4` provides the bottom spacing instead, and
|
||||
// it pins flush with the dialog's ui:bottom edge.'ui:bg-background ui:border ui:data-[state=open]:animate-in ui:data-[state=closed]:animate-out ui:data-[state=closed]:fade-out-0 ui:data-[state=open]:fade-in-0 ui:data-[state=closed]:zoom-out-95 ui:data-[state=open]:zoom-in-95 ui:fixed ui:left-[50%] ui:top-[50%] ui:z-200 ui:grid ui:w-full ui:max-w-[calc(100%-2rem)] ui:translate-x-[-50%] ui:translate-y-[-50%] ui:gap-4 ui:rounded-lg ui:p-6 ui:[&:has([data-slot=dialog-footer][data-sticky=true])]:pb-0 ui:shadow-lg ui:duration-200 ui:sm:max-w-lg',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="ui:size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn('ui:size-4', !checked && 'text-transparent')} />
|
||||
<CheckIcon class={cn('ui:size-4', !checked && 'ui:text-transparent')} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"ui:data-highlighted:bg-accent ui:data-highlighted:text-accent-foreground ui:data-[variant=destructive]:text-destructive ui:data-[variant=destructive]:data-highlighted:bg-destructive/10 ui:dark:ui:data-[variant=destructive]:data-highlighted:bg-destructive/20 ui:data-[variant=destructive]:data-highlighted:text-destructive ui:data-[variant=destructive]:*:[svg]:text-destructive! ui:[&_svg:not([class*='text-'])]:text-muted-foreground ui:outline-hidden ui:relative ui:flex ui:cursor-default ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-1.5 ui:text-sm ui:data-disabled:pointer-events-none ui:data-inset:pl-8 ui:data-disabled:opacity-50 ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0",
|
||||
"ui:data-highlighted:bg-accent ui:data-highlighted:text-accent-foreground ui:data-[variant=destructive]:text-destructive ui:data-[variant=destructive]:data-highlighted:bg-destructive/10 ui:dark:data-[variant=destructive]:data-highlighted:bg-destructive/20 ui:data-[variant=destructive]:data-highlighted:text-destructive ui:data-[variant=destructive]:*:[svg]:text-destructive! ui:[&_svg:not([class*='text-'])]:text-muted-foreground ui:outline-hidden ui:relative ui:flex ui:cursor-default ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-1.5 ui:text-sm ui:data-disabled:pointer-events-none ui:data-inset:pl-8 ui:data-disabled:opacity-50 ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
'ui:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px ui:has-[>[data-slot=field-content]]:items-start'
|
||||
],
|
||||
responsive: [
|
||||
'@md/field-group:ui:flex-row @md/field-group:ui:items-center @md/field-group:ui:[&>*]:w-auto ui:flex-col ui:*:w-full ui:*.sr-only:w-auto',
|
||||
'@md/field-group:ui:[&>[data-slot=field-label]]:flex-auto',
|
||||
'@md/field-group:ui:has-[>[data-slot=field-content]]:items-start @md/field-group:ui:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
||||
'ui:@md/field-group:flex-row ui:@md/field-group:items-center ui:@md/field-group:[&>*]:w-auto ui:flex-col ui:*:w-full ui:*.sr-only:w-auto',
|
||||
'ui:@md/field-group:[&>[data-slot=field-label]]:flex-auto',
|
||||
'ui:@md/field-group:has-[>[data-slot=field-content]]:items-start ui:@md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px'
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
{#if indeterminate}
|
||||
<MinusIcon class="ui:size-4" />
|
||||
{:else}
|
||||
<CheckIcon class={cn('ui:size-4', !checked && 'text-transparent')} />
|
||||
<CheckIcon class={cn('ui:size-4', !checked && 'ui:text-transparent')} />
|
||||
{/if}
|
||||
</span>
|
||||
{@render childrenProp?.()}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
data-inset={inset}
|
||||
data-variant={variant}
|
||||
class={cn(
|
||||
"ui:focus:bg-accent ui:focus:text-accent-foreground ui:data-[variant=destructive]:text-destructive ui:data-[variant=destructive]:focus:bg-destructive/10 ui:dark:ui:data-[variant=destructive]:focus:bg-destructive/20 ui:data-[variant=destructive]:focus:text-destructive ui:data-[variant=destructive]:*:[svg]:text-destructive! ui:[&_svg:not([class*='text-'])]:text-muted-foreground ui:outline-hidden ui:relative ui:flex ui:cursor-default ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-1.5 ui:text-sm ui:data-disabled:pointer-events-none ui:data-inset:pl-8 ui:data-disabled:opacity-50 ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0",
|
||||
"ui:focus:bg-accent ui:focus:text-accent-foreground ui:data-[variant=destructive]:text-destructive ui:data-[variant=destructive]:focus:bg-destructive/10 ui:dark:data-[variant=destructive]:focus:bg-destructive/20 ui:data-[variant=destructive]:focus:text-destructive ui:data-[variant=destructive]:*:[svg]:text-destructive! ui:[&_svg:not([class*='text-'])]:text-muted-foreground ui:outline-hidden ui:relative ui:flex ui:cursor-default ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-1.5 ui:text-sm ui:data-disabled:pointer-events-none ui:data-inset:pl-8 ui:data-disabled:opacity-50 ui:[&_svg:not([class*='size-'])]:size-4 ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
bind:ref
|
||||
data-slot="navigation-menu-content"
|
||||
class={cn(
|
||||
'ui:data-[motion^=from-]:animate-in ui:data-[motion^=to-]:animate-out ui:data-[motion^=from-]:fade-in ui:data-[motion^=to-]:fade-out ui:data-[motion=from-end]:slide-in-from-end-52 ui:data-[motion=from-start]:slide-in-from-start-52 ui:data-[motion=to-end]:slide-out-to-end-52 ui:data-[motion=to-start]:slide-out-to-start-52 ui:start-0 ui:top-0 ui:w-full ui:md:ui:absolute ui:md:ui:w-auto',
|
||||
'ui:data-[motion^=from-]:animate-in ui:data-[motion^=to-]:animate-out ui:data-[motion^=from-]:fade-in ui:data-[motion^=to-]:fade-out ui:data-[motion=from-end]:slide-in-from-end-52 ui:data-[motion=from-start]:slide-in-from-start-52 ui:data-[motion=to-end]:slide-out-to-end-52 ui:data-[motion=to-start]:slide-out-to-start-52 ui:start-0 ui:top-0 ui:w-full ui:md:absolute ui:md:w-auto',
|
||||
'ui:group-data-[viewport=false]/navigation-menu:bg-popover ui:group-data-[viewport=false]/navigation-menu:text-popover-foreground ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 ui:group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 ui:group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 ui:**:data-[slot=navigation-menu-link]:focus:ring-0 ui:**:data-[slot=navigation-menu-link]:focus:outline-none ui:group-data-[viewport=false]/navigation-menu:top-full ui:group-data-[viewport=false]/navigation-menu:mt-1.5 ui:group-data-[viewport=false]/navigation-menu:overflow-hidden ui:group-data-[viewport=false]/navigation-menu:rounded-md ui:group-data-[viewport=false]/navigation-menu:border ui:group-data-[viewport=false]/navigation-menu:shadow ui:group-data-[viewport=false]/navigation-menu:duration-200',
|
||||
className
|
||||
)}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { tv } from 'tailwind-variants';
|
||||
|
||||
export const navigationMenuTriggerStyle = tv({
|
||||
base: 'ui:bg-background ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:data-[state=open]:ui:hover:bg-accent ui:data-[state=open]:ui:text-accent-foreground ui:data-[state=open]:ui:focus:bg-accent ui:data-[state=open]:ui:bg-accent/50 ui:focus-visible:ring-ring/50 ui:group ui:inline-flex ui:h-9 ui:w-max ui:items-center ui:justify-center ui:rounded-md ui:px-4 ui:py-2 ui:text-sm ui:font-medium ui:outline-none ui:transition-[color,box-shadow] ui:focus-visible:outline-1 ui:focus-visible:ring-[3px] ui:disabled:pointer-events-none ui:disabled:opacity-50'
|
||||
base: 'ui:bg-background ui:hover:bg-accent ui:hover:text-accent-foreground ui:focus:bg-accent ui:focus:text-accent-foreground ui:data-[state=open]:hover:bg-accent ui:data-[state=open]:text-accent-foreground ui:data-[state=open]:focus:bg-accent ui:data-[state=open]:bg-accent/50 ui:focus-visible:ring-ring/50 ui:group ui:inline-flex ui:h-9 ui:w-max ui:items-center ui:justify-center ui:rounded-md ui:px-4 ui:py-2 ui:text-sm ui:font-medium ui:outline-none ui:transition-[color,box-shadow] ui:focus-visible:outline-1 ui:focus-visible:ring-[3px] ui:disabled:pointer-events-none ui:disabled:opacity-50'
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
{@render children?.()}
|
||||
|
||||
<ChevronDownIcon
|
||||
class="ui:relative ui:top-px ui:ms-1 ui:size-3 ui:transition ui:duration-300 ui:group-data-[state=open]:ui:rotate-180"
|
||||
class="ui:relative ui:top-px ui:ms-1 ui:size-3 ui:transition ui:duration-300 ui:group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
bind:ref
|
||||
data-slot="navigation-menu-viewport"
|
||||
class={cn(
|
||||
'ui:origin-top-center ui:bg-popover ui:text-popover-foreground ui:data-[state=open]:animate-in ui:data-[state=closed]:animate-out ui:data-[state=closed]:zoom-out-95 ui:data-[state=open]:zoom-in-90 ui:relative ui:mt-1.5 ui:h-(--bits-navigation-menu-viewport-height) ui:w-full ui:overflow-hidden ui:rounded-md ui:border ui:shadow ui:md:ui:w-[var(--bits-navigation-menu-viewport-width)]',
|
||||
'ui:origin-top-center ui:bg-popover ui:text-popover-foreground ui:data-[state=open]:animate-in ui:data-[state=closed]:animate-out ui:data-[state=closed]:zoom-out-95 ui:data-[state=open]:zoom-in-90 ui:relative ui:mt-1.5 ui:h-(--bits-navigation-menu-viewport-height) ui:w-full ui:overflow-hidden ui:rounded-md ui:border ui:shadow ui:md:w-[var(--bits-navigation-menu-viewport-width)]',
|
||||
className
|
||||
)}
|
||||
{...restProps}
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
data-active={isActive}
|
||||
class={cn(
|
||||
buttonVariants({
|
||||
variant: isActive ? 'outline' : 'ghost',
|
||||
variant: isActive ? 'ui:outline' : 'ghost',
|
||||
size
|
||||
}),
|
||||
className
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
data-slot="sidebar-container"
|
||||
class={cn(
|
||||
'ui:w-(--sidebar-width) ui:fixed ui:inset-y-0 ui:z-100 ui:hidden ui:max-h-svh ui:transition-[left,right,width] ui:duration-200 ui:ease-linear ui:md:flex',
|
||||
side === 'left'
|
||||
side === 'ui:left'
|
||||
? 'ui:left-0 ui:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
||||
: 'ui:right-0 ui:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||
// Adjust the padding for floating and inset variants.
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
let {
|
||||
variant,
|
||||
duration = 1,
|
||||
class: className = 'text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-7xl md:leading-20',
|
||||
class:
|
||||
className = 'ui:text-4xl ui:font-bold ui:tracking-[-0.02em] ui:drop-shadow-sm ui:md:text-7xl ui:md:leading-20',
|
||||
children
|
||||
}: Props = $props();
|
||||
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
let { children, className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('p-4 text-left', className)}>
|
||||
<div class={cn('ui:p-4 ui:text-left', className)}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -13,15 +13,19 @@
|
||||
|
||||
<div
|
||||
class={cn(
|
||||
'relative mx-auto w-full border border-dashed border-zinc-300 px-8 shadow-sm sm:px-6 md:px-8 dark:border-zinc-800 dark:bg-[#09090B]',
|
||||
'ui:relative ui:mx-auto ui:w-full ui:border ui:border-dashed ui:border-zinc-300 ui:px-8 ui:shadow-sm ui:sm:px-6 ui:md:px-8 ui:dark:border-zinc-800 ui:dark:bg-[#09090B]',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="absolute top-4 left-0 z-0 h-px w-full bg-zinc-400 sm:top-6 md:top-8 dark:bg-zinc-700"></div>
|
||||
<div class="absolute bottom-4 left-0 z-0 h-px w-full bg-zinc-400 sm:bottom-6 md:bottom-8 dark:bg-zinc-700"></div>
|
||||
<div class="relative w-full border-x border-zinc-400 dark:border-zinc-700">
|
||||
<div
|
||||
class="ui:absolute ui:top-4 ui:left-0 ui:z-0 ui:h-px ui:w-full ui:bg-zinc-400 ui:sm:top-6 ui:md:top-8 ui:dark:bg-zinc-700"
|
||||
></div>
|
||||
<div
|
||||
class="ui:absolute ui:bottom-4 ui:left-0 ui:z-0 ui:h-px ui:w-full ui:bg-zinc-400 ui:sm:bottom-6 ui:md:bottom-8 ui:dark:bg-zinc-700"
|
||||
></div>
|
||||
<div class="ui:relative ui:w-full ui:border-x ui:border-zinc-400 ui:dark:border-zinc-700">
|
||||
<Ellipses />
|
||||
<div class="relative z-20 mx-auto py-8">
|
||||
<div class="ui:relative ui:z-20 ui:mx-auto ui:py-8">
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
'rounded-full outline outline-8 dark:outline-gray-950 sm:my-6 md:my-8 size-1 my-4 outline-gray-50 bg-green-400';
|
||||
</script>
|
||||
|
||||
<div class="absolute z-0 grid h-full w-full items-center gap-8 lg:grid-cols-2">
|
||||
<section class="absolute z-0 grid h-full w-full grid-cols-2 place-content-between">
|
||||
<div class={`${sharedClass} -mx-[2.5px]`}></div>
|
||||
<div class={`${sharedClass} -mx-[2px] place-self-end`}></div>
|
||||
<div class={`${sharedClass} -mx-[2.5px]`}></div>
|
||||
<div class={`${sharedClass} -mx-[2px] place-self-end`}></div>
|
||||
<div class="ui:absolute ui:z-0 ui:grid ui:h-full ui:w-full ui:items-center ui:gap-8 ui:lg:grid-cols-2">
|
||||
<section class="ui:absolute ui:z-0 ui:grid ui:h-full ui:w-full ui:place-content-between grid-cols-2">
|
||||
<div class={`${sharedClass} ui:-mx-[2.5px]`}></div>
|
||||
<div class={`${sharedClass} ui:-mx-[2px] ui:place-self-end`}></div>
|
||||
<div class={`${sharedClass} ui:-mx-[2.5px]`}></div>
|
||||
<div class={`${sharedClass} ui:-mx-[2px] ui:place-self-end`}></div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
@@ -275,14 +275,14 @@
|
||||
<div
|
||||
bind:this={root}
|
||||
class={cn(
|
||||
'relative h-full w-full',
|
||||
'ui:relative ui:h-full ui:w-full',
|
||||
glowColor == null && 'ui:[--dot-field-glow:#fff] ui:dark:[--dot-field-glow:#0a0a0a]',
|
||||
className
|
||||
)}
|
||||
style={glowColor != null ? `--dot-field-glow: ${glowColor}` : undefined}
|
||||
>
|
||||
<canvas bind:this={canvas} class="absolute inset-0 h-full w-full"></canvas>
|
||||
<svg class="pointer-events-none absolute inset-0 h-full w-full" aria-hidden="true">
|
||||
<canvas bind:this={canvas} class="ui:absolute ui:inset-0 ui:h-full ui:w-full"></canvas>
|
||||
<svg class="ui:pointer-events-none ui:absolute ui:inset-0 ui:h-full ui:w-full" aria-hidden="true">
|
||||
<defs>
|
||||
<radialGradient id={glowId}>
|
||||
<stop offset="0%" stop-color="var(--dot-field-glow, #fff)" />
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class={cn('pointer-events-none absolute inset-0 h-full w-full', className)}
|
||||
class={cn('ui:pointer-events-none ui:absolute ui:inset-0 ui:h-full ui:w-full', className)}
|
||||
{...restProps}
|
||||
fill={fillColor}
|
||||
>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
let {
|
||||
class: className = 'drop-shadow-sm',
|
||||
class: className = 'ui:drop-shadow-sm',
|
||||
containerClass = '',
|
||||
words = 'Gradual Spacing',
|
||||
duration = 0.5,
|
||||
@@ -30,7 +30,7 @@
|
||||
const wordsspilit = $derived(words.split(''));
|
||||
</script>
|
||||
|
||||
<div class={cn('flex justify-center space-x-1', containerClass)}>
|
||||
<div class={cn('ui:flex ui:justify-center ui:space-x-1', containerClass)}>
|
||||
<AnimatePresence let:_item list={[{ key: wordsspilit }]}>
|
||||
{#each wordsspilit as char, i}
|
||||
<Motion
|
||||
|
||||
@@ -89,34 +89,34 @@
|
||||
const selectedAnimation = $derived(animationVariants[animationStyle]);
|
||||
</script>
|
||||
|
||||
<div class="relative">
|
||||
<div class="ui:relative">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<div class="relative cursor-pointer" onclick={openVideo} role="presentation">
|
||||
<div class="ui:relative ui:cursor-pointer" onclick={openVideo} role="presentation">
|
||||
<img
|
||||
src={thumbnailSrc}
|
||||
alt={thumbnailAlt}
|
||||
width={1920}
|
||||
height={1080}
|
||||
class="w-full rounded-2xl transition-all duration-200"
|
||||
class="ui:w-full ui:rounded-2xl ui:transition-all ui:duration-200"
|
||||
/>
|
||||
<div class="absolute inset-0 flex items-center justify-center">
|
||||
<div class="ui:absolute ui:inset-0 ui:flex ui:items-center ui:justify-center">
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="flex size-24 items-center justify-center rounded-full border border-neutral-400 bg-black backdrop-blur-md transition-transform duration-300 ease-out"
|
||||
class="ui:flex ui:size-24 ui:items-center ui:justify-center ui:rounded-full ui:border ui:border-neutral-400 ui:bg-black ui:backdrop-blur-md ui:transition-transform ui:duration-300 ui:ease-out"
|
||||
onmouseenter={() => (isPlayHovered = true)}
|
||||
onmouseleave={() => (isPlayHovered = false)}
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
class="relative flex size-20 items-center justify-center rounded-full border border-neutral-400 bg-black backdrop-blur-2xl transition-all duration-300 ease-out"
|
||||
class="ui:relative ui:flex ui:size-20 ui:items-center ui:justify-center ui:rounded-full ui:border ui:border-neutral-400 ui:bg-black ui:backdrop-blur-2xl ui:transition-all ui:duration-300 ui:ease-out"
|
||||
>
|
||||
<span
|
||||
class="inline-flex size-8 items-center justify-center transition-transform duration-300 ease-out"
|
||||
class="ui:inline-flex ui:size-8 ui:items-center ui:justify-center ui:transition-transform ui:duration-300 ui:ease-out"
|
||||
style:transform={`scale(${isPlayHovered ? 1.1 : 1})`}
|
||||
style:color={iconColor}
|
||||
>
|
||||
<Play strokeWidth={1.7} class="size-full" />
|
||||
<Play strokeWidth={1.7} class="ui:size-full" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -126,7 +126,10 @@
|
||||
<AnimatePresence let:item list={[{ key: isVideoOpen }]}>
|
||||
{#if item.key}
|
||||
<Motion initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} let:motion>
|
||||
<div use:motion class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-md">
|
||||
<div
|
||||
use:motion
|
||||
class="ui:fixed ui:inset-0 ui:z-50 ui:flex ui:items-center ui:justify-center ui:bg-black/50 ui:backdrop-blur-md"
|
||||
>
|
||||
<Motion
|
||||
initial={selectedAnimation.initial}
|
||||
animate={selectedAnimation.animate}
|
||||
@@ -134,28 +137,28 @@
|
||||
transition={{ type: 'spring', damping: 30, stiffness: 300 }}
|
||||
let:motion
|
||||
>
|
||||
<div use:motion class="relative mx-4 aspect-video w-full max-w-4xl md:mx-0">
|
||||
<div use:motion class="ui:relative ui:mx-4 ui:w-full ui:max-w-4xl ui:md:mx-0 aspect-video">
|
||||
<Motion let:motion whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.95 }}>
|
||||
<button
|
||||
type="button"
|
||||
use:motion
|
||||
class="absolute -top-16 right-0 rounded-full bg-neutral-900/50 p-2 text-xl text-white ring-1 backdrop-blur-md"
|
||||
class="ui:absolute ui:-top-16 ui:right-0 ui:rounded-full ui:bg-neutral-900/50 ui:p-2 ui:text-xl ui:text-white ui:ring-1 ui:backdrop-blur-md"
|
||||
onclick={closeVideo}
|
||||
onmouseenter={() => (isCloseHovered = true)}
|
||||
onmouseleave={() => (isCloseHovered = false)}
|
||||
>
|
||||
<X class="size-5 text-white" />
|
||||
<X class="ui:size-5 ui:text-white" />
|
||||
</button>
|
||||
</Motion>
|
||||
<Motion animate={{ scale: isCloseHovered ? 0.98 : 1 }} transition={{ duration: 0.2 }} let:motion>
|
||||
<div
|
||||
use:motion
|
||||
class="relative isolate z-1 size-full overflow-hidden rounded-2xl border-2 border-white"
|
||||
class="ui:relative ui:z-1 ui:size-full ui:overflow-hidden ui:rounded-2xl ui:border-2 ui:border-white isolate"
|
||||
>
|
||||
<!-- svelte-ignore a11y_missing_attribute -->
|
||||
<iframe
|
||||
src={videoSrc}
|
||||
class="size-full rounded-2xl"
|
||||
class="ui:size-full ui:rounded-2xl"
|
||||
allowfullscreen
|
||||
title="Video"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
|
||||
@@ -50,16 +50,16 @@
|
||||
onmousemove={handleMouseMove}
|
||||
onmouseleave={handleMouseLeave}
|
||||
class={cn(
|
||||
'group relative flex size-full justify-center overflow-hidden rounded-xl border bg-neutral-100 py-4 text-black dark:bg-neutral-900 dark:text-white',
|
||||
'group ui:relative ui:flex ui:size-full ui:justify-center ui:overflow-hidden ui:rounded-xl ui:border ui:bg-neutral-100 ui:py-4 ui:text-black ui:dark:bg-neutral-900 ui:dark:text-white',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="relative z-10">
|
||||
<div class="ui:relative ui:z-10">
|
||||
{#if children}
|
||||
{@render children()}
|
||||
{:else}
|
||||
<div class="flex h-full items-center justify-center text-center">
|
||||
<p class="text-2xl">Magic Card</p>
|
||||
<div class="ui:flex ui:h-full ui:items-center ui:justify-center ui:text-center">
|
||||
<p class="ui:text-2xl">Magic Card</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -72,7 +72,7 @@
|
||||
>
|
||||
<div
|
||||
use:motion
|
||||
class="pointer-events-none absolute -inset-px rounded-xl opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
||||
class="ui:pointer-events-none ui:absolute ui:-inset-px ui:rounded-xl ui:opacity-0 ui:transition-opacity ui:duration-300 ui:group-hover:opacity-100"
|
||||
></div>
|
||||
</Motion>
|
||||
</div>
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
<p
|
||||
style:--shimmer-width="{shimmerWidth}px"
|
||||
class={cn(
|
||||
'mx-auto max-w-md text-neutral-600/50 dark:text-neutral-400/50',
|
||||
'animate-shimmer bg-size-[var(--shimmer-width)_100%] bg-clip-text bg-position-[0_0] bg-no-repeat [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',
|
||||
'bg-linear-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80',
|
||||
'ui:mx-auto ui:max-w-md ui:text-neutral-600/50 ui:dark:text-neutral-400/50',
|
||||
'ui:animate-shimmer ui:bg-size-[var(--shimmer-width)_100%] ui:bg-clip-text ui:bg-position-[0_0] ui:bg-no-repeat [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]',
|
||||
'ui:bg-linear-to-r ui:from-transparent ui:via-black/80 ui:via-50% ui:to-transparent ui:dark:via-white/80',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
transition={{ duration: 0.8, repeat: Infinity, delay }}
|
||||
let:motion
|
||||
>
|
||||
<div {id} use:motion class="pointer-events-none absolute z-20" style:left={x} style:top={y}>
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" class="block">
|
||||
<div {id} use:motion class="ui:pointer-events-none ui:absolute ui:z-20" style:left={x} style:top={y}>
|
||||
<svg width="21" height="21" viewBox="0 0 21 21" class="ui:block">
|
||||
<path
|
||||
d="M9.82531 0.843845C10.0553 0.215178 10.9446 0.215178 11.1746 0.843845L11.8618 2.72026C12.4006 4.19229 12.3916 6.39157 13.5 7.5C14.6084 8.60843 16.8077 8.59935 18.2797 9.13822L20.1561 9.82534C20.7858 10.0553 20.7858 10.9447 20.1561 11.1747L18.2797 11.8618C16.8077 12.4007 14.6084 12.3916 13.5 13.5C12.3916 14.6084 12.4006 16.8077 11.8618 18.2798L11.1746 20.1562C10.9446 20.7858 10.0553 20.7858 9.82531 20.1562L9.13819 18.2798C8.59932 16.8077 8.60843 14.6084 7.5 13.5C6.39157 12.3916 4.19225 12.4007 2.72023 11.8618L0.843814 11.1747C0.215148 10.9447 0.215148 10.0553 0.843814 9.82534L2.72023 9.13822C4.19225 8.59935 6.39157 8.60843 7.5 7.5C8.60843 6.39157 8.59932 4.19229 9.13819 2.72026L9.82531 0.843845Z"
|
||||
fill={color}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
</script>
|
||||
|
||||
<div style:--sparkles-first-color="{colors.first};" style:--sparkles-second-color="{colors.second};" class={className}>
|
||||
<span class="relative inline-block">
|
||||
<span class="ui:relative ui:inline-block">
|
||||
{#each sparkles as item}
|
||||
<SparkleParticle {...item} />
|
||||
{/each}
|
||||
|
||||
@@ -10,6 +10,6 @@
|
||||
let { children, className }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class={cn('text-left', className)}>
|
||||
<div class={cn('ui:text-left', className)}>
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
stroke-width="1"
|
||||
stroke-linecap="round"
|
||||
stroke="currentColor"
|
||||
class={cn('absolute text-black dark:text-white', className)}
|
||||
class={cn('ui:absolute ui:text-black ui:dark:text-white', className)}
|
||||
{...rest}
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m6-6H6" />
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors hover:ui:bg-muted"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors ui:hover:bg-muted"
|
||||
aria-label="Zoom out"
|
||||
onclick={handleZoomOut}
|
||||
>
|
||||
@@ -120,7 +120,7 @@
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors hover:ui:bg-muted"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors ui:hover:bg-muted"
|
||||
aria-label="Zoom in"
|
||||
onclick={handleZoomIn}
|
||||
>
|
||||
@@ -128,7 +128,7 @@
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors hover:ui:bg-muted"
|
||||
class="ui:flex ui:size-7 ui:items-center ui:justify-center ui:rounded-full ui:transition-colors ui:hover:bg-muted"
|
||||
aria-label="Fit to viewport"
|
||||
onclick={handleFit}
|
||||
>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
/>
|
||||
</Field.Field>
|
||||
{#if footer}
|
||||
<div class="-mt-1 min-w-0 px-4 pb-3">
|
||||
<div class="ui:-mt-1 ui:min-w-0 ui:px-4 ui:pb-3">
|
||||
{@render footer()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
width={size}
|
||||
height={size}
|
||||
viewBox={`0 0 ${size} ${size}`}
|
||||
class={cn('shrink-0 -rotate-90', className)}
|
||||
class={cn('ui:-rotate-90 ui:shrink-0', className)}
|
||||
aria-valuenow={clampedValue}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
@@ -48,7 +48,7 @@
|
||||
r={radius}
|
||||
fill="none"
|
||||
stroke-width={strokeWidth}
|
||||
class={cn('transition-all', trackClass)}
|
||||
class={cn('ui:transition-all', trackClass)}
|
||||
/>
|
||||
<circle
|
||||
cx={center}
|
||||
@@ -59,6 +59,6 @@
|
||||
stroke-dasharray={circumference}
|
||||
stroke-dashoffset={offset}
|
||||
stroke-linecap="round"
|
||||
class={cn('transition-all duration-300', progressClass)}
|
||||
class={cn('ui:transition-all ui:duration-300', progressClass)}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
{...props}
|
||||
data-code-overflow
|
||||
data-collapsed={collapsed}
|
||||
class={cn('ui:relative ui:overflow-y-hidden ui:data-[collapsed=true]:ui:max-h-[300px]', className)}
|
||||
class={cn('ui:relative ui:overflow-y-hidden ui:data-[collapsed=true]:max-h-[300px]', className)}
|
||||
>
|
||||
{@render children?.()}
|
||||
{#if collapsed}
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
href={fileUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="ui:border-input ui:bg-background ui:text-foreground hover:ui:bg-accent hover:ui:text-accent-foreground ui:shrink ui:inline-flex ui:items-center ui:gap-1.5 ui:rounded-md ui:border ui:px-3 ui:py-1.5 ui:text-sm ui:font-medium"
|
||||
class="ui:border-input ui:bg-background ui:text-foreground ui:hover:bg-accent ui:hover:text-accent-foreground ui:shrink ui:inline-flex ui:items-center ui:gap-1.5 ui:rounded-md ui:border ui:px-3 ui:py-1.5 ui:text-sm ui:font-medium"
|
||||
>
|
||||
{viewLabel}
|
||||
</a>
|
||||
@@ -72,7 +72,7 @@
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
download={title}
|
||||
class="ui:border-input ui:bg-background ui:text-foreground hover:ui:bg-accent hover:ui:text-accent-foreground ui:shrink ui:inline-flex ui:items-center ui:gap-1.5 ui:rounded-md ui:border ui:px-3 ui:py-1.5 ui:text-sm ui:font-medium"
|
||||
class="ui:border-input ui:bg-background ui:text-foreground ui:hover:bg-accent ui:hover:text-accent-foreground ui:shrink ui:inline-flex ui:items-center ui:gap-1.5 ui:rounded-md ui:border ui:px-3 ui:py-1.5 ui:text-sm ui:font-medium"
|
||||
>
|
||||
<DownloadIcon class="ui:size-4" />
|
||||
{downloadLabel}
|
||||
|
||||
@@ -71,6 +71,6 @@
|
||||
</Button>
|
||||
</div>
|
||||
<pre bind:this={preRef} draggable={false}>
|
||||
<NodeViewContent as="code" class={`language-${defaultLanguage} ui:pt-3!`} {...node.attrs} />
|
||||
<NodeViewContent as="code" class={`language- ${defaultLanguage} ui:pt-3!`} {...node.attrs} />
|
||||
</pre>
|
||||
</NodeViewWrapper>
|
||||
|
||||
@@ -142,10 +142,10 @@
|
||||
id="resizable-container-media"
|
||||
class={cn(
|
||||
'ui:relative ui:my-2 ui:flex ui:flex-col ui:rounded-md ui:border-2 ui:border-transparent',
|
||||
selected ? 'border-muted-foreground' : '',
|
||||
node.attrs.align === 'left' && 'left-0 -translate-x-0',
|
||||
node.attrs.align === 'center' && 'left-1/2 -translate-x-1/2',
|
||||
node.attrs.align === 'right' && 'left-full -translate-x-full'
|
||||
selected ? 'ui:border-muted-foreground' : '',
|
||||
node.attrs.align === 'ui:left' && 'ui:left-0 ui:-translate-x-0',
|
||||
node.attrs.align === 'center' && 'ui:left-1/2 ui:-translate-x-1/2',
|
||||
node.attrs.align === 'ui:right' && 'ui:left-full ui:-translate-x-full'
|
||||
)}
|
||||
style={`width: ${node.attrs.width}`}
|
||||
>
|
||||
@@ -191,13 +191,13 @@
|
||||
<div
|
||||
class={cn(
|
||||
'ui:bg-background/50 ui:absolute ui:-top-2 ui:left-[calc(50%-3rem)] ui:flex ui:items-center ui:gap-1 ui:rounded ui:border ui:p-1 ui:opacity-0 ui:backdrop-blur-sm ui:transition-opacity',
|
||||
!resizing && 'group-hover:opacity-100',
|
||||
openedMore && 'opacity-100'
|
||||
!resizing && 'ui:group-hover:opacity-100',
|
||||
openedMore && 'ui:opacity-100'
|
||||
)}
|
||||
>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'left' && 'bg-muted')}
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'ui:left' && 'ui:bg-muted')}
|
||||
onclick={() => updateAttributes({ align: 'left' })}
|
||||
title="Align Left"
|
||||
>
|
||||
@@ -205,7 +205,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'center' && 'bg-muted')}
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'center' && 'ui:bg-muted')}
|
||||
onclick={() => updateAttributes({ align: 'center' })}
|
||||
title="Align Center"
|
||||
>
|
||||
@@ -213,7 +213,7 @@
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'right' && 'bg-muted')}
|
||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'ui:right' && 'ui:bg-muted')}
|
||||
onclick={() => updateAttributes({ align: 'right' })}
|
||||
title="Align Right"
|
||||
>
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
<Button
|
||||
id={`${groupIndex}-${commandIndex}`}
|
||||
variant="ghost"
|
||||
class={cn('ui:h-8 ui:w-full ui:justify-start ui:gap-2 ui:rounded-sm', isActive && 'bg-muted')}
|
||||
class={cn('ui:h-8 ui:w-full ui:justify-start ui:gap-2 ui:rounded-sm', isActive && 'ui:bg-muted')}
|
||||
onclick={() => selectItem(groupIndex, commandIndex)}
|
||||
>
|
||||
<Icon />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class={cn(command.isActive?.(editor) && 'bg-muted')}
|
||||
class={cn(command.isActive?.(editor) && 'ui:bg-muted')}
|
||||
onclick={() => command.onClick?.(editor)}
|
||||
disabled={command.clickable ? !command.clickable(editor) : false}
|
||||
>
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
`size-6 border-0 p-0 font-normal`,
|
||||
editor.isActive('textStyle', { color: color.value }) && 'border-2 font-extrabold',
|
||||
color.value === '' && 'border'
|
||||
editor.isActive('ui:textStyle', { color: color.value }) && 'ui:border-2 ui:font-extrabold',
|
||||
color.value === '' && 'ui:border'
|
||||
)}
|
||||
style={`color: ${color.value}; background-color: ${color.value}50; border-color: ${color.value};`}
|
||||
title={color.label}
|
||||
@@ -79,8 +79,8 @@
|
||||
variant="ghost"
|
||||
class={cn(
|
||||
`size-6 border-0 p-0 font-normal`,
|
||||
editor.isActive('highlight', { color: color.value }) && 'border-2',
|
||||
color.value === '' && 'border'
|
||||
editor.isActive('highlight', { color: color.value }) && 'ui:border-2',
|
||||
color.value === '' && 'ui:border'
|
||||
)}
|
||||
style={`background-color: ${color.value}50; border-color: ${color.value};`}
|
||||
title={color.label}
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class={cn('ui:transition-transform', showMore && 'bg-muted rotate-90')}
|
||||
class={cn('ui:transition-transform', showMore && 'ui:bg-muted ui:rotate-90')}
|
||||
onclick={() => (showMore = !showMore)}
|
||||
title="Show More"
|
||||
>
|
||||
@@ -111,7 +111,7 @@
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
class={cn(caseSensitive && 'bg-muted')}
|
||||
class={cn(caseSensitive && 'ui:bg-muted')}
|
||||
onclick={() => {
|
||||
caseSensitive = !caseSensitive;
|
||||
updateSearchTerm();
|
||||
|
||||
@@ -105,7 +105,7 @@
|
||||
</script>
|
||||
|
||||
<div class="drag-handle">
|
||||
<Button variant="ghost" class="!ui:size-6 ui:rounded-sm ui:p-0" onclick={() => (open = true)}>
|
||||
<Button variant="ghost" class="ui:!size-6 ui:rounded-sm ui:p-0" onclick={() => (open = true)}>
|
||||
<GripVertical />
|
||||
</Button>
|
||||
<DropdownMenu.Root bind:open>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
}: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex justify-end gap-3">
|
||||
<div class="ui:flex ui:justify-end ui:gap-3">
|
||||
<Button type="button" variant="outline" size="sm" onclick={() => onPrevious()} disabled={!canGoBack || isSubmitting}>
|
||||
{previousLabel}
|
||||
</Button>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<div class="ui:flex ui:max-h-64 ui:flex-col ui:gap-0 ui:overflow-y-auto">
|
||||
{#each FILE_UPLOAD_SUPPORTED_TYPES as fileType (fileType.value)}
|
||||
<label
|
||||
class="ui:flex ui:cursor-pointer ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-2 ui:text-sm hover:ui:bg-accent hover:ui:text-accent-foreground {disabled
|
||||
class="ui:flex ui:cursor-pointer ui:select-none ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-2 ui:text-sm ui:hover:bg-accent ui:hover:text-accent-foreground {disabled
|
||||
? 'ui:pointer-events-none ui:opacity-50'
|
||||
: ''}"
|
||||
>
|
||||
|
||||
@@ -10,13 +10,13 @@ export function getOptionNumericId(option: { id?: number | string }): number | n
|
||||
|
||||
export function getMcqReviewRowBorderClass(isSelected: boolean, isCorrect: boolean): string {
|
||||
if (isSelected && isCorrect) {
|
||||
return 'ui:border-emerald-500 ui:bg-emerald-50/40 dark:ui:bg-emerald-950/30';
|
||||
return 'ui:border-emerald-500 ui:bg-emerald-50/40 ui:dark:bg-emerald-950/30';
|
||||
}
|
||||
if (isSelected && !isCorrect) {
|
||||
return 'ui:border-red-500 ui:bg-red-50/40 dark:ui:bg-red-950/30';
|
||||
return 'ui:border-red-500 ui:bg-red-50/40 ui:dark:bg-red-950/30';
|
||||
}
|
||||
if (!isSelected && isCorrect) {
|
||||
return 'ui:border-emerald-300 ui:bg-emerald-50/20 dark:ui:bg-emerald-950/20';
|
||||
return 'ui:border-emerald-300 ui:bg-emerald-50/20 ui:dark:bg-emerald-950/20';
|
||||
}
|
||||
return 'ui:border-muted';
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{#if onPick && !disabled}
|
||||
<button
|
||||
type="button"
|
||||
class="ui:rounded-md ui:p-1 ui:text-amber-600 ui:transition-colors ui:hover:bg-amber-500/10 focus-visible:ui:ring-ring ui:outline-none focus-visible:ui:ring-2 ui:dark:text-amber-400"
|
||||
class="ui:rounded-md ui:p-1 ui:text-amber-600 ui:transition-colors ui:hover:bg-amber-500/10 ui:focus-visible:ring-ring ui:outline-none ui:focus-visible:ring-2 ui:dark:text-amber-400"
|
||||
aria-label={starLabel(starIndex)}
|
||||
aria-pressed={value >= starIndex}
|
||||
{disabled}
|
||||
|
||||
+2
-2
@@ -20,7 +20,7 @@
|
||||
<ToggleGroup.Root type="single" value={studentValue} disabled={true} variant="outline">
|
||||
<ToggleGroup.Item
|
||||
value="true"
|
||||
class={correctIsTrue ? 'ui:border-emerald-500 ui:bg-emerald-50/40 dark:ui:bg-emerald-950/30' : ''}
|
||||
class={correctIsTrue ? 'ui:border-emerald-500 ui:bg-emerald-50/40 ui:dark:bg-emerald-950/30' : ''}
|
||||
>
|
||||
{label('true_false.true_label')}
|
||||
<OptionCorrectnessBadges
|
||||
@@ -31,7 +31,7 @@
|
||||
</ToggleGroup.Item>
|
||||
<ToggleGroup.Item
|
||||
value="false"
|
||||
class={!correctIsTrue ? 'ui:border-emerald-500 ui:bg-emerald-50/40 dark:ui:bg-emerald-950/30' : ''}
|
||||
class={!correctIsTrue ? 'ui:border-emerald-500 ui:bg-emerald-50/40 ui:dark:bg-emerald-950/30' : ''}
|
||||
>
|
||||
{label('true_false.false_label')}
|
||||
<OptionCorrectnessBadges
|
||||
|
||||
@@ -93,7 +93,7 @@
|
||||
<p class="ui:text-muted-foreground ui:text-xs">{label('word_bank.edit.correct_answers_heading')}</p>
|
||||
<div class="ui:flex ui:flex-col ui:gap-2">
|
||||
{#each Array.from({ length: blankCount }, (_, i) => i) as blankIndex (blankIndex)}
|
||||
<div class="ui:flex ui:flex-col ui:gap-1 sm:ui:flex-row sm:ui:items-center sm:ui:gap-3">
|
||||
<div class="ui:flex ui:flex-col ui:gap-1 ui:sm:flex-row ui:sm:items-center ui:sm:gap-3">
|
||||
<span class="ui:text-muted-foreground ui:min-w-18 ui:text-sm"
|
||||
>{label('word_bank.edit.blank_label')} {blankIndex + 1}</span
|
||||
>
|
||||
|
||||
@@ -166,7 +166,7 @@
|
||||
{@const text = chipId ? (chipById.get(chipId)?.label ?? '') : ''}
|
||||
<button
|
||||
type="button"
|
||||
class="ui:inline-flex ui:align-middle ui:mx-0.5 ui:min-h-8 ui:min-w-[5rem] ui:max-w-[16rem] ui:items-center ui:justify-center ui:rounded ui:border-2 ui:px-2 ui:py-0.5 ui:text-left ui:text-sm ui:transition-all ui:duration-200 ui:border-primary disabled:ui:cursor-not-allowed"
|
||||
class="ui:inline-flex ui:align-middle ui:mx-0.5 ui:min-h-8 ui:min-w-[5rem] ui:max-w-[16rem] ui:items-center ui:justify-center ui:rounded ui:border-2 ui:px-2 ui:py-0.5 ui:text-left ui:text-sm ui:transition-all ui:duration-200 ui:border-primary ui:disabled:cursor-not-allowed"
|
||||
class:ui:border-dashed={!text}
|
||||
class:ui:bg-background={!text}
|
||||
class:ui:border-solid={!!text}
|
||||
@@ -195,7 +195,7 @@
|
||||
<button
|
||||
type="button"
|
||||
draggable={!disabled && !placed}
|
||||
class="ui:rounded ui:border-2 ui:border-primary ui:bg-primary ui:px-3 ui:py-1.5 ui:text-sm ui:text-primary-foreground ui:transition-opacity disabled:ui:cursor-not-allowed"
|
||||
class="ui:rounded ui:border-2 ui:border-primary ui:bg-primary ui:px-3 ui:py-1.5 ui:text-sm ui:text-primary-foreground ui:transition-opacity ui:disabled:cursor-not-allowed"
|
||||
class:ui:opacity-40={placed}
|
||||
class:ui:pointer-events-none={placed}
|
||||
{disabled}
|
||||
|
||||
@@ -30,13 +30,7 @@
|
||||
<div class="ui:flex ui:flex-wrap ui:gap-1.5">
|
||||
{#each section.questions as question (question.key)}
|
||||
<span
|
||||
class={`ui:inline-flex ui:h-7 ui:min-w-7 ui:items-center ui:justify-center ui:rounded ui:border ui:px-1.5 ui:text-xs ui:font-medium ${
|
||||
question.isCurrent
|
||||
? 'ui:border-primary ui:bg-primary ui:text-primary-foreground'
|
||||
: question.isAnswered
|
||||
? 'ui:border-primary/40 ui:bg-primary/10 ui:text-primary'
|
||||
: 'ui:border-border ui:text-muted-foreground'
|
||||
}`}
|
||||
class={`ui:inline-flex ui:h-7 ui:min-w-7 ui:items-center ui:justify-center ui:rounded ui:border ui:px-1.5 ui:text-xs ui:font-medium ${question.isCurrent ? 'ui:border-primary ui:bg-primary ui:text-primary-foreground' : question.isAnswered ? 'ui:border-primary/40 ui:bg-primary/10 ui:text-primary' : 'ui:border-border ui:text-muted-foreground'}`}
|
||||
>
|
||||
{question.label}
|
||||
</span>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</p>
|
||||
<span class={`ui:mt-8 ui:h-1 ui:w-14 ui:rounded-full ${ACCENT_CLASSES[colorTheme]}`}></span>
|
||||
|
||||
<h2 class="ui:mt-10 ui:font-serif ui:text-5xl ui:font-bold ui:tracking-tight ui:text-foreground md:ui:text-6xl">
|
||||
<h2 class="ui:mt-10 ui:font-serif ui:text-5xl ui:font-bold ui:tracking-tight ui:text-foreground ui:md:text-6xl">
|
||||
{sectionTitle}
|
||||
</h2>
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
{@render children()}
|
||||
{:else}
|
||||
<HoverableItem
|
||||
class="ui:hover:bg-accent/25 ui:flex ui:h-48 ui:flex-col ui:place-items-center ui:justify-center ui:gap-2 ui:rounded-lg ui:border ui:border-dashed ui:p-6 ui:transition-all ui:group-aria-disabled/file-drop-zone-trigger:ui:opacity-50 ui:hover:cursor-pointer ui:group-aria-disabled/file-drop-zone-trigger:ui:hover:cursor-not-allowed"
|
||||
class="ui:hover:bg-accent/25 ui:flex ui:h-48 ui:flex-col ui:place-items-center ui:justify-center ui:gap-2 ui:rounded-lg ui:border ui:border-dashed ui:p-6 ui:transition-all ui:group-aria-disabled/file-drop-zone-trigger:opacity-50 ui:hover:cursor-pointer ui:group-aria-disabled/file-drop-zone-trigger:hover:cursor-not-allowed"
|
||||
>
|
||||
{#snippet children(isHovered)}
|
||||
<div class="ui:flex ui:flex-col ui:place-items-center ui:justify-center ui:gap-2">
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
{...rest}
|
||||
showCloseButton={false}
|
||||
class={cn(
|
||||
'ui:min-h-96 ui:max-w-[60%] ui:rounded-none ui:border-x-0 ui:sm:ui:max-w-lg ui:sm:ui:rounded-lg ui:sm:ui:border-x',
|
||||
'ui:min-h-96 ui:max-w-[60%] ui:rounded-none ui:border-x-0 ui:sm:max-w-lg ui:sm:rounded-lg ui:sm:border-x',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
|
||||
<Field.Field class={className}>
|
||||
{#if label}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="ui:flex ui:items-center ui:justify-between">
|
||||
<Field.Label for={name || 'input-field'} class={labelClassName}>
|
||||
{label}
|
||||
{#if isRequired}
|
||||
|
||||
@@ -75,7 +75,7 @@
|
||||
aria-selected={index === selectedIndex}
|
||||
class={cn(
|
||||
'ui:flex ui:w-full ui:items-center ui:gap-2 ui:rounded-sm ui:px-2 ui:py-1.5 ui:text-left ui:text-sm ui:transition-colors',
|
||||
index === selectedIndex ? 'ui:bg-accent ui:text-accent-foreground' : 'hover:ui:bg-accent/50'
|
||||
index === selectedIndex ? 'ui:bg-accent ui:text-accent-foreground' : 'ui:hover:bg-accent/50'
|
||||
)}
|
||||
onmousedown={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -73,37 +73,37 @@
|
||||
<Popover.Root bind:open>
|
||||
<Popover.Trigger
|
||||
{disabled}
|
||||
class="ui:text-muted-foreground hover:ui:bg-muted ui:hover:text-foreground flex shrink-0 items-center gap-1 rounded-md px-2 py-1 text-xs transition-colors disabled:pointer-events-none disabled:opacity-40"
|
||||
class="ui:text-muted-foreground ui:hover:bg-muted ui:hover:text-foreground ui:flex ui:shrink-0 ui:items-center ui:gap-1 ui:rounded-md ui:px-2 ui:py-1 ui:text-xs ui:transition-colors ui:disabled:pointer-events-none ui:disabled:opacity-40"
|
||||
>
|
||||
<span class="truncate">{AGENT_MODELS[value].label}</span>
|
||||
{#if AGENT_MODELS[value].costTier === 'high'}
|
||||
<span class="ui:text-red-500 font-medium">$$$</span>
|
||||
<span class="ui:text-red-500 ui:font-medium">$$$</span>
|
||||
{:else}
|
||||
<span class="ui:text-green-500 font-medium">$</span>
|
||||
<span class="ui:text-green-500 ui:font-medium">$</span>
|
||||
{/if}
|
||||
<ChevronDownIcon size={12} />
|
||||
</Popover.Trigger>
|
||||
|
||||
<Popover.Content class="ui:p-0! ui:z-[110] w-48" align="end" side="top">
|
||||
<div class="py-1">
|
||||
<Popover.Content class="ui:p-0! ui:z-[110] ui:w-48" align="end" side="top">
|
||||
<div class="ui:py-1">
|
||||
{#each UI_PICKER_MODEL_IDS as id (id)}
|
||||
{@const isSelected = id === value}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => handleSelect(id)}
|
||||
class="hover:ui:bg-muted flex w-full items-center justify-between gap-2 px-3 py-2 text-left text-xs transition-colors"
|
||||
class="ui:hover:bg-muted ui:flex ui:w-full ui:items-center ui:justify-between ui:gap-2 ui:px-3 ui:py-2 ui:text-left ui:text-xs ui:transition-colors"
|
||||
>
|
||||
<span class="flex min-w-0 items-center gap-2">
|
||||
<span class="min-w-0 truncate">{AGENT_MODELS[id].label}</span>
|
||||
<span class="ui:flex ui:min-w-0 ui:items-center ui:gap-2">
|
||||
<span class="ui:min-w-0 truncate">{AGENT_MODELS[id].label}</span>
|
||||
{#if isLocked(id)}
|
||||
<RocketIcon size={16} class="ui:text-primary! ui:shrink-0" />
|
||||
{/if}
|
||||
</span>
|
||||
<span class="flex shrink-0 items-center gap-1">
|
||||
<span class="ui:flex ui:shrink-0 ui:items-center ui:gap-1">
|
||||
{#if AGENT_MODELS[id].costTier === 'high'}
|
||||
<span class="ui:text-red-500 font-medium">$$$</span>
|
||||
<span class="ui:text-red-500 ui:font-medium">$$$</span>
|
||||
{:else}
|
||||
<span class="ui:text-green-500 font-medium">$</span>
|
||||
<span class="ui:text-green-500 ui:font-medium">$</span>
|
||||
{/if}
|
||||
{#if isSelected}
|
||||
<CheckIcon size={14} class="ui:text-primary" />
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
class:animate={isHovered}
|
||||
>
|
||||
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H19a1 1 0 0 1 1 1v18a1 1 0 0 1-1 1H6.5a1 1 0 0 1 0-5H20" />
|
||||
<path d="M8 11h8" class="ui:text-line ui:text-line-1" />
|
||||
<path d="M8 7h6" class="ui:text-line ui:text-line-2" />
|
||||
<path d="M8 11h8" class="text-line text-line-1" />
|
||||
<path d="M8 7h6" class="text-line text-line-2" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -102,14 +102,10 @@
|
||||
<EditableLandingSection sectionKey={block.id}>
|
||||
<article
|
||||
id={block.id}
|
||||
class={`ui:border ui:border-[var(--landing-border)] ${
|
||||
variant === 'terminal' ? 'ui:bg-[var(--landing-card)]' : ''
|
||||
}`}
|
||||
class={`ui:border ui:border-[var(--landing-border)] ${variant === 'terminal' ? 'ui:bg-[var(--landing-card)]' : ''}`}
|
||||
>
|
||||
<div
|
||||
class={`ui:flex ui:items-center ui:justify-between ui:px-4 ui:py-2 ui:border-b ui:border-[var(--landing-border)] ${
|
||||
variant === 'terminal' ? 'ui:bg-[var(--landing-bg-section)]' : ''
|
||||
}`}
|
||||
class={`ui:flex ui:items-center ui:justify-between ui:px-4 ui:py-2 ui:border-b ui:border-[var(--landing-border)] ${variant === 'terminal' ? 'ui:bg-[var(--landing-bg-section)]' : ''}`}
|
||||
>
|
||||
<span class="ui:text-xs ui:font-mono ui:text-[var(--landing-fg-muted)]">
|
||||
// {block.monoLabel}
|
||||
|
||||
@@ -36,7 +36,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:text-[var(--landing-fg-muted)] ui:hover:text-[var(--landing-fg)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-fg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-fg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-3 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:p-1.5 ui:rounded-md ui:text-[var(--landing-fg-muted)] ui:hover:text-[var(--landing-fg)] ui:transition-colors ui:no-underline',
|
||||
@@ -64,7 +64,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:text-[var(--landing-bg)]/75 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-4 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:size-8 ui:border ui:border-[var(--landing-bg)]/15 ui:bg-[var(--landing-bg)]/5 ui:text-[var(--landing-bg)]/75 ui:hover:text-[var(--landing-bg)] ui:hover:bg-[var(--landing-bg)]/10 ui:transition-colors ui:no-underline',
|
||||
@@ -92,7 +92,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-[13px] ui:text-[var(--landing-fg)] ui:hover:text-[var(--landing-fg-muted)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-medium ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-medium ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-3 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:size-7 ui:rounded-md ui:text-[var(--landing-fg-muted)] ui:hover:text-[var(--landing-fg)] ui:hover:bg-[var(--landing-card-soft)] ui:transition-colors ui:no-underline',
|
||||
@@ -119,7 +119,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-[13.5px] ui:text-[#9da1ab] ui:hover:text-[#e9eaed] ui:transition-colors ui:break-words ui:py-1 ui:md:py-0 ui:no-underline',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-medium ui:text-[#e9eaed] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-medium ui:text-[#e9eaed] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-3 ui:mt-4 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:text-[#9da1ab] ui:hover:text-[#e9eaed] ui:transition-colors ui:no-underline',
|
||||
@@ -174,7 +174,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:text-[var(--landing-fg-muted)] ui:hover:text-[var(--landing-fg)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-4 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:size-8 ui:rounded-full ui:border ui:border-[var(--landing-border)] ui:bg-[var(--landing-card-soft)]/40 ui:text-[var(--landing-fg-muted)] ui:hover:text-[var(--landing-fg)] ui:hover:bg-[var(--landing-card-soft)] ui:transition-colors ui:no-underline',
|
||||
@@ -203,7 +203,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-lg ui:font-medium ui:text-[var(--landing-fg)] ui:hover:text-[var(--landing-accent)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-1 ui:no-underline',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-medium ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-medium ui:text-[var(--landing-accent)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-3 ui:mt-4 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:size-9 ui:rounded-md ui:border ui:border-[var(--landing-border)] ui:bg-[var(--landing-bg)] ui:text-[var(--landing-fg)] ui:hover:text-[var(--landing-accent)] ui:transition-colors ui:no-underline',
|
||||
@@ -230,7 +230,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:text-[#2c2b29] ui:hover:text-[#1a1a1a] ui:transition-colors ui:break-words ui:py-1 ui:md:py-0 ui:no-underline',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-medium ui:text-[#c25237] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-medium ui:text-[#c25237] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-3 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-3 ui:mt-4 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:text-[#76746c] ui:hover:text-[#1a1a1a] ui:transition-colors ui:no-underline',
|
||||
@@ -257,7 +257,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:font-medium ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-bold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-bold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-3 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:p-1.5 ui:rounded-md ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:no-underline',
|
||||
@@ -284,7 +284,7 @@ export function getFooterTokens(variant: OrgLandingPageTheme): FooterRegionToken
|
||||
columnLink:
|
||||
'ui:block ui:text-sm ui:font-medium ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:break-words ui:py-1.5 ui:md:py-0',
|
||||
columnCta:
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline hover:ui:underline',
|
||||
'ui:text-sm ui:font-semibold ui:text-[var(--landing-bg)] ui:inline-flex ui:items-center ui:gap-1.5 ui:mt-4 ui:no-underline ui:hover:underline',
|
||||
socialWrap: 'ui:flex ui:flex-wrap ui:gap-2 ui:mt-3 ui:list-none ui:m-0 ui:p-0',
|
||||
socialIcon:
|
||||
'ui:inline-flex ui:items-center ui:justify-center ui:p-1.5 ui:rounded-md ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:no-underline',
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
const clampedValue = $derived(Math.max(0, Math.min(100, value)));
|
||||
const labelPercentage = $derived(Math.round(clampedValue));
|
||||
const strokeDashoffset = $derived(RING_CIRCUMFERENCE * (1 - clampedValue / 100));
|
||||
const sizeClass = $derived(size === 'small' ? 'size-10' : 'size-14');
|
||||
const labelClass = $derived(size === 'small' ? 'text-[10px]' : 'text-sm');
|
||||
const sizeClass = $derived(size === 'small' ? 'ui:size-10' : 'ui:size-14');
|
||||
const labelClass = $derived(size === 'small' ? 'ui:text-[10px]' : 'ui:text-sm');
|
||||
</script>
|
||||
|
||||
<div class="relative shrink-0">
|
||||
<div class="ui:relative ui:shrink-0">
|
||||
<svg
|
||||
class="{sizeClass} -rotate-90"
|
||||
class="{sizeClass} ui:-rotate-90"
|
||||
style="transform-box: fill-box; transform-origin: center;"
|
||||
viewBox="0 0 100 100"
|
||||
aria-hidden="true"
|
||||
@@ -31,7 +31,7 @@
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="8"
|
||||
class="text-gray-200 dark:text-gray-700"
|
||||
class="ui:text-gray-200 ui:dark:text-gray-700"
|
||||
/>
|
||||
<circle
|
||||
cx="50"
|
||||
@@ -41,12 +41,12 @@
|
||||
stroke="currentColor"
|
||||
stroke-width="8"
|
||||
stroke-linecap="round"
|
||||
class="text-green-600 transition-[stroke-dashoffset] duration-700 ease-out"
|
||||
class="ui:text-green-600 ui:transition-[stroke-dashoffset] ui:duration-700 ui:ease-out"
|
||||
stroke-dasharray={RING_CIRCUMFERENCE}
|
||||
stroke-dashoffset={strokeDashoffset}
|
||||
/>
|
||||
</svg>
|
||||
<div class="absolute inset-0 flex flex-col items-center justify-center">
|
||||
<div class="ui:absolute ui:inset-0 ui:flex ui:flex-col ui:items-center ui:justify-center">
|
||||
<p class={labelClass}>{labelPercentage}%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -126,7 +126,7 @@
|
||||
{lesson.title}
|
||||
</h1>
|
||||
|
||||
<div class="prose sm:prose-sm ui:mt-8 ui:max-w-none ui:dark:text-white">
|
||||
<div class="prose ui:sm:prose-sm ui:mt-8 ui:max-w-none ui:dark:text-white">
|
||||
{#if lesson.body}
|
||||
<SafeHtmlContent content={lesson.body} />
|
||||
{:else}
|
||||
|
||||
@@ -121,7 +121,7 @@
|
||||
isActive
|
||||
? 'ui:border-l-foreground ui:bg-secondary ui:text-secondary-foreground'
|
||||
: 'ui:border-l-transparent ui:bg-transparent ui:text-foreground',
|
||||
!isActive && 'hover:ui:bg-muted/40'
|
||||
!isActive && 'ui:hover:bg-muted/40'
|
||||
)}
|
||||
aria-current={isActive ? 'true' : undefined}
|
||||
onclick={() => selectItem(item.key)}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
<div
|
||||
class={cn(
|
||||
'group ui:inline-flex ui:w-full ui:items-center',
|
||||
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
||||
disabled ? 'ui:cursor-not-allowed' : 'ui:cursor-pointer',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { tv, type VariantProps } from 'tailwind-variants';
|
||||
|
||||
export const resourceListRowVariants = tv({
|
||||
base: 'ui:flex-nowrap! ui:rounded-none ui:border-0 ui:border-b ui:border-border last:ui:border-b-0 ui:hover:bg-muted/50',
|
||||
base: 'ui:flex-nowrap! ui:rounded-none ui:border-0 ui:border-b ui:border-border ui:last:border-b-0 ui:hover:bg-muted/50',
|
||||
variants: {
|
||||
align: {
|
||||
center: 'ui:items-center',
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
|
||||
<Field.Field class={className}>
|
||||
{#if label}
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="ui:flex ui:items-center ui:justify-between">
|
||||
<Field.Label class={labelClassName}>
|
||||
{label}
|
||||
{#if isRequired}
|
||||
|
||||
@@ -30,10 +30,10 @@
|
||||
bind:ref
|
||||
data-slot="underline-tabs-trigger"
|
||||
class={cn(
|
||||
"dark:data-[state=active]:text-foreground data-[state=active]:text-foreground ui:text-foreground ui:z-10 ui:relative ui:inline-flex ui:h-[calc(100%-3px)] ui:flex-1 ui:items-center ui:justify-center ui:gap-1.5 ui:whitespace-nowrap ui:px-3 ui:py-1 ui:text-sm ui:transition-colors ui:focus:outline-none ui:disabled:pointer-events-none ui:disabled:opacity-50 ui:cursor-pointer [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"ui:dark:data-[state=active]:text-foreground ui:data-[state=active]:text-foreground ui:text-foreground ui:z-10 ui:relative ui:inline-flex ui:h-[calc(100%-3px)] ui:flex-1 ui:items-center ui:justify-center ui:gap-1.5 ui:whitespace-nowrap ui:px-3 ui:py-1 ui:text-sm ui:transition-colors ui:focus:outline-none ui:disabled:pointer-events-none ui:disabled:opacity-50 ui:cursor-pointer ui:[&_svg]:pointer-events-none ui:[&_svg]:shrink-0 ui:[&_svg:not([class*='size-'])]:size-4",
|
||||
state.rootState.isHovered &&
|
||||
state.rootState.hoveredTab === value &&
|
||||
'data-[state=inactive]:text-primary-foreground ui:text-primary',
|
||||
'ui:data-[state=inactive]:text-primary-foreground ui:text-primary',
|
||||
className
|
||||
)}
|
||||
{...state.props}
|
||||
@@ -44,7 +44,7 @@
|
||||
{#if state.rootState.hoveredTab === value}
|
||||
<div
|
||||
class={cn(
|
||||
'ui:bg-primary-foreground ui:z-1 ui:absolute ui:top-0 ui:h-[calc(100%-3px)] ui:w-full ui:rounded-md ui:opacity-0 ui:transition-opacity ui:duration-300 ui:pointer-events-none ui:peer-focus-visible:ui:opacity-100',
|
||||
'ui:bg-primary-foreground ui:z-1 ui:absolute ui:top-0 ui:h-[calc(100%-3px)] ui:w-full ui:rounded-md ui:opacity-0 ui:transition-opacity ui:duration-300 ui:pointer-events-none ui:peer-focus-visible:opacity-100',
|
||||
state.rootState.isHovered && 'ui:opacity-100'
|
||||
)}
|
||||
in:receive={{ key: `${state.rootState.opts.id.current}-tab-hover`, duration: 300 }}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
iconClass?: string;
|
||||
}
|
||||
|
||||
let { src, alt = 'User', class: className = 'size-6', iconClass = '' }: Props = $props();
|
||||
let { src, alt = 'User', class: className = 'ui:size-6', iconClass = '' }: Props = $props();
|
||||
</script>
|
||||
|
||||
<Avatar.Root class={className}>
|
||||
@@ -16,6 +16,6 @@
|
||||
<Avatar.Image {src} {alt} />
|
||||
{/if}
|
||||
<Avatar.Fallback>
|
||||
<img src="/images/avatar.svg" alt="User avatar" class="size-10 rounded-md {iconClass}" />
|
||||
<img src="/images/avatar.svg" alt="User avatar" class="ui:size-10 ui:rounded-md {iconClass}" />
|
||||
</Avatar.Fallback>
|
||||
</Avatar.Root>
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user