| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- // @ts-nocheck
- import { createSignal, onMount } from "solid-js"
- import { createResizeObserver } from "@solid-primitives/resize-observer"
- import { createStore } from "solid-js/store"
- import { useSpring } from "./motion-spring"
- import { TextStrikethrough } from "./text-strikethrough"
- const TEXT_SHORT = "Remove inline measure nodes"
- const TEXT_MED = "Remove inline measure nodes and keep width morph behavior intact"
- const TEXT_LONG =
- "Refactor ToolStatusTitle DOM measurement to offscreen global measurer (unconstrained by timeline layout)"
- const btn = (active?: boolean) =>
- ({
- padding: "8px 18px",
- "border-radius": "6px",
- border: "1px solid var(--color-divider, #444)",
- background: active ? "var(--color-accent, #58f)" : "var(--color-fill-element, #222)",
- color: "var(--color-text, #eee)",
- cursor: "pointer",
- "font-size": "14px",
- "font-weight": "500",
- }) as const
- const heading = {
- "font-size": "11px",
- "font-weight": "600",
- "text-transform": "uppercase" as const,
- "letter-spacing": "0.05em",
- color: "var(--text-weak, #888)",
- "margin-bottom": "4px",
- }
- const card = {
- padding: "16px 20px",
- "border-radius": "10px",
- border: "1px solid var(--border-weak-base, #333)",
- background: "var(--surface-base, #1a1a1a)",
- }
- /* ─── Variant A: scaleX pseudo-line at 50% ─── */
- function VariantA(props: { active: boolean; text: string }) {
- const progress = useSpring(
- () => (props.active ? 1 : 0),
- () => ({ visualDuration: 0.35, bounce: 0 }),
- )
- return (
- <span
- style={{
- position: "relative",
- display: "block",
- color: props.active ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- >
- {props.text}
- <span
- style={{
- position: "absolute",
- left: "0",
- right: "0",
- top: "50%",
- height: "1.5px",
- background: "currentColor",
- "transform-origin": "left center",
- transform: `scaleX(${progress()})`,
- "pointer-events": "none",
- }}
- />
- </span>
- )
- }
- /* ─── Variant D: background-image line ─── */
- function VariantD(props: { active: boolean; text: string }) {
- const progress = useSpring(
- () => (props.active ? 1 : 0),
- () => ({ visualDuration: 0.35, bounce: 0 }),
- )
- return (
- <span
- style={{
- display: "block",
- color: props.active ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- "background-image": "linear-gradient(currentColor, currentColor)",
- "background-repeat": "no-repeat",
- "background-size": `${progress() * 100}% 1.5px`,
- "background-position": "left center",
- }}
- >
- {props.text}
- </span>
- )
- }
- /* ─── Variant E: grid stacking + clip-path (container %) ─── */
- function VariantE(props: { active: boolean; text: string }) {
- const progress = useSpring(
- () => (props.active ? 1 : 0),
- () => ({ visualDuration: 0.35, bounce: 0 }),
- )
- return (
- <span
- style={{
- display: "grid",
- color: props.active ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- >
- <span style={{ "grid-area": "1 / 1" }}>{props.text}</span>
- <span
- aria-hidden="true"
- style={{
- "grid-area": "1 / 1",
- "text-decoration": "line-through",
- "pointer-events": "none",
- "clip-path": `inset(0 ${(1 - progress()) * 100}% 0 0)`,
- }}
- >
- {props.text}
- </span>
- </span>
- )
- }
- /* ─── Variant F: grid stacking + clip-path mapped to text width ─── */
- function VariantF(props: { active: boolean; text: string }) {
- const progress = useSpring(
- () => (props.active ? 1 : 0),
- () => ({ visualDuration: 0.35, bounce: 0 }),
- )
- let baseRef: HTMLSpanElement | undefined
- let containerRef: HTMLSpanElement | undefined
- const [state, setState] = createStore({
- textWidth: 0,
- containerWidth: 0,
- })
- const textWidth = () => state.textWidth
- const containerWidth = () => state.containerWidth
- const measure = () => {
- if (baseRef) setState("textWidth", baseRef.scrollWidth)
- if (containerRef) setState("containerWidth", containerRef.offsetWidth)
- }
- onMount(measure)
- createResizeObserver(() => containerRef, measure)
- const clipRight = () => {
- const cw = containerWidth()
- const tw = textWidth()
- if (cw <= 0 || tw <= 0) return `${(1 - progress()) * 100}%`
- const revealed = progress() * tw
- const remaining = Math.max(0, cw - revealed)
- return `${remaining}px`
- }
- return (
- <span
- ref={containerRef}
- style={{
- display: "grid",
- color: props.active ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- >
- <span ref={baseRef} style={{ "grid-area": "1 / 1" }}>
- {props.text}
- </span>
- <span
- aria-hidden="true"
- style={{
- "grid-area": "1 / 1",
- "text-decoration": "line-through",
- "pointer-events": "none",
- "clip-path": `inset(0 ${clipRight()} 0 0)`,
- }}
- >
- {props.text}
- </span>
- </span>
- )
- }
- export default {
- title: "UI/Text Strikethrough",
- id: "components-text-strikethrough",
- tags: ["autodocs"],
- parameters: {
- docs: {
- description: {
- component: `### Animated Strikethrough Variants
- - **A** — scaleX line at 50% (single line only)
- - **D** — background-image line (single line only)
- - **E** — grid stacking + clip-path (container %)
- - **F** — grid stacking + clip-path mapped to text width (the real component)`,
- },
- },
- },
- }
- export const Playground = {
- render: () => {
- const [active, setActive] = createSignal(false)
- const toggle = () => setActive((v) => !v)
- return (
- <div style={{ display: "grid", gap: "24px", padding: "24px", "max-width": "700px" }}>
- <button onClick={toggle} style={btn(active())}>
- {active() ? "Undo strikethrough" : "Strike through all"}
- </button>
- <div style={card}>
- <div style={heading}>F — grid stacking + clip mapped to text width (THE COMPONENT)</div>
- <TextStrikethrough
- active={active()}
- text={TEXT_SHORT}
- style={{
- color: active() ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- />
- <div style={{ "margin-top": "12px" }} />
- <TextStrikethrough
- active={active()}
- text={TEXT_MED}
- style={{
- color: active() ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- />
- <div style={{ "margin-top": "12px" }} />
- <TextStrikethrough
- active={active()}
- text={TEXT_LONG}
- style={{
- color: active() ? "var(--text-weak, #888)" : "var(--text-strong, #eee)",
- transition: "color 220ms ease",
- }}
- />
- </div>
- <div style={card}>
- <div style={heading}>F (inline) — same but just inline variants</div>
- <VariantF active={active()} text={TEXT_SHORT} />
- <div style={{ "margin-top": "12px" }} />
- <VariantF active={active()} text={TEXT_MED} />
- <div style={{ "margin-top": "12px" }} />
- <VariantF active={active()} text={TEXT_LONG} />
- </div>
- <div style={card}>
- <div style={heading}>E — grid stacking + clip-path (container %)</div>
- <VariantE active={active()} text={TEXT_SHORT} />
- <div style={{ "margin-top": "12px" }} />
- <VariantE active={active()} text={TEXT_MED} />
- <div style={{ "margin-top": "12px" }} />
- <VariantE active={active()} text={TEXT_LONG} />
- </div>
- <div style={card}>
- <div style={heading}>A — scaleX line at 50%</div>
- <VariantA active={active()} text={TEXT_SHORT} />
- <div style={{ "margin-top": "12px" }} />
- <VariantA active={active()} text={TEXT_LONG} />
- </div>
- <div style={card}>
- <div style={heading}>D — background-image line</div>
- <VariantD active={active()} text={TEXT_SHORT} />
- <div style={{ "margin-top": "12px" }} />
- <VariantD active={active()} text={TEXT_LONG} />
- </div>
- </div>
- )
- },
- }
|