fix: ui prefix breaking sidebar
This commit is contained in:
@@ -173,6 +173,7 @@ export class NewsfeedApi extends BaseApiWithErrors {
|
|||||||
ZNewsfeedUpdate.safeParse({ ...fields, content: '' }).error!,
|
ZNewsfeedUpdate.safeParse({ ...fields, content: '' }).error!,
|
||||||
'newsfeed'
|
'newsfeed'
|
||||||
);
|
);
|
||||||
|
snackbar.error(this.errors.content ?? 'Failed to update post');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
|
|
||||||
import type { Feed } from '$features/course/utils/types';
|
import type { Feed } from '$features/course/utils/types';
|
||||||
import { newsfeedApi } from '$features/course/api';
|
import { newsfeedApi } from '$features/course/api';
|
||||||
@@ -50,9 +50,10 @@
|
|||||||
|
|
||||||
let isDeleteFeedModal = $state(false);
|
let isDeleteFeedModal = $state(false);
|
||||||
|
|
||||||
const openEditFeed = () => {
|
const openEditFeed = async () => {
|
||||||
editFeed = feed;
|
editFeed = feed;
|
||||||
edit = true;
|
edit = true;
|
||||||
|
await tick();
|
||||||
newsfeedApi.openNewFeedModal();
|
newsfeedApi.openNewFeedModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -17,21 +17,19 @@
|
|||||||
let { courseId = '', edit = $bindable(false), editFeed = $bindable() }: Props = $props();
|
let { courseId = '', edit = $bindable(false), editFeed = $bindable() }: Props = $props();
|
||||||
|
|
||||||
let isLoading = $state(false);
|
let isLoading = $state(false);
|
||||||
let errors: Record<string, string> = $state({
|
|
||||||
newPost: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
let newPost = $state('');
|
let newPost = $state('');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!newsfeedApi.isNewFeedModalOpen) {
|
if (!newsfeedApi.isNewFeedModalOpen) {
|
||||||
newPost = '';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edit && editFeed) {
|
if (edit && editFeed) {
|
||||||
newPost = editFeed.content || '';
|
newPost = editFeed.content || '';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newPost = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const onPost = async () => {
|
const onPost = async () => {
|
||||||
@@ -43,6 +41,10 @@
|
|||||||
|
|
||||||
if (newsfeedApi.success) {
|
if (newsfeedApi.success) {
|
||||||
resetEditor();
|
resetEditor();
|
||||||
|
} else if (newsfeedApi.errors.content) {
|
||||||
|
snackbar.error(newsfeedApi.errors.content);
|
||||||
|
} else {
|
||||||
|
snackbar.error('snackbar.newsfeed.error.editing');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await newsfeedApi.create(courseId, {
|
await newsfeedApi.create(courseId, {
|
||||||
@@ -101,8 +103,8 @@
|
|||||||
placeholder={$t('course.navItem.news_feed.heading_button.placeholder')}
|
placeholder={$t('course.navItem.news_feed.heading_button.placeholder')}
|
||||||
/>
|
/>
|
||||||
{/key}
|
{/key}
|
||||||
{#if errors.newPost}
|
{#if newsfeedApi.errors.content}
|
||||||
<p class="text-sm text-red-500">{errors.newPost}</p>
|
<p class="ui:text-destructive text-sm">{newsfeedApi.errors.content}</p>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
<Dialog.Footer>
|
<Dialog.Footer>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount } from 'svelte';
|
import { onMount, tick } from 'svelte';
|
||||||
import type { ProgramNewsfeedItem } from '$features/program/utils/types';
|
import type { ProgramNewsfeedItem } from '$features/program/utils/types';
|
||||||
import { programNewsfeedApi, type ProgramNewsfeedCommentsByFeedId } from '$features/program/api';
|
import { programNewsfeedApi, type ProgramNewsfeedCommentsByFeedId } from '$features/program/api';
|
||||||
import {
|
import {
|
||||||
@@ -59,10 +59,11 @@
|
|||||||
const reactionCounts = $derived(getNewsfeedReactionCounts(feed.reaction));
|
const reactionCounts = $derived(getNewsfeedReactionCounts(feed.reaction));
|
||||||
const selectedReactionType = $derived(getSelectedNewsfeedReactionType(feed.reaction, reactionAuthorId));
|
const selectedReactionType = $derived(getSelectedNewsfeedReactionType(feed.reaction, reactionAuthorId));
|
||||||
|
|
||||||
const openEditFeed = () => {
|
const openEditFeed = async () => {
|
||||||
programNewsfeedApi.openNewFeedModal();
|
|
||||||
edit = true;
|
|
||||||
editFeed = feed;
|
editFeed = feed;
|
||||||
|
edit = true;
|
||||||
|
await tick();
|
||||||
|
programNewsfeedApi.openNewFeedModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleReact = (reactionType: string) => addNewReaction(reactionType, feed.id, reactionAuthorId);
|
const handleReact = (reactionType: string) => addNewReaction(reactionType, feed.id, reactionAuthorId);
|
||||||
|
|||||||
@@ -21,13 +21,15 @@
|
|||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!programNewsfeedApi.isNewFeedModalOpen) {
|
if (!programNewsfeedApi.isNewFeedModalOpen) {
|
||||||
newPost = '';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edit && editFeed) {
|
if (edit && editFeed) {
|
||||||
newPost = editFeed.content || '';
|
newPost = editFeed.content || '';
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newPost = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetEditor = () => {
|
const resetEditor = () => {
|
||||||
|
|||||||
@@ -28,6 +28,33 @@ const REPO_ROOT = path.join(__dirname, '../..');
|
|||||||
const UI_SRC_DIR = path.join(__dirname, 'src');
|
const UI_SRC_DIR = path.join(__dirname, 'src');
|
||||||
const UI_SOURCE_EXTENSIONS = new Set(['.svelte', '.ts', '.tsx', '.js', '.jsx']);
|
const UI_SOURCE_EXTENSIONS = new Set(['.svelte', '.ts', '.tsx', '.js', '.jsx']);
|
||||||
|
|
||||||
|
// Variant/prop literals — never Tailwind class strings (e.g. side === 'left', variant: 'outline')
|
||||||
|
const SEMANTIC_LITERALS = new Set([
|
||||||
|
'left',
|
||||||
|
'right',
|
||||||
|
'top',
|
||||||
|
'bottom',
|
||||||
|
'center',
|
||||||
|
'start',
|
||||||
|
'end',
|
||||||
|
'outline',
|
||||||
|
'ghost',
|
||||||
|
'default',
|
||||||
|
'destructive',
|
||||||
|
'secondary',
|
||||||
|
'link',
|
||||||
|
'sm',
|
||||||
|
'md',
|
||||||
|
'lg',
|
||||||
|
'xl',
|
||||||
|
'2xl',
|
||||||
|
'icon',
|
||||||
|
'dot',
|
||||||
|
'line',
|
||||||
|
'page',
|
||||||
|
'none'
|
||||||
|
]);
|
||||||
|
|
||||||
// Collapse duplicate ui: markers (e.g. ui:hover:ui:bg-accent → ui:hover:bg-accent)
|
// Collapse duplicate ui: markers (e.g. ui:hover:ui:bg-accent → ui:hover:bg-accent)
|
||||||
function normalizeUiClass(className) {
|
function normalizeUiClass(className) {
|
||||||
if (!className.includes(UI_PREFIX)) {
|
if (!className.includes(UI_PREFIX)) {
|
||||||
@@ -38,8 +65,36 @@ function normalizeUiClass(className) {
|
|||||||
return `${UI_PREFIX}${stripped}`;
|
return `${UI_PREFIX}${stripped}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldProcessAsClassString(value) {
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SEMANTIC_LITERALS.has(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokens = value.split(/\s+/).filter(Boolean);
|
||||||
|
if (tokens.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tokens.some((token) => token.startsWith(UI_PREFIX))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tailwindTokenCount = tokens.filter((token) => isTailwindClass(token)).length;
|
||||||
|
|
||||||
|
// Avoid treating prose in // comments (e.g. dialog's … sticky footer) as class strings.
|
||||||
|
return tailwindTokenCount >= Math.max(1, Math.ceil(tokens.length * 0.5));
|
||||||
|
}
|
||||||
|
|
||||||
// Check if a class looks like a Tailwind utility class
|
// Check if a class looks like a Tailwind utility class
|
||||||
function isTailwindClass(className) {
|
function isTailwindClass(className) {
|
||||||
|
if (SEMANTIC_LITERALS.has(className)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip if already prefixed (including malformed mid-chain ui: after a variant)
|
// Skip if already prefixed (including malformed mid-chain ui: after a variant)
|
||||||
if (className.startsWith(UI_PREFIX) || className.includes(':ui:')) {
|
if (className.startsWith(UI_PREFIX) || className.includes(':ui:')) {
|
||||||
return false;
|
return false;
|
||||||
@@ -156,8 +211,28 @@ function processClassString(classString, scopedClasses = new Set()) {
|
|||||||
return processClassTokens(classString, scopedClasses);
|
return processClassTokens(classString, scopedClasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function repairCommentSwallowedClassStrings(content) {
|
||||||
|
// cn(..., // comment.'ui:classes', ...) — class string got glued into a line comment
|
||||||
|
return content.replace(
|
||||||
|
/^(\s*\/\/[^\n]*?)\.(['"]ui:[^'"]+['"],?\s*)$/gm,
|
||||||
|
(_, commentLine, classLiteral) => `${commentLine}.\n${classLiteral}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function repairCorruptedQuotedPrefixes(content) {
|
function repairCorruptedQuotedPrefixes(content) {
|
||||||
return content.replace(/ui:'([^']*)'/g, "'ui:$1'").replace(/ui:"([^"]*)"/g, '"ui:$1"');
|
let repaired = content.replace(/ui:'([^']*)'/g, "'ui:$1'").replace(/ui:"([^"]*)"/g, '"ui:$1"');
|
||||||
|
|
||||||
|
for (const literal of SEMANTIC_LITERALS) {
|
||||||
|
const escaped = literal.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
repaired = repaired.replace(new RegExp(`===\\s*['"]${UI_PREFIX}${escaped}['"]`, 'g'), `=== '${literal}'`);
|
||||||
|
repaired = repaired.replace(new RegExp(`!==\\s*['"]${UI_PREFIX}${escaped}['"]`, 'g'), `!== '${literal}'`);
|
||||||
|
repaired = repaired.replace(
|
||||||
|
new RegExp(`(variant|side|align|size|indicator|density)\\s*:\\s*['"]${UI_PREFIX}${escaped}['"]`, 'g'),
|
||||||
|
`$1: '${literal}'`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return repaired;
|
||||||
}
|
}
|
||||||
|
|
||||||
function processInterpolatedSegments(value, scopedClasses, expressionPattern) {
|
function processInterpolatedSegments(value, scopedClasses, expressionPattern) {
|
||||||
@@ -191,11 +266,19 @@ function processQuotedClassStrings(content, scopedClasses = new Set()) {
|
|||||||
let processed = content;
|
let processed = content;
|
||||||
|
|
||||||
processed = processed.replace(/'([^']*)'/g, (match, quoted) => {
|
processed = processed.replace(/'([^']*)'/g, (match, quoted) => {
|
||||||
|
if (!shouldProcessAsClassString(quoted)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
const result = processClassString(quoted, scopedClasses);
|
const result = processClassString(quoted, scopedClasses);
|
||||||
return result !== quoted ? `'${result}'` : match;
|
return result !== quoted ? `'${result}'` : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
processed = processed.replace(/"([^"]*)"/g, (match, quoted) => {
|
processed = processed.replace(/"([^"]*)"/g, (match, quoted) => {
|
||||||
|
if (!shouldProcessAsClassString(quoted)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
const result = processClassString(quoted, scopedClasses);
|
const result = processClassString(quoted, scopedClasses);
|
||||||
return result !== quoted ? `"${result}"` : match;
|
return result !== quoted ? `"${result}"` : match;
|
||||||
});
|
});
|
||||||
@@ -203,9 +286,22 @@ function processQuotedClassStrings(content, scopedClasses = new Set()) {
|
|||||||
return processed;
|
return processed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function processMultilineCnArgs(args, scopedClasses = new Set()) {
|
||||||
|
return args
|
||||||
|
.split('\n')
|
||||||
|
.map((line) => {
|
||||||
|
if (line.trimStart().startsWith('//')) {
|
||||||
|
return line;
|
||||||
|
}
|
||||||
|
|
||||||
|
return processQuotedClassStrings(line, scopedClasses);
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
// Process file content
|
// Process file content
|
||||||
function processFileContent(content, scopedClasses = new Set()) {
|
function processFileContent(content, scopedClasses = new Set()) {
|
||||||
let modified = repairCorruptedQuotedPrefixes(content);
|
let modified = repairCommentSwallowedClassStrings(repairCorruptedQuotedPrefixes(content));
|
||||||
let changes = modified !== content ? 1 : 0;
|
let changes = modified !== content ? 1 : 0;
|
||||||
|
|
||||||
// Helper to process and count changes
|
// Helper to process and count changes
|
||||||
@@ -312,18 +408,30 @@ function processFileContent(content, scopedClasses = new Set()) {
|
|||||||
|
|
||||||
// Process double-quoted strings
|
// Process double-quoted strings
|
||||||
processedObj = processedObj.replace(/"([^"]*)"/g, (match, value) => {
|
processedObj = processedObj.replace(/"([^"]*)"/g, (match, value) => {
|
||||||
|
if (!shouldProcessAsClassString(value)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
const processed = processClassString(value, scopedClasses);
|
const processed = processClassString(value, scopedClasses);
|
||||||
return processed !== value ? `"${processed}"` : match;
|
return processed !== value ? `"${processed}"` : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process single-quoted strings
|
// Process single-quoted strings
|
||||||
processedObj = processedObj.replace(/'([^']*)'/g, (match, value) => {
|
processedObj = processedObj.replace(/'([^']*)'/g, (match, value) => {
|
||||||
|
if (!shouldProcessAsClassString(value)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
const processed = processClassString(value, scopedClasses);
|
const processed = processClassString(value, scopedClasses);
|
||||||
return processed !== value ? `'${processed}'` : match;
|
return processed !== value ? `'${processed}'` : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Process template literals
|
// Process template literals
|
||||||
processedObj = processedObj.replace(/`([^`]*)`/g, (match, value) => {
|
processedObj = processedObj.replace(/`([^`]*)`/g, (match, value) => {
|
||||||
|
if (!shouldProcessAsClassString(value)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
const processed = processClassString(value, scopedClasses);
|
const processed = processClassString(value, scopedClasses);
|
||||||
return processed !== value ? `\`${processed}\`` : match;
|
return processed !== value ? `\`${processed}\`` : match;
|
||||||
});
|
});
|
||||||
@@ -334,13 +442,13 @@ function processFileContent(content, scopedClasses = new Set()) {
|
|||||||
// Pattern 8: class: cn(...) patterns (like in derived props)
|
// Pattern 8: class: cn(...) patterns (like in derived props)
|
||||||
// Matches: class: cn('string1', 'string2', ...)
|
// Matches: class: cn('string1', 'string2', ...)
|
||||||
processAndReplace(/class:\s*cn\s*\(([^)]+)\)/g, (match, args) => {
|
processAndReplace(/class:\s*cn\s*\(([^)]+)\)/g, (match, args) => {
|
||||||
const processedArgs = processQuotedClassStrings(args, scopedClasses);
|
const processedArgs = processMultilineCnArgs(args, scopedClasses);
|
||||||
return processedArgs !== args ? `class: cn(${processedArgs})` : match;
|
return processedArgs !== args ? `class: cn(${processedArgs})` : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Pattern 8b: class={cn(...)} in Svelte markup (supports embedded quotes in class strings)
|
// Pattern 8b: class={cn(...)} in Svelte markup (supports embedded quotes in class strings)
|
||||||
processAndReplace(/class=\{cn\s*\(([\s\S]*?)\)\}/g, (match, args) => {
|
processAndReplace(/class=\{cn\s*\(([\s\S]*?)\)\}/g, (match, args) => {
|
||||||
const processedArgs = processQuotedClassStrings(args, scopedClasses);
|
const processedArgs = processMultilineCnArgs(args, scopedClasses);
|
||||||
return processedArgs !== args ? `class={cn(${processedArgs})}` : match;
|
return processedArgs !== args ? `class={cn(${processedArgs})}` : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -470,6 +578,17 @@ const AUDIT_PATTERNS = [
|
|||||||
id: 'inverted-variant-prefix',
|
id: 'inverted-variant-prefix',
|
||||||
label: 'variant:ui:utility (ui: after variant instead of at start)',
|
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:/
|
regex: /(?:^|[\s"'`{])(?:hover|focus|active|disabled|dark|sm|md|lg|xl|2xl|group-hover|peer-focus):ui:/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'semantic-literal-prefix',
|
||||||
|
label: "Semantic prop/compare literals prefixed (e.g. === 'ui:left', side: 'ui:right')",
|
||||||
|
regex:
|
||||||
|
/===\s*['"]ui:(?:left|right|top|bottom|outline|ghost|default|center|start|end)['"]|(variant|side|align|size|indicator|density)\s*:\s*['"]ui:(?:left|right|top|bottom|outline|ghost|default)['"]/
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'comment-swallowed-class',
|
||||||
|
label: 'Class string glued into a // comment (dialog/chart cn() corruption)',
|
||||||
|
regex: /\/\/[^\n]*\.'ui:[^'\n]+'/
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,12 @@
|
|||||||
// of the other series, this overrides that
|
// 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',
|
'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
|
// a grid line there and rule ends up overlapping the marks because it is rendered after
|
||||||
// the marks
|
// 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',
|
'ui:[&_.lc-rule-x-line:not(.lc-grid-x-rule)]:stroke-0 ui:[&_.lc-rule-y-line:not(.lc-grid-y-rule)]:stroke-0',
|
||||||
|
|||||||
@@ -26,8 +26,10 @@
|
|||||||
data-slot="dialog-content"
|
data-slot="dialog-content"
|
||||||
class={cn(
|
class={cn(
|
||||||
// `has-[[data-slot=dialog-footer][data-sticky=true]]:pb-0` drops the
|
// `has-[[data-slot=dialog-footer][data-sticky=true]]:pb-0` drops the
|
||||||
// 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
|
// dialog's own bottom padding when a sticky footer is rendered —
|
||||||
// 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',
|
// 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',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...restProps}
|
{...restProps}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@
|
|||||||
data-active={isActive}
|
data-active={isActive}
|
||||||
class={cn(
|
class={cn(
|
||||||
buttonVariants({
|
buttonVariants({
|
||||||
variant: isActive ? 'ui:outline' : 'ghost',
|
variant: isActive ? 'outline' : 'ghost',
|
||||||
size
|
size
|
||||||
}),
|
}),
|
||||||
className
|
className
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
side: 'ui:right'
|
side: 'right'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
data-slot="sidebar-container"
|
data-slot="sidebar-container"
|
||||||
class={cn(
|
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',
|
'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 === 'ui:left'
|
side === 'left'
|
||||||
? 'ui:left-0 ui:group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
|
? '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)]',
|
: 'ui:right-0 ui:group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
|
||||||
// Adjust the padding for floating and inset variants.
|
// Adjust the padding for floating and inset variants.
|
||||||
|
|||||||
@@ -143,9 +143,9 @@
|
|||||||
class={cn(
|
class={cn(
|
||||||
'ui:relative ui:my-2 ui:flex ui:flex-col ui:rounded-md ui:border-2 ui:border-transparent',
|
'ui:relative ui:my-2 ui:flex ui:flex-col ui:rounded-md ui:border-2 ui:border-transparent',
|
||||||
selected ? 'ui:border-muted-foreground' : '',
|
selected ? 'ui:border-muted-foreground' : '',
|
||||||
node.attrs.align === 'ui:left' && 'ui:left-0 ui:-translate-x-0',
|
node.attrs.align === 'left' && 'ui:left-0 ui:-translate-x-0',
|
||||||
node.attrs.align === 'center' && 'ui:left-1/2 ui:-translate-x-1/2',
|
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'
|
node.attrs.align === 'right' && 'ui:left-full ui:-translate-x-full'
|
||||||
)}
|
)}
|
||||||
style={`width: ${node.attrs.width}`}
|
style={`width: ${node.attrs.width}`}
|
||||||
>
|
>
|
||||||
@@ -197,7 +197,7 @@
|
|||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'ui:left' && 'ui:bg-muted')}
|
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'left' && 'ui:bg-muted')}
|
||||||
onclick={() => updateAttributes({ align: 'left' })}
|
onclick={() => updateAttributes({ align: 'left' })}
|
||||||
title="Align Left"
|
title="Align Left"
|
||||||
>
|
>
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'ui:right' && 'ui:bg-muted')}
|
class={cn('ui:size-6 ui:p-0', node.attrs.align === 'right' && 'ui:bg-muted')}
|
||||||
onclick={() => updateAttributes({ align: 'right' })}
|
onclick={() => updateAttributes({ align: 'right' })}
|
||||||
title="Align Right"
|
title="Align Right"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user