Put component classes in a cascade layer so utilities can override them
This commit is contained in:
parent
298cab2e54
commit
36cb34613f
15 changed files with 57 additions and 53 deletions
|
|
@ -129,6 +129,7 @@ callbacks and hot paths.
|
||||||
- Never hard-code the app's name. The brand is a build-time `VITE_PLATFORM_*` variable, so user-facing copy interpolates `PLATFORM_NAME` from `@app/env`, and `PLATFORM_URL`, `PLATFORM_LOGO`, `PLATFORM_ABOUT` for the rest of it. Each is set by the deployment, so don't write a `"Flotilla"` fallback behind one either.
|
- Never hard-code the app's name. The brand is a build-time `VITE_PLATFORM_*` variable, so user-facing copy interpolates `PLATFORM_NAME` from `@app/env`, and `PLATFORM_URL`, `PLATFORM_LOGO`, `PLATFORM_ABOUT` for the rest of it. Each is set by the deployment, so don't write a `"Flotilla"` fallback behind one either.
|
||||||
- Svelte 5 runes (`$state`, `$derived`, `$effect`) only in UI components
|
- Svelte 5 runes (`$state`, `$derived`, `$effect`) only in UI components
|
||||||
- TailwindCSS styling with css components customized by theme. See lib/components for examples.
|
- TailwindCSS styling with css components customized by theme. See lib/components for examples.
|
||||||
|
- A component class goes in `@layer components`, in its own file under `lib/components`. An unlayered rule beats a layered one whatever the specificity, so a class outside the layer can never be overridden by a utility in markup. The third-party overrides in `base.css` stay unlayered. The library defaults they beat are unlayered too.
|
||||||
- Comments, naming, conditionals and single-use indirection are covered by the Cleanup Pass above.
|
- Comments, naming, conditionals and single-use indirection are covered by the Cleanup Pass above.
|
||||||
- Do not use `any`. If there are type errors related to `unknown`, they are likely because the upstream definition of the data is incorrect.
|
- Do not use `any`. If there are type errors related to `unknown`, they are likely because the upstream definition of the data is incorrect.
|
||||||
- When dynamically building classes, use `cx` from `classnames` rather than embedded ternaries or svelte 4's old `class:` syntax.
|
- When dynamically building classes, use `cx` from `classnames` rather than embedded ternaries or svelte 4's old `class:` syntax.
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isRoomOrRelay}
|
{#if isRoomOrRelay}
|
||||||
<ContentLinkUrl {url} class="link-content whitespace-nowrap" />
|
<ContentLinkUrl {url} class="link-content" />
|
||||||
{:else if isAudio}
|
{:else if isAudio}
|
||||||
<ContentLinkBlockAudio {url} {event} />
|
<ContentLinkBlockAudio {url} {event} />
|
||||||
{:else if isVideo}
|
{:else if isVideo}
|
||||||
|
|
@ -130,6 +130,6 @@
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
{:catch}
|
{:catch}
|
||||||
<ContentLinkUrl {url} class="link-content link-content--wrap" />
|
<ContentLinkUrl {url} class="link-content whitespace-normal wrap-anywhere" />
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if failed}
|
{#if failed}
|
||||||
<a href={url} class="link-content link-content--wrap">{displayUrl(url)}</a>
|
<a href={url} class="link-content whitespace-normal wrap-anywhere">{displayUrl(url)}</a>
|
||||||
{:else if source}
|
{:else if source}
|
||||||
<audio controls src={source} preload="metadata" class="my-2 w-full max-w-xl" onerror={onError}
|
<audio controls src={source} preload="metadata" class="my-2 w-full max-w-xl" onerror={onError}
|
||||||
></audio>
|
></audio>
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if hasError}
|
{#if hasError}
|
||||||
<a href={url} class="link-content link-content--wrap">
|
<a href={url} class="link-content whitespace-normal wrap-anywhere">
|
||||||
<Icon icon={LinkRound} size={3} class="inline-block" />
|
<Icon icon={LinkRound} size={3} class="inline-block" />
|
||||||
{displayUrl(url)}
|
{displayUrl(url)}
|
||||||
</a>
|
</a>
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
<!-- Use a real link so people can copy the href -->
|
<!-- Use a real link so people can copy the href -->
|
||||||
<a
|
<a
|
||||||
href={url}
|
href={url}
|
||||||
class="link-content link-content--wrap"
|
class="link-content whitespace-normal wrap-anywhere"
|
||||||
onclick={stopPropagation(preventDefault(expand))}>
|
onclick={stopPropagation(preventDefault(expand))}>
|
||||||
<Icon icon={LinkRound} size={3} class="inline-block" />
|
<Icon icon={LinkRound} size={3} class="inline-block" />
|
||||||
{displayUrl(url)}
|
{displayUrl(url)}
|
||||||
</a>
|
</a>
|
||||||
{:else}
|
{:else}
|
||||||
<ContentLinkUrl {url} class="link-content link-content--wrap" />
|
<ContentLinkUrl {url} class="link-content whitespace-normal wrap-anywhere" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@
|
||||||
<ModalTitle>Unable to Zap</ModalTitle>
|
<ModalTitle>Unable to Zap</ModalTitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<p>
|
<p>
|
||||||
Zapping <ProfileLink {pubkey} class="text-primary!" /> isn't possible right now because
|
Zapping <ProfileLink {pubkey} class="text-primary" /> isn't possible right now because
|
||||||
{#if $zapper}
|
{#if $zapper}
|
||||||
their zap receiver isn't correctly set up.
|
their zap receiver isn't correctly set up.
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@
|
||||||
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
const openProfile = () => pushModal(ProfileDetail, {pubkey, url})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button
|
<Button onclick={preventDefault(openProfile)} class={cx(props.class, {"link-content": !unstyled})}>
|
||||||
onclick={preventDefault(openProfile)}
|
|
||||||
class={cx(props.class, {"link-content bg-surface": !unstyled})}>
|
|
||||||
@<ProfileName {pubkey} {url} />
|
@<ProfileName {pubkey} {url} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,6 @@
|
||||||
const path = makeSpacePath(url, h)
|
const path = makeSpacePath(url, h)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Link href={path} class={cx(props.class, {"link-content bg-surface": !unstyled})}>
|
<Link href={path} class={cx(props.class, {"link-content": !unstyled})}>
|
||||||
#<RoomName {h} {url} />
|
#<RoomName {h} {url} />
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
<ModalHeader>
|
<ModalHeader>
|
||||||
<ModalTitle>Send a Zap</ModalTitle>
|
<ModalTitle>Send a Zap</ModalTitle>
|
||||||
<ModalSubtitle>To <ProfileLink {pubkey} class="text-primary!" /></ModalSubtitle>
|
<ModalSubtitle>To <ProfileLink {pubkey} class="text-primary" /></ModalSubtitle>
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
|
|
||||||
{#if invoice}
|
{#if invoice}
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Keyboard-open adjustments
|
Keyboard-open adjustments — unlayered on purpose. Every element carrying
|
||||||
|
.hide-on-keyboard also carries display utilities (flex, md:hidden), and a
|
||||||
|
utility beats @layer components whatever the specificity.
|
||||||
---------------------------------------------------------------------------- */
|
---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
body.keyboard-open {
|
body.keyboard-open {
|
||||||
|
|
@ -246,8 +248,14 @@ body.keyboard-open .room {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ----------------------------------------------------------------------------
|
/* ----------------------------------------------------------------------------
|
||||||
Integrations — rewired onto the shared tokens (unlayered so they win over
|
Integrations — third-party class names rewired onto the shared tokens.
|
||||||
third-party defaults)
|
|
||||||
|
These stay unlayered. Every library here ships its own stylesheet outside any
|
||||||
|
cascade layer, and an unlayered rule beats a layered one whatever the
|
||||||
|
specificity, so a layered override would lose to the default it overrides.
|
||||||
|
@welshman/editor sets `.tiptap {min-height: 0}`, which beats a layered
|
||||||
|
`.note-editor .tiptap`. Our own classes go in @layer components, in the file
|
||||||
|
that owns them.
|
||||||
---------------------------------------------------------------------------- */
|
---------------------------------------------------------------------------- */
|
||||||
|
|
||||||
/* tiptap editor */
|
/* tiptap editor */
|
||||||
|
|
@ -388,25 +396,6 @@ body.keyboard-open .room {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* link-content (tiptap pill) */
|
|
||||||
.link-content {
|
|
||||||
@apply bg-surface-more text-content max-w-full overflow-hidden px-1 text-ellipsis whitespace-nowrap;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link-content--wrap {
|
|
||||||
@apply whitespace-normal;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* welshman/content */
|
|
||||||
.welshman-content a {
|
|
||||||
@apply text-primary cursor-pointer underline underline-offset-2;
|
|
||||||
}
|
|
||||||
.welshman-content-error a {
|
|
||||||
@apply underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* date picker. @svelte-plugins/datepicker renders its own markup, so these class
|
/* date picker. @svelte-plugins/datepicker renders its own markup, so these class
|
||||||
names appear nowhere in src/ — don't delete them on a grep. */
|
names appear nowhere in src/ — don't delete them on a grep. */
|
||||||
.picker {
|
.picker {
|
||||||
|
|
@ -431,12 +420,6 @@ body.keyboard-open .room {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* tippy popover */
|
/* tippy popover */
|
||||||
.tippy-target {
|
|
||||||
@apply z-tooltip pointer-events-none fixed inset-0;
|
|
||||||
}
|
|
||||||
.tippy-target > * {
|
|
||||||
@apply pointer-events-auto;
|
|
||||||
}
|
|
||||||
.tippy-box {
|
.tippy-box {
|
||||||
@apply rounded-3xl;
|
@apply rounded-3xl;
|
||||||
box-shadow: var(--shadow-lg);
|
box-shadow: var(--shadow-lg);
|
||||||
|
|
@ -471,13 +454,3 @@ emoji-picker {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* content width for fixed elements (sidebar offsets) */
|
|
||||||
.left-content {
|
|
||||||
left: var(--sail);
|
|
||||||
}
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.left-content {
|
|
||||||
left: calc(18.5rem + var(--sail));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,22 @@
|
||||||
@layer components {
|
@layer components {
|
||||||
.link {
|
/* .welshman-content wraps rendered note content, whose links are raw anchors */
|
||||||
|
.link,
|
||||||
|
.welshman-content a {
|
||||||
@apply text-primary cursor-pointer underline underline-offset-2;
|
@apply text-primary cursor-pointer underline underline-offset-2;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply text-primary-bold;
|
@apply text-primary-bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* an error toast colors its own text, so the link only needs the underline */
|
||||||
|
.welshman-content-error a {
|
||||||
|
@apply underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* an inline pill for a link, mention or topic inside rendered content */
|
||||||
|
.link-content {
|
||||||
|
@apply bg-surface-more text-content max-w-full overflow-hidden px-1 text-ellipsis whitespace-nowrap;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,4 +10,13 @@
|
||||||
@apply p-2 sm:p-4;
|
@apply p-2 sm:p-4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* left edge of the content column, for fixed elements the page cannot position */
|
||||||
|
.left-content {
|
||||||
|
left: var(--sail);
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
left: calc(18.5rem + var(--sail));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,8 @@
|
||||||
.room-pins__message {
|
.room-pins__message {
|
||||||
@apply text-primary-content text-sm leading-relaxed;
|
@apply text-primary-content text-sm leading-relaxed;
|
||||||
|
|
||||||
:where(.link-content, a, p, span, code) {
|
/* a pill draws its own background, so it keeps its own color */
|
||||||
|
:where(a, p, span, code):not(.link-content) {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Design-system entry point (imported once, in src/routes/+layout.svelte).
|
Design-system entry point (imported once, in src/routes/+layout.svelte).
|
||||||
|
|
||||||
tailwindcss — framework + our @theme token registration (via base.css)
|
tailwindcss — framework + our @theme token registration (via base.css)
|
||||||
base.css — theme-independent structure (components, utilities, base)
|
base.css — theme-independent structure (base, utilities, integrations)
|
||||||
clay.css — clay theme token values ([data-fl-theme="clay"] + dark)
|
clay.css — clay theme token values ([data-fl-theme="clay"] + dark)
|
||||||
flat.css — flat theme token values ([data-fl-theme="flat"] + dark)
|
flat.css — flat theme token values ([data-fl-theme="flat"] + dark)
|
||||||
navy.css — navy theme token values ([data-fl-theme="navy"] + dark)
|
navy.css — navy theme token values ([data-fl-theme="navy"] + dark)
|
||||||
|
|
|
||||||
|
|
@ -41,4 +41,13 @@
|
||||||
.mobile .tip[data-tip]::before {
|
.mobile .tip[data-tip]::before {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the host tippy renders its popovers into; see getTippyTarget in lib/html.ts */
|
||||||
|
.tippy-target {
|
||||||
|
@apply z-tooltip pointer-events-none fixed inset-0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-target > * {
|
||||||
|
@apply pointer-events-auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue