self-resizing text

This commit is contained in:
Joshua Seigler 2025-04-05 21:52:33 -07:00
parent bc29510bb5
commit 9649d407fd
4 changed files with 63 additions and 10 deletions

View file

@ -12,14 +12,14 @@
},
"license": "MIT",
"dependencies": {
"solid-js": "^1.9.3",
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-opener": "^2"
"@tauri-apps/plugin-opener": "^2",
"solid-js": "^1.9.3"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"typescript": "~5.6.2",
"vite": "^6.0.3",
"vite-plugin-solid": "^2.11.0",
"@tauri-apps/cli": "^2"
"vite-plugin-solid": "^2.11.0"
}
}

View file

@ -76,13 +76,13 @@
flex-direction: column;
gap: calc(1 * var(--unit));
font-size: calc(3 * var(--unit));
font-weight: 700;
width: calc((20 * 4 + 3) * var(--unit));
margin: 0 auto;
}
.puzzle-header {
display: flex;
align-items: center;
flex-direction: row;
> h1 {
flex-grow: 1;
@ -112,10 +112,11 @@
user-select: none;
flex-basis: 0;
flex-grow: 1;
padding: 0;
padding: calc(1 * var(--unit));
border-radius: calc(1 * var(--unit));
background-color: var(--color-foreground-trace);
font-weight: 500;
font-size: calc(5 * var(--unit));
font-weight: 600;
}
.badge {
position: absolute;
@ -194,6 +195,7 @@ h1 {
button {
appearance: none;
overflow: visible;
min-width: 2em;
background: var(--color-foreground-faint);
color: var(--color-foreground);
font-size: inherit;
@ -202,7 +204,7 @@ button {
border-radius: calc(1 * var(--unit, 0.5em));
}
button:disabled {
opacity: 0.5;
opacity: 0.25;
}
button:focus-visible, button:hover {
box-shadow: 0 0 3em -1.5em inset var(--color-foreground)

View file

@ -1,6 +1,7 @@
import { For } from "solid-js";
import "./App.css";
import useAppModel from "./useAppModel";
import FitText from "./FitText";
function App() {
const {
@ -35,6 +36,7 @@ function App() {
on:input={({ target: { value } }) =>
handleSelectGame(parseInt(value, 10))
}
value={store.puzzleId}
>
<For each={connections}>
{({ id }) => <option value={id}>{id}</option>}
@ -53,6 +55,7 @@ function App() {
</button>
</div>
</header>
<div>Create four groups of four words!</div>
<For each={store.solvedGroups}>
{({ group, level, members }) => (
<div class="puzzle-row">
@ -68,7 +71,7 @@ function App() {
<div class="puzzle-row">
{[0, 1, 2, 3].map((col) => {
const index = 4 * row + col;
const answer = getFromPuzzle(index).answer;
const answer = () => getFromPuzzle(index).answer;
return (
<button
classList={{
@ -85,7 +88,7 @@ function App() {
});
}}
>
{answer}
<FitText body={answer} />
{store.pinnedCount > index && <div class="badge">🔒</div>}
</button>
);

48
src/FitText.tsx Normal file
View file

@ -0,0 +1,48 @@
import { createEffect, createSignal, on, type Accessor } from "solid-js";
export default function FitText(props: { body: Accessor<string> }) {
let textRef: SVGTextElement | undefined;
const [width, setWidth] = createSignal(100);
const [height, setHeight] = createSignal(100);
createEffect(
on(
props.body,
async () => {
setWidth(100)
setHeight(100)
await new Promise(resolve => setTimeout(resolve, 1))
const bounds = textRef?.getBBox();
if (bounds === undefined) {
return;
}
setWidth(bounds.width);
setHeight(bounds.height);
}
)
)
return (
<svg
style={{
width: "100%",
"max-height": "50%",
}}
viewBox={`0 0 ${width()} ${height()}`}
overflow="visible"
fill="currentcolor"
>
<text
ref={textRef}
x="50%"
y="50%"
dominant-baseline="central"
text-anchor="middle"
font-size="1rem"
font-family="inherit"
>
{props.body()}
</text>
</svg>
);
}