mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-10-15 02:15:01 +03:00
fix(sponsor): keep long sponsor copy inside every slot
The Sponsors page card sized its body to max-content because the title never wrapped, so a long title spilled out of the card onto the next one. The compact card (sidebar, login) put logo, copy and close button in one row, leaving the copy ~110px of the 220px sidebar and clamping it to two lines; the dashboard banner cut its title to a single line. Titles and descriptions now wrap, breaking unbroken strings too. The compact card stacks logo and tag above copy that spans the full card width, keeping a bounded height in the sidebar with a 2-line title and 4-line description clamp and the full copy in the link tooltip.
This commit is contained in:
@@ -61,7 +61,7 @@
|
||||
|
||||
.sponsor-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -77,15 +77,14 @@
|
||||
}
|
||||
|
||||
.sponsor-title {
|
||||
overflow: hidden;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.sponsor-text {
|
||||
font-size: 13px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.sponsor-visit {
|
||||
@@ -129,42 +128,52 @@
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-main {
|
||||
gap: 10px;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: 4px 8px;
|
||||
padding: 8px 10px;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-logo {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
.sponsor-card-compact .sponsor-body,
|
||||
.sponsor-card-compact .sponsor-head {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 2px;
|
||||
.sponsor-card-compact .sponsor-logo {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-tag {
|
||||
justify-self: start;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-title,
|
||||
.sponsor-card-compact .sponsor-text {
|
||||
display: -webkit-box;
|
||||
grid-column: 1 / -1;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-title {
|
||||
display: -webkit-box;
|
||||
max-width: 100%;
|
||||
font-size: 13px;
|
||||
white-space: normal;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-text {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
font-size: 12px;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
}
|
||||
|
||||
.sponsor-card-compact .sponsor-close {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
inset-inline-end: 4px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin: 4px 4px 0 0;
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,34 @@ const sponsor: Sponsor = {
|
||||
link: 'https://acme.example/?utm_source=3x-ui',
|
||||
};
|
||||
|
||||
// Copy as long as the real sponsors.json entries; each story renders it at its slot's width.
|
||||
const longSponsor: Sponsor = {
|
||||
...sponsor,
|
||||
title: { en: 'Advertise here — reach 3X-UI server admins' },
|
||||
text: {
|
||||
en: 'Put your VPS, hosting or domain brand in front of the admins who use 3X-UI every day. Message us for slots and pricing.',
|
||||
},
|
||||
};
|
||||
|
||||
function part(root: HTMLElement, selector: string): HTMLElement {
|
||||
const el = root.querySelector<HTMLElement>(selector);
|
||||
if (!el) throw new Error(`missing ${selector}`);
|
||||
return el;
|
||||
}
|
||||
|
||||
async function expectInsideCard(root: HTMLElement, selector: string) {
|
||||
const card = part(root, '.sponsor-card').getBoundingClientRect();
|
||||
const box = part(root, selector).getBoundingClientRect();
|
||||
await expect(box.left).toBeGreaterThanOrEqual(card.left - 0.5);
|
||||
await expect(box.right).toBeLessThanOrEqual(card.right + 0.5);
|
||||
}
|
||||
|
||||
async function expectNotClipped(root: HTMLElement, selector: string) {
|
||||
const el = part(root, selector);
|
||||
await expect(el.scrollWidth).toBeLessThanOrEqual(el.clientWidth + 1);
|
||||
await expect(el.scrollHeight).toBeLessThanOrEqual(el.clientHeight + 1);
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Sponsor/SponsorCard',
|
||||
component: SponsorCard,
|
||||
@@ -68,6 +96,62 @@ export const Card: Story = {
|
||||
),
|
||||
};
|
||||
|
||||
export const BannerLongCopy: Story = {
|
||||
args: { variant: 'banner', sponsor: longSponsor, onClose: () => {} },
|
||||
render: (args) => (
|
||||
<div style={{ width: 360 }}>
|
||||
<SponsorCard {...args} />
|
||||
</div>
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectNotClipped(canvasElement, '.sponsor-title');
|
||||
},
|
||||
};
|
||||
|
||||
export const SidebarLongCopy: Story = {
|
||||
args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} },
|
||||
render: (args) => (
|
||||
<div style={{ width: 204 }}>
|
||||
<SponsorCard {...args} />
|
||||
</div>
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectNotClipped(canvasElement, '.sponsor-title');
|
||||
await expectInsideCard(canvasElement, '.sponsor-text');
|
||||
await expect(part(canvasElement, '.sponsor-main')).toHaveAttribute(
|
||||
'title',
|
||||
`${longSponsor.title.en}\n${longSponsor.text.en}`,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const LoginLongCopy: Story = {
|
||||
args: { variant: 'compact', sponsor: longSponsor, onClose: () => {} },
|
||||
render: (args) => (
|
||||
<div style={{ width: 334 }}>
|
||||
<SponsorCard {...args} />
|
||||
</div>
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectNotClipped(canvasElement, '.sponsor-title');
|
||||
await expectNotClipped(canvasElement, '.sponsor-text');
|
||||
},
|
||||
};
|
||||
|
||||
export const PageLongCopy: Story = {
|
||||
args: { variant: 'card', sponsor: longSponsor },
|
||||
render: (args) => (
|
||||
<div style={{ width: 320 }}>
|
||||
<SponsorCard {...args} />
|
||||
</div>
|
||||
),
|
||||
play: async ({ canvasElement }) => {
|
||||
await expectInsideCard(canvasElement, '.sponsor-title');
|
||||
await expectInsideCard(canvasElement, '.sponsor-text');
|
||||
await expectNotClipped(canvasElement, '.sponsor-title');
|
||||
},
|
||||
};
|
||||
|
||||
export const IconOnly: Story = {
|
||||
args: { iconOnly: true },
|
||||
};
|
||||
|
||||
@@ -70,6 +70,7 @@ export default function SponsorCard({
|
||||
href={sponsor.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer sponsored"
|
||||
title={variant === 'compact' ? [title, text].filter(Boolean).join('\n') : undefined}
|
||||
>
|
||||
<SponsorLogo sponsor={sponsor} />
|
||||
<span className="sponsor-body" dir="auto">
|
||||
|
||||
Reference in New Issue
Block a user