From 4569bd5a1a72b8c574120a52330300bcd75ac1e1 Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Thu, 6 Aug 2026 17:20:13 +0200 Subject: [PATCH] Update jdd. Add a linktree-component --- package-lock.json | 14 ++-- package.json | 2 +- src/back/elements/button.css | 23 ++++++- src/back/elements/button.ts | 30 -------- src/back/elements/button.tsx | 43 ++++++++++++ src/back/jdd-components/linktree/linktree.css | 60 ++++++++++++++++ .../jdd-components/linktree/linktree.jdd.tsx | 69 +++++++++++++++++++ 7 files changed, 200 insertions(+), 41 deletions(-) delete mode 100644 src/back/elements/button.ts create mode 100644 src/back/elements/button.tsx create mode 100644 src/back/jdd-components/linktree/linktree.css create mode 100644 src/back/jdd-components/linktree/linktree.jdd.tsx diff --git a/package-lock.json b/package-lock.json index 8f60e76..87ee874 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@sealcode/add-to-head": "^1.0.0", "@sealcode/file-manager": "^1.0.2", "@sealcode/jdd": "^0.8.10", - "@sealcode/jdd-editor": "^0.2.30", + "@sealcode/jdd-editor": "^0.2.31", "@sealcode/monaco-wrapper": "^0.0.11", "@sealcode/sealgen": "^0.19.21", "@sealcode/show-first-row": "^0.1.0", @@ -1380,9 +1380,9 @@ } }, "node_modules/@sealcode/jdd-editor": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.30.tgz", - "integrity": "sha512-b+f5xR4FrGc8MLSDKB9AJhehQX8c9NLP7M+towfNUIQNVVVEbecKgJuidmGtSOwrPuahcjRxF8nZqXpFEGdBlQ==", + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.31.tgz", + "integrity": "sha512-mPxrh+YGN5q9zSSZU9fjp/M+kTnDb99MGkwEAlcB17dDPJr5enjQJHLMFanC+v/cV5+H7Qxfyj5Yi3+b7ICciQ==", "dependencies": { "@koa/router": "^13.1.0", "@sealcode/jdd": "^0.8.10", @@ -16016,9 +16016,9 @@ } }, "@sealcode/jdd-editor": { - "version": "0.2.30", - "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.30.tgz", - "integrity": "sha512-b+f5xR4FrGc8MLSDKB9AJhehQX8c9NLP7M+towfNUIQNVVVEbecKgJuidmGtSOwrPuahcjRxF8nZqXpFEGdBlQ==", + "version": "0.2.31", + "resolved": "https://registry.npmjs.org/@sealcode/jdd-editor/-/jdd-editor-0.2.31.tgz", + "integrity": "sha512-mPxrh+YGN5q9zSSZU9fjp/M+kTnDb99MGkwEAlcB17dDPJr5enjQJHLMFanC+v/cV5+H7Qxfyj5Yi3+b7ICciQ==", "requires": { "@koa/router": "^13.1.0", "@sealcode/jdd": "^0.8.10", diff --git a/package.json b/package.json index 7d91bfa..42ba951 100644 --- a/package.json +++ b/package.json @@ -98,7 +98,7 @@ "@sealcode/add-to-head": "^1.0.0", "@sealcode/file-manager": "^1.0.2", "@sealcode/jdd": "^0.8.10", - "@sealcode/jdd-editor": "^0.2.30", + "@sealcode/jdd-editor": "^0.2.31", "@sealcode/monaco-wrapper": "^0.0.11", "@sealcode/sealgen": "^0.19.21", "@sealcode/show-first-row": "^0.1.0", diff --git a/src/back/elements/button.css b/src/back/elements/button.css index fbbd070..f90f5cc 100644 --- a/src/back/elements/button.css +++ b/src/back/elements/button.css @@ -1,9 +1,15 @@ .button { + --icon-width: 32px; + --gap: 8px; padding: 8px 16px; - display: inline-block; + display: inline-flex; + flex-flow: row; + align-items: center; + gap: 8px; text-decoration: none; margin: 4px; cursor: pointer; + color: var(--button-fg-color); &:hover { filter: brightness(1.1); @@ -13,14 +19,25 @@ opacity: 0.5; pointer-events: none; } + + svg * { + fill: var(--button-fg-color) !important; + } +} + +.button--centered { + span { + text-align: center; + margin-right: calc(var(--gap) + var(--icon-width)); + } } .button--color-accent1 { background-color: var(--color-brand-accent); - color: var(--color-brand-text-on-accent); + --button-fg-color: var(--color-brand-text-on-accent); } .button--color-accent2 { background-color: var(--color-brand-accent2); - color: var(--color-brand-text-on-accent2); + --button-fg-color: var(--color-brand-text-on-accent2); } diff --git a/src/back/elements/button.ts b/src/back/elements/button.ts deleted file mode 100644 index 4b86591..0000000 --- a/src/back/elements/button.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const button_variants = ["accent1", "accent2"] as const; -type variant = (typeof button_variants)[number]; - -export function button({ - text, - href, - variant, - disabled = false, - submit = false, -}: { - text: string; - href: string; - variant: variant; - disabled?: boolean; - submit?: boolean; -}): JSX.Element { - const classstr = `class="button button--color-${variant}"`; - if (submit) { - return /* HTML */ ``; - } - return /* HTML */ ` - ${text} - `; -} diff --git a/src/back/elements/button.tsx b/src/back/elements/button.tsx new file mode 100644 index 0000000..6f57a52 --- /dev/null +++ b/src/back/elements/button.tsx @@ -0,0 +1,43 @@ +import { TempstreamJSX } from "tempstream"; +import { icon as render_icon } from "../icons.js"; + +export const button_variants = ["accent1", "accent2"] as const; +type variant = (typeof button_variants)[number]; + +export function button({ + text, + href, + variant, + disabled = false, + submit = false, + icon, + centered = false, +}: { + text: string; + href: string; + variant: variant; + disabled?: boolean; + submit?: boolean; + icon?: string; + centered: boolean; +}): JSX.Element { + const classstr = `button button--color-${variant}`; + if (submit) { + return /* HTML */ ``; + } + return ( + + {icon ? render_icon(icon) : ""} {text} + + ); +} diff --git a/src/back/jdd-components/linktree/linktree.css b/src/back/jdd-components/linktree/linktree.css new file mode 100644 index 0000000..68a72a9 --- /dev/null +++ b/src/back/jdd-components/linktree/linktree.css @@ -0,0 +1,60 @@ +.linktree { + display: flex; + flex-flow: column; + align-items: center; + --image-width: 200px; + + .linktree__photo { + picture { + border-radius: calc(var(--image-width) / 2); + overflow: hidden; + + img { + width: var(--image-width) !important; + height: var(--image-width) !important; + + overflow: hidden; + } + } + } + + h2 { + text-align: center; + } + + h3 { + margin-top: 16px; + margin-bottom: 8px; + } + + .linktree__sections { + width: max-content; + max-width: 100cqw; + + ul { + padding: 0; + margin: 0; + display: flex; + flex-flow: column; + gap: 8px; + justify-content: stretch; + + li { + list-style: none; + display: flex; + align-items: center; + + a.button { + margin: 0; + width: 100%; + min-width: 300px; + + span { + text-align: center; + flex-grow: 1; + } + } + } + } + } +} diff --git a/src/back/jdd-components/linktree/linktree.jdd.tsx b/src/back/jdd-components/linktree/linktree.jdd.tsx new file mode 100644 index 0000000..d2829d5 --- /dev/null +++ b/src/back/jdd-components/linktree/linktree.jdd.tsx @@ -0,0 +1,69 @@ +import { TempstreamJSX } from "tempstream"; +import type { ComponentToHTMLArgs } from "@sealcode/jdd"; +import { Component, ComponentArguments as Args, insert_nbsp } from "@sealcode/jdd"; +import { getAllIconNames } from "src/back/icons.js"; +import { button, button_variants } from "src/back/elements/button.js"; + +const component_arguments = { + photo: new Args.Image(), + title: new Args.ShortText(), + description: new Args.Markdown(), + sections: new Args.List( + new Args.Structured({ + name: new Args.ShortText(), + links: new Args.List( + new Args.Structured({ + href: new Args.URL("absolute"), + text: new Args.ShortText(), + icon: new Args.Enum(getAllIconNames), + variant: new Args.Enum(button_variants), + }) + ), + }) + ), +} as const; + +export class Linktree extends Component { + getArguments() { + return component_arguments; + } + + async toHTML({ + args: { photo, title, description, sections }, + classes, + jdd_context: { render_markdown, render_image, language }, + }: ComponentToHTMLArgs): Promise { + return ( +
+
+ {photo + ? render_image(photo, { + sizesAttr: "200px", + + container: { + width: 200, + height: 200, + objectFit: "cover", + }, + alt: title || "", + }) + : ""} +
+ {title ?

{insert_nbsp(title)}

: ""} + {description ?
{render_markdown(language, description)}
: ""} +
+ {sections.map((section) => ( + /* HTML */
+

{section.name}

+
    + {section.links.map((link) => ( +
  • {button({ ...link, centered: true })}
  • + ))} +
+
+ ))} +
+
+ ); + } +}