Skip to content

feat: redesign knobs — fonts (buttons/inputs), transparent layout, Statistic valueAs/countUp, Tier price detail + modern appearance, ReqoreTierGroup (3D stack on phones), Panel media, FeatureCard actions, Timeline states, SegmentedControl item props — 0.78.0 - #696

Merged
Foxhoundn merged 18 commits into
developfrom
feature/redesign-knobs
Oct 7, 2026

Conversation

@Foxhoundn

@Foxhoundn Foxhoundn commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

What

Nine knobs the qorus.cloud redesign needs, in one PR (the redesign spec's R1–R9). The landing page uses an interim for each today (marked TODO(reqore) there); these replace them. Filip's rule for the site: "If a look needs a knob Reqore lacks, add the knob upstream instead of a local component."

Every knob is opt-in and renders nothing differently without its prop, except R1, which is a font bug fix: buttons and inputs inherit the page font; tags and textareas are unchanged (see "Snapshots" below).

Changes after review (7 Oct)

Per Filip's review ("OK without the tag change please"), R1 no longer touches tags or textareas:

  • ReqoreTag (and every badge) and the ReqoreKeyboardShortcut keys keep their hard-coded system-ui, exactly as on develop.
  • ReqoreTextarea stays monospace by default, as on develop (the IDE relies on it). The "no longer monospace" behaviour change and its note are gone. ReqoreExportModal and ReqoreTree are back to develop's code, since the default already gives them monospace, and BROWSER_MONOSPACE_FONT is removed.
  • Kept: buttons and inputs inherit the page font (the real fix), and theme.fontFamily, which the layout wrapper and the portal set. Tags, shortcut keys and textareas are excluded from it, and theme.ts / COMPONENTS.md say so.
  • R2–R9 are unchanged.

Develop released 0.77.7 (#697) while this PR was open. It is merged in. With the tier work below (a new component), this PR ships as 0.78.0.

Tiers: a modern appearance, and a 3D stack on phones (7 Oct)

Filip: "We need to improve the look and feel of the ReqoreTier, I don't like them that much. On mobile I would also like to improve the presentation, like 3D stacked cards, one is always at the top, and others are behind it on each side and you switch between them with swiping."

Both are opt-in. A tier without appearance renders exactly as before: the four existing Tier snapshots are byte-identical (md5) to their captures before this change, and the classic code path is untouched apart from a reqore-tier class on its root.

What was weak in the classic tier

  • Everything is centred: a feature's text is centred while its icon sits at the card's left edge, so the list is ragged and hard to scan.
  • The name, the period and the button label are forced into upper case; the period is a 12px line under the price at 1.6:1.
  • Each button sits wherever its description ended, so a row of tiers has its buttons at different heights; the highlighted tier is scaled to 1.05 (it overflows its column) and its gradient animates.
  • On a light theme the minimal button's label is light blue on light blue.
  • The price is a heading (a ReqoreH1 its control group sizes into an <h3>), one per plan.

ReqoreTier appearance="modern"

Modelled on the pricing pages of Linear, Vercel, Supabase and Clerk:

  • A left-aligned card on the panel's flat surface with a hairline border, radiusSize big, padding from size (24px at normal).
  • The name is a heading (<h3>; labelSize sets the level) with its badge beside it as a soft pill. The price is 40px (32 / 48 at small / big) and never a heading. The period sits on its baseline, as written, in a muted colour kept at 4.5:1, and wraps under the price when there is no room. A sale strikes the original price through (<del>).
  • The description is muted at 4.5:1. The features are a list (<ul>), each icon held on its feature's first line.
  • The button is pinned to the bottom edge, so stretched tiers share one button line: in a ReqoreTierGroup, or in any grid that stretches its items, such as ReqoreColumns.
  • A highlighted tier gets its badge, a border, a faint wash and a soft shadow in its intent (info by default), and a filled button. It is not scaled and not animated. The other buttons are neutral; an active tier's is a read-only success button with a check.
  • labelEffect / descriptionEffect style the name and the description; priceDetailEffect / priceDetailProps, contentEffect and actionButtonProps still apply over the defaults.

ReqoreTierGroup (new component)

  • Wide: a row of equal-height columns (minColumnWidth 240px, maxColumnWidth 1fr, gapSize big).
  • Narrow (the group's own width at or below mobileBreakpoint, 480px): the same row wrapped to one column (mobileLayout="columns", the default), or a 3D stack (mobileLayout="stack"). The stack shows one tier in front (the highlighted one at first) and its neighbours behind it on either side, scaled to 0.9, turned 12° and dimmed by brightness, peeking 20px past its edges. Dimming uses brightness rather than opacity because faded dark cards vanish into a dark page.
  • Switching: a swipe (touch or mouse drag; the tiers follow the finger, and a drag past an end gives a little and springs back), the arrow keys and Home / End, the previous / next buttons (disabled at the ends), a dot per tier, or a tap on a neighbour. The switch is a CSS transition on a curve that overshoots slightly, so it settles like a spring. A capture with animations disabled shows the end state.
  • prefers-reduced-motion: the tiers switch at once, do not follow the finger and are not turned.
  • Ends: clamped by default, because the order of the plans means something. loop wraps round.
  • Never widens the page: the stack clips at its sides (overflow-x: clip) and handles only horizontal swipes (touch-action: pan-y). The stories assert no overflow at 360px.
  • Accessibility: the stack is a labelled carousel region (stackLabel, "Plans"), focusable with a focus ring. Each tier is a slide named "Pro, 2 of 3" (stackItemLabel), and the tiers behind the front are inert. A polite live line names the new front tier. Focus that would be lost moves to the carousel. Each dot is named after its tier and the front one has aria-current.
  • Hands appearance and size to tiers that do not set their own, and cascades its customTheme. intent colours the front dot, the focus ring and the buttons.
Component Prop Type Default
ReqoreTier appearance 'classic' | 'modern' 'classic'
ReqoreTierGroup appearance TReqoreTierAppearance each tier's own
mobileLayout 'columns' | 'stack' 'columns'
mobileBreakpoint number (px, the group's own width) 480
activeIndex / defaultActiveIndex number the first highlighted tier, else 0
onActiveIndexChange (index: number) => void —
loop boolean false
minColumnWidth / maxColumnWidth / gapSize string / string / TSizes '240px' / '1fr' / 'big'
stackLabel / previousLabel / nextLabel string 'Plans' / 'Previous plan' / 'Next plan'
stackItemLabel (name, index, count) => string 'Pro, 2 of 3'
size, intent, customTheme, inheritCustomTheme, tooltip, ref, div attributes standard

The arithmetic is pure and exported: normalizeTierIndex, getTierStackOffset, getInitialTierIndex, getTierSwipeStep, getTierStackItemStyle, and getTierColors for the modern tier.

Supporting changes

  • useNarrowContainer now measures before the first paint (a layout effect), so a narrow container is never painted once in its wide layout first. EntityRow and SeverityRow captures are byte-identical with and without the change (42 of 42).
  • isTextEntry (from ButtonRail) and prefersReducedMotion / usePrefersReducedMotion (from useCountUp, and now kept current) are shared helpers rather than private copies.

Stories (all new; each has a description and a play test)

Story What it shows
Other/Tier · Modern Three modern plans in a plain ReqoreColumns: equal heights, one button line, the period on the baseline, a highlighted Pro
Other/Tier · Modern Light The same on a light theme
Other/Tier · Modern States An active plan, a sale, an <h2> name with a detail and an orange badge, an excluded feature and a right icon
Other/Tier · Modern Sizes small / normal / big
Other/Tier Group · Row Four plans in one row
Other/Tier Group · Stack A 360px phone: Pro in front, the neighbours on both sides, the controls
Other/Tier Group · Stack Light The same on a light theme
Other/Tier Group · Stack Swipe After a swipe to the left: Enterprise in front, next disabled; a swipe past the end springs back
Other/Tier Group · Stack Keyboard After the arrows, Home / End, the buttons and a dot: Starter in front, the focus ring on the carousel
Other/Tier Group · Stack Reduced Motion The switch at once, the stack flat
Other/Tier Group · Stack Loop Four plans with loop: Enterprise behind Starter on the left
Other/Tier Group · Stack Themed The group's customTheme, intent and size reaching its tiers and controls
Other/Tier Group · Narrow Columns 360px with the default mobileLayout: one column

The play tests assert: equal heights and one button line (±1px); the period on the price's line or wrapped to the next; no tier scaled; the name a heading and no <h1>; muted lines at 4.5:1 or better; the front tier and the dots after swipes, keys, buttons and dots; clamping and wrapping; inert tiers; no transitions under reduced motion; no horizontal overflow at 360px.

Tests

tier.test.tsx (4 → 18: the modern structure, heading level, period, sale, currency, features, active, highlight, effects, and contrast on dark and light themes); tierGroup.test.tsx (new, 23: index arithmetic, swipe decisions, stack geometry, row / stack, the breakpoint, keyboard, loop, buttons and dots, a controlled index, translated labels, reduced motion, customTheme, tiers arriving late, the first-paint measurement); hooks/usePrefersReducedMotion.test.tsx (new, 3). yarn precheck: 1358 tests, green.

Snapshots (tiers)

Qlip build #337 (head 0578ab9): 0 changed, 13 new, 1112 unchanged. The four classic Tier stories (Default, Group, Price Detail Effect and its Light variant) are byte-identical to their baselines: the same sha256 blob, 0 diff pixels. The 13 new stories are the ones in the table above and are left for Filip; nothing was accepted. On #336, Narrow Columns was captured at 205×1080 (a 1900px-tall viewport, scaled down to the window) and denied. #337 captures it at 360×1080. Stack Keyboard varies by 2 anti-aliased pixels on its focus ring's corner between runs.

Adopting it on the landing page (once published)

The prerelease 0.78.0-pr.696.g0578ab9 is on npm for trying this locally (never committed to a consumer).

<ReqoreTierGroup appearance='modern' mobileLayout='stack' minColumnWidth='250px' data-catalog-version={…}>
  {plans.map(({ plan, copy }) => (
    <ReqoreTier
      key={plan.code}
      name={copy.name}
      price={formatMoney(…)}
      currency=''
      priceDetail={price ? perPeriod : undefined}
      description={copy.tagline}
      featureList={planFeatures(…)}
      highlight={copy.highlighted}
      badge={copy.highlighted ? l.popular : undefined}
      actionLabel={action}
      actionButtonProps={{ icon: 'ArrowRightLine', 'data-track-click': 'join-waitlist', … }}
      customTheme={cardTheme}
    />
  ))}
</ReqoreTierGroup>

This drops the interims: the period goes back to priceDetail instead of the description fragment, and the contentEffect={{ gradient: undefined }}, labelEffect and minimal: false overrides are no longer needed.

The knobs

# Component Prop Type What it does
R1 ReqoreButton, ReqoreInput (no prop: a fix) — font-family: inherit. Buttons and inputs were left to the browser, which draws form controls in its control face (-webkit-small-control: system-ui on macOS, the system sans on Linux). They now use the page's font, and effect.fontFamily still picks one. Tags and textareas are unchanged: ReqoreTag (badges included) and the ReqoreKeyboardShortcut keys keep system-ui, and ReqoreTextarea keeps the browser's monospace.
R1 ReqoreUIProvider theme.fontFamily 'mono' | 'system' | string The font Reqore renders in. The layout wrapper and the portal both set it, and text, buttons and inputs inherit it. Tags, shortcut keys and textareas are excluded (system-ui / monospace). Unset (default): the wrapper and the portal name no font.
R2 ReqoreStatistic valueAs 'h1' … 'h6' | 'p' | 'div' | 'span' The whole value (prefix, number, suffix) is ONE element of that kind instead of three headings, drawn exactly as before (same size from size, bold, no margins).
R3 ReqoreUIProvider layoutWrapperProps Omit<IReqoreLayoutWrapperProps, 'children' | 'withSidebar'> & TReqoreDataAttributes Props for .reqore-layout-wrapper: transparent, className (merged), style, aria-*, data-*.
R3 ReqoreLayoutWrapper transparent boolean No surface: the page's own background shows through, no seam. Text colour kept.
R4 ReqoreTier priceDetailEffect IReqoreEffect Spread over the "/ month" defaults ({ uppercase: false } keeps the case; colour / opacity make it readable).
R4 ReqoreTier priceDetailProps Partial<IReqoreParagraphProps> & TReqoreDataAttributes Spread over the paragraph's intent: 'muted', size: 'small'; its effect merges under priceDetailEffect.
R5 ReqorePanel media string | ReactNode A cover drawn edge to edge, clipped to the panel's inner curve (also with a sticky header). A URL renders an <img>. Hidden while collapsed.
R5 ReqorePanel mediaPosition 'top' | 'bottom' (default 'top') Above the title bar, or under the bottom actions.
R5 ReqorePanel mediaAlt string (default '') The image text of a URL (empty = decorative).
R5 ReqorePanel mediaAspectRatio string | number Fixes the frame (aspect-ratio); the media covers it.
R5 ReqorePanel mediaProps HTMLAttributes<HTMLDivElement> & TReqoreDataAttributes Props for the frame.
R6 ReqoreFeatureCard actions IReqoreFeatureCardAction[] (Omit<IReqoreButtonProps, 'children'> & { label?: string }) Buttons in a footer pinned to the card's bottom edge (buttons of stretched cards share one line). They take the card's size, intent, customTheme and disabled; a click never reaches the card's onClick.
R6 ReqoreFeatureCard footer ReactNode Footer content before the actions; text at the description's size.
R6 ReqoreFeatureCard footerProps Partial<IReqoreControlGroupProps> & TReqoreDataAttributes Restyles the footer row (horizontalAlign, vertical, gapSize, …).
R7 ReqoreTimeline item highlighted boolean Halo round the marker in the item's colour, bolder title, aria-current="step".
R7 ReqoreTimeline item pending boolean Spinning LoaderLine in place of the icon or dot (still under reduced motion), aria-busy.
R8 ReqoreStatistic countUp boolean | { duration?: number; from?: number; threshold?: number; format?: (n: number) => string } Counts up once threshold (0.35) of the value is in view, over duration (1000 ms), keeping the value's own format; ends on the value as given. Not-one-number values and reduced motion: shown at once. The moving number is aria-hidden; the value is read instead.
R9 ReqoreSegmentedControl item props TReqoreSegmentedControlItemProps (ReqoreButton props minus onClick/role/tabIndex/aria-checked/label/children, plus data-*) data-*, aria-*, id, style, className for one segment's button; the control keeps the radio-group props. Folded segments pass them to their menu item.

New shared type: TReqoreDataAttributes (types/global), an index signature for data-* keys, so a props bag written as an object can carry data-* attributes. New hook: hooks/useCountUp (useCountUp, parseCountUpValue, formatCountUpValue), internal.

Snapshots (R1)

The font fix is the only change without a new prop, so it is the only one that can touch existing snapshots. Button and input text is now in the page's font, which in the story suite is the Helvetica Neue / Helvetica / Arial stack from .storybook/preview-body.html. In CI's Chromium on Linux the browser's control font resolves to the same face, so button and input labels barely move there. Tag, badge, shortcut-key and textarea snapshots are unchanged against develop. The other eight knobs add stories only.

Qlip build #332 (head 65842e7): 0 changed, 20 new, 1092 unchanged.

One real change Qlip did not surface. Its perceptual-hash shortcut auto-passed it, so I compared every image with develop's own capture to find it. In ReqoreDataView, the "+ Add item" / "+ Add property" buttons inside a value cell now inherit the cell's MONO_FONT and render in monospace (before, the browser's control font overrode it). It shows in Collections/Dataview · Editable Array Ops and · Editable Open Api Schema, and there are comments on both snapshots. This needs a decision: keep it (buttons follow the surrounding font), or scope DataView's monospace to the value text. Everything else that differed was flaky (image loads, a per-run stack trace) or sub-threshold anti-aliasing.

Stories (all new; each has a description and a play test)

Story What it shows
Utilities/UI Provider · Page Font Panel, tags, input, textarea, buttons and a shortcut key on a serif page. Title, paragraph, input and buttons are in the page font; tags and the key stay system-ui, and the textarea stays monospace
Utilities/UI Provider · Theme Font Family The same under theme.fontFamily: 'mono'. The theme font reaches the text, input and buttons, not the tags, the key or the textarea
Utilities/UI Provider · Transparent Layout Default wrapper (a box of the theme colour on the page) vs layoutWrapperProps.transparent, dark and light pages
Display/Statistic · Value As Four identical-looking tiles: default (3 headings), p, div, h3 (1 heading)
Display/Statistic · Count Up $48,200, 99.9 + %, 1.2M counting up; 2–4 shown at once
Display/Statistic · Count Up When In View A tile below the fold of a scroll box: 0 until scrolled to, then 12,345
Other/Tier · Price Detail Effect (+ Light) Default "/ MONTH", as written, readable
Layout/Panel · Media (+ Light, Mobile) Article card with a top cover, a 4:3 photo card, a 21:9 bottom cover
Display/Feature Card · Actions (+ Light, Mobile) Three stretched problem cards whose buttons share one line; fluid buttons on a phone
Display/Feature Card · Footer Tags + action, price + Buy right-aligned, text footer
Utilities/Timeline · Highlighted And Pending (+ Horizontal, Light, Mobile) A run with a highlighted current step and a running step
Form/SegmentedControl · Item Props data-track-* on billing-cycle segments; aria-label on icon-only segments

Tests

New tests:

  • fontFamily.test.tsx reproduces the browser's control-font and textarea-monospace rules, so it fails without the fix. It checks both sides: buttons and inputs follow the page font, the body font and theme.fontFamily, while tags and shortcut keys stay system-ui and textareas stay monospace under all three.
  • statisticValue.test.tsx, hooks/useCountUp.test.ts, tier.test.tsx, panelMedia.test.tsx.

Additions to layout, FeatureCard, Timeline and segmentedControl. tag.test.tsx is back to develop's version, which asserts system-ui.

yarn precheck is green after merging develop: 1316 tests. The UI Provider, Tag, TextArea and KeyboardShortcut story files run locally and pass (46 stories).

Not changed (noted for later)

  • The classic ReqoreTier still renders the price as a heading: a ReqoreH1 that its control group sizes into an <h3> (this note said <h1> before; the tests now show which), one per plan. The same class of issue as R2. appearance="modern" (below) fixes it for tiers that opt in: the name is the heading and the price is a paragraph. The classic look is unchanged.
  • ReqoreProvider reports isMobile: false under NODE_ENV=test, so no story can show a viewport-width branch (e.g. the Timeline's horizontal → vertical fallback); the Timeline phone story shows both directions explicitly instead.

Version

0.77.7 → 0.78.0. This is a minor release because of a new component, ReqoreTierGroup; the rest are new props on existing components and a bug fix. The branch was at 0.77.8, and before that 0.77.6, and neither was ever published. The other open PRs are at 0.77.9 (#708) and 0.77.10 (#709). The notes in COMPONENTS.md that said "new in 0.77.8" now say 0.78.0.

🤖 Generated with Claude Code

Foxhoundn and others added 8 commits October 6, 2026 19:45
…layout — 0.77.6

Buttons, inputs and textareas were left to the browser's user-agent
stylesheet, which draws form controls in the platform's control face
(`-webkit-small-control`: system-ui on macOS, the system sans on Linux),
and ReqoreTag and the ReqoreKeyboardShortcut keys named `system-ui`. On a
page set in its own typeface every button, field, tag and badge was in a
different font from the text around it. They now declare
`font-family: inherit`: the page's font, or the theme's.

`theme.fontFamily` (the `'mono'` / `'system'` shorthands or any CSS font
stack) is the font every Reqore surface renders in: the layout wrapper and
the portal popovers, modals and drawers render into both set it, and
everything inherits it. Unset (the default) Reqore names no font of its
own. `effect.fontFamily` still picks a font for one element.

ReqoreLayoutWrapper takes `transparent`: no surface, so a page that paints
its own background behind the app shows through without a rectangle of
another colour and a seam where it ends. ReqoreUIProvider passes props to
the wrapper through `layoutWrapperProps` (`transparent`, `className`,
`style`, `aria-*`, `data-*`); `options.withSidebar` still decides the
direction. `TReqoreDataAttributes` lets a props bag written as an object
carry `data-*` attributes.

The font change is the only change without a new prop: every snapshot
with a button, an input, a textarea, a tag or a shortcut key re-renders
its text in the page's font (in the story suite, the Helvetica / Arial
stack of .storybook/preview-body.html) instead of the control font.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
valueAs: the prefix, the value and the suffix are each a heading by
default (h1 – h4 from `size`), so a row of KPI tiles puts one to three
headings per tile into the page outline and a screen reader lists the
figures as section titles. `valueAs` ('h1'–'h6' | 'p' | 'div' | 'span')
writes the whole value as ONE element of that kind instead, drawn as
before: the same size from `size`, bold where the element is not a
heading, no margins. Without it nothing renders differently.

countUp (`boolean | { duration, from, threshold, format }`): the value
counts up once `threshold` (0.35) of it is on screen, from `from` (0)
over `duration` ms (1000), easing out, keeping its own format — decimals,
thousands separator and the text around the number ('$1.2M', '99.8%',
'1,234', '1.234,5') — or writing each frame with `format`. The last frame
is the value as given. A value that is not one number ('2–4', '24/7') and
every value under prefers-reduced-motion show at once. While it counts,
the moving number is aria-hidden and the value itself is read from a
visually hidden copy. The hook lives in hooks/useCountUp with its parser
and formatter.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The line under the price ("/ month") was fixed at small, uppercase and
the muted intent — about 1.6:1 on the default dark theme, and forced into
capitals. `priceDetailEffect` is spread over its effect defaults
(`{ uppercase: false }` keeps the text as written; a colour or opacity
makes it readable) and `priceDetailProps` over its paragraph's (another
intent or size, aria-*, data-*), the props' own effect merged under
priceDetailEffect. The paragraph carries `.reqore-tier-price-detail`.
Without them nothing renders differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`media` (an image URL, or any node: an img, a picture, a video, an
illustration) is drawn edge to edge across the panel, outside its
padding, in a frame whose outer corners take the panel's inner curve —
its radius, less the 1px border when it draws one — so the cover is
clipped to the panel's shape even with a sticky header, which makes the
panel overflow visible. `mediaPosition` puts it above the title bar (the
default; the sticky-header sentinel moves under it) or under the bottom
actions; `mediaAlt` is a URL's image text (default '': decorative);
`mediaAspectRatio` fixes the frame and makes the media cover it;
`mediaProps` reach the frame. Hidden while the panel is collapsed. A
panel with only `media` and `padded={false}` is a photo card. Without
`media` nothing renders differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`actions` (ReqoreButton props plus `label`) and `footer` (any node; text
at the description's size) make a last row, pinned to the card's bottom
edge, so in a row of stretched cards the buttons share one line whatever
each description's length. The footer content comes first, in a
ReqoreControlGroupItem so the row hands it none of its props, then the
actions. Actions take the card's size, intent and customTheme unless they
set their own, are disabled with the card and fluid only when they say
so, and a click on one never reaches the card's own onClick (the rule
ReqoreEntityRow and ReqoreSeverityRow follow). `footerProps` restyle the
row. Without actions or a footer nothing renders differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
`highlighted` marks the step that matters now: a halo round its marker in
the item's colour (its intent, the timeline's, or the neutral line
colour), a bolder title and aria-current="step". `pending` marks a
running step: a spinning LoaderLine in the item's colour replaces its
icon or dot, standing still under prefers-reduced-motion, and the item is
aria-busy. Both work in either direction. Without them nothing renders
differently.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An item's `props` reach its ReqoreButton: data-* (a data-track-click for
analytics), aria-* (an aria-label for an icon-only segment), id, style,
className (merged). They go under the control's own, so the segments stay
the radio group the control runs — the selection handler, role,
aria-checked, tabIndex, disabled and the selected look are the control's,
and the item's own icon, rightIcon, badge, tooltip and effect win where
set. A segment folded into the More menu passes them to its menu item.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
ReqoreButton, ReqoreInput, ReqoreTextarea and ReqoreTag (and the
shortcut keys) are in the page's font or theme.fontFamily; a new
ReqoreUIProvider row covers theme.fontFamily and layoutWrapperProps, and
ReqoreLayoutWrapper `transparent`. ReqoreStatistic valueAs / countUp,
ReqoreTier priceDetailEffect / priceDetailProps, ReqorePanel media,
ReqoreFeatureCard actions / footer, ReqoreTimeline highlighted / pending
and ReqoreSegmentedControl per-item props are described where each
component is.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Foxhoundn Foxhoundn self-assigned this Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

📦 Prerelease published for this PR:

@qoretechnologies/reqore@0.78.0-pr.696.g0578ab9

Install it in a consumer (updates package.json + lockfile):

yarn add @qoretechnologies/reqore@0.78.0-pr.696.g0578ab9

Published under the pr dist-tag — latest/beta are untouched. Repoint consumers to the released version once this PR merges.

@qlip-qore-technologies

qlip-qore-technologies Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

qlip visual regression

🔍 0 changed, 13 new of 1125 snapshots.
✅ All changes approved.

Review build 20261007-100642 in the dashboard →

A <textarea> gets `font-family: monospace` from the browser's own
stylesheet, so ReqoreTextarea was monospace until it started following the
page's font. For prose (a description, a message) the page's font is
right; for data it is not. ReqoreExportModal's CSV / JSON / YAML export
and ReqoreTree's raw value editor now ask for the generic `monospace`
(`BROWSER_MONOSPACE_FONT`), the face they always had, so their rendering
does not change.

The font test now reproduces the browser's textarea rule too, and checks
both data textareas.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Foxhoundn and others added 3 commits October 7, 2026 08:55
Per the review of #696, the font fix stays for buttons and inputs only.
ReqoreTag (and so every badge) and the ReqoreKeyboardShortcut keys name
system-ui again, and ReqoreTextarea is left to the browser's own
stylesheet again, which draws it in monospace: the IDE relies on that
for the code and data it edits in textareas. All of them render exactly
as on develop.

theme.fontFamily stays. The layout wrapper and the portal set it, and
the text, the buttons and the inputs inherit it. Tags, shortcut keys and
textareas are excluded from it, as they are from the page's font, and
the theme.ts and COMPONENTS.md docs say so. effect.fontFamily still
picks a font for any one of them.

With textareas monospace by default, ReqoreExportModal's export and
ReqoreTree's raw value editor no longer need to ask for it, so they go
back to develop's code and BROWSER_MONOSPACE_FONT is gone.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nobs

Brings in #697 (styling props no longer reach the DOM), released as
0.77.7 while this PR was open. The only conflict was the version: this
PR moves from 0.77.6 to 0.77.8, the next one above develop's 0.77.7.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
develop released 0.77.7 (#697) while this PR was open, so these knobs and
the button / input font fix ship in 0.77.8, not 0.77.6. The "new in" and
"changed in" notes say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Foxhoundn Foxhoundn changed the title feat: redesign knobs — fonts, transparent layout, Statistic valueAs/countUp, Tier price detail, Panel media, FeatureCard actions, Timeline states, SegmentedControl item props — 0.77.6 feat: redesign knobs — fonts (buttons/inputs), transparent layout, Statistic valueAs/countUp, Tier price detail, Panel media, FeatureCard actions, Timeline states, SegmentedControl item props — 0.77.8 Oct 7, 2026
Since buttons and inputs take the font around them (0.77.8), the
"+ Add item" / "+ Add property" buttons inside a value cell inherited
ValueCell's MONO_FONT and rendered in monospace, and so did the type
picker, the delete button and the edit and rename inputs of every
nested level (Qlip #332: Collections/DataView · Editable Array Ops and
· Editable Open Api Schema). Per the review of #696, buttons keep the
normal font.

ValueCell keeps its monospace, so its own text and line boxes, and with
them the data, render exactly as before. The buttons and inputs in it
get the theme's font when theme.fontFamily is set, and otherwise the
browser's own control font (`font-family: revert`), which is what they
rendered in on develop. The page's font cannot be named from inside a
cell that has replaced it, and moving the monospace off the cell onto
the value text would change the line boxes of every nested level.

Checked locally against develop. With the page font set to Arial, the
headless browser's control font and the face CI resolves the stories'
font to, all 25 DataView stories are byte-identical to develop's. With
the stories' Helvetica Neue only the root "+ Add property" differs, as
it already does on this branch: that is the button font change itself.
Unit tests cover a theme font and no theme font; Editable Array Ops
asserts the two fonts in its play.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Foxhoundn and others added 5 commits October 7, 2026 11:42
…ners measured before paint

The tier group needs what ButtonRail and useCountUp each kept to
themselves: a test for "this key belongs to a text field" and a read of
prefers-reduced-motion. Both are shared now (isTextEntry in
helpers/utils, prefersReducedMotion / usePrefersReducedMotion in
hooks/usePrefersReducedMotion, which also follows the preference while
mounted), so the group does not grow a third copy of either.

useNarrowContainer answered only from the ResizeObserver's first
callback, which arrives after React has committed: a narrow container
was painted once in its wide layout and then jumped. It now measures in
a layout effect before the first paint and lets the observer keep the
answer current. EntityRow and SeverityRow captures are byte-identical
with and without the change (42 of 42); the group's stack relies on it
to avoid a one-frame layout shift on phones.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…one line with its neighbours'

Filip, 7 Oct: "We need to improve the look and feel of the ReqoreTier,
I don't like them that much." The classic tier centres everything,
forces the name, the period and the button label into upper case,
draws the period as a faint 12px line under the price, scales and
animates the highlighted tier, and leaves each button wherever its
description ended, so a row of tiers has its buttons at different
heights.

appearance="modern" is opt-in; the default ('classic') renders exactly
as before (the four existing Tier snapshots are byte-identical). Modern,
after the pricing pages of Linear, Vercel, Supabase and Clerk:
left-aligned on the panel's flat surface with a hairline border; the
name a heading (h3, `labelSize` sets the level) with its badge beside
it as a soft pill; a large price (40px at normal) that is never a
heading, the period on its baseline, as written, in a muted colour kept
at 4.5:1; the description, muted, 4.5:1; the features as a list with
each icon on its feature's first line; and the button pinned to the
bottom edge, so stretched tiers share one button line. A highlighted
tier gets a border, a faint wash and a soft shadow in its intent, and a
filled button — no scaling, no animation. A sale strikes the original
price through (<del>). `labelEffect` / `descriptionEffect` style the
name and the description; `priceDetailEffect` / `priceDetailProps`
still apply. Every tier now carries `.reqore-tier`.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… on a narrow screen

Filip, 7 Oct: "On mobile I would also like to improve the presentation,
like 3D stacked cards, one is always at the top, and others are behind
it on each side and you switch between them with swiping."

ReqoreTierGroup lays out ReqoreTiers. Wide: a grid of equal-height
columns. Narrow (the group's own width, 480px by default): either the
same row wrapped (mobileLayout="columns", the default) or a stack
(mobileLayout="stack"): one tier in front — the highlighted one at
first — and its neighbours behind it on either side, scaled, turned a
little and dimmed by brightness (opacity made dark cards vanish into a
dark page). Swipes (touch or mouse drag; past 18% of the width or a
quick flick), the arrow keys, Home and End, the previous / next buttons
and a dot per tier change the tier in front; the switch is a CSS
transition on a slightly overshooting curve, so captures with
animations disabled show its end state. Under prefers-reduced-motion it
switches at once, flat. Clamped at the ends by default (the order of
plans means something); `loop` wraps.

The stack is a labelled carousel region (focusable, focus ring), each
tier a slide; tiers behind the front one are inert; a polite live line
names the new front tier; focus that would be lost moves to the
carousel; the stack clips at its sides and never widens the page. The
group hands `appearance` and `size` to tiers without their own and
cascades its customTheme. Index and gesture arithmetic are pure,
exported and unit tested.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s 0.78.0

A new component makes this PR a minor release: 0.77.8 → 0.78.0 (0.77.8
was never published; develop is at 0.77.7 and the other open PRs at
0.77.9 / 0.77.10). The knobs this PR documented as "new in 0.77.8" now
say 0.78.0. COMPONENTS.md gains the modern Tier appearance and a
ReqoreTierGroup entry, and records that the classic tier's price is a
heading too — a ReqoreH1 its control group sizes into an <h3>.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ons say near-white

Narrow Columns asked for a 360×1900 viewport. That is taller than the
1080px browser window, so the test runner scaled the frame down and Qlip
captured it at 205×1080 (denied on build #336). It now uses 360×1080,
the third plan running on below the screen, and its description says so.

Modern Light and Stack Light described the cards as white. They are the
panel's own near-white surface. The reduced-motion story's sentence now
reads clearly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Foxhoundn Foxhoundn changed the title feat: redesign knobs — fonts (buttons/inputs), transparent layout, Statistic valueAs/countUp, Tier price detail, Panel media, FeatureCard actions, Timeline states, SegmentedControl item props — 0.77.8 feat: redesign knobs — fonts (buttons/inputs), transparent layout, Statistic valueAs/countUp, Tier price detail + modern appearance, ReqoreTierGroup (3D stack on phones), Panel media, FeatureCard actions, Timeline states, SegmentedControl item props — 0.78.0 Oct 7, 2026
davidnich added a commit that referenced this pull request Oct 7, 2026
…s a focus list again — 0.78.1

Foxhoundn's review of #708: the check for a field focused as it mounts ran
on every re-run of the listener effect, and the effect re-runs whenever
the list opens or closes (it depends on `open`, which depends on
`isOpen`). Closing the list with the cursor still in the field - Escape,
or a click on it with closeOnTargetClick - re-ran it, found the field
focused, and reopened the list at once. The check is made once per
trigger, remembered in a ref, as the listener is first attached.

Test: a list closed with Escape stays closed while the field keeps the
focus (fails without the change).

0.78.1: #696 goes in first as 0.78.0.
@Foxhoundn
Foxhoundn merged commit b1a555a into develop Oct 7, 2026
6 of 7 checks passed
@Foxhoundn
Foxhoundn deleted the feature/redesign-knobs branch October 7, 2026 12:24
davidnich added a commit that referenced this pull request Oct 7, 2026
davidnich added a commit that referenced this pull request Oct 7, 2026
…description takes block content

#696 added the panel's media props (media, mediaAlt, mediaPosition,
mediaAspectRatio, mediaProps) and the modern Tier appearance. The panel's
compiler-checked style list caught the props: they are now in it, so
they do not reach a component the panel is rendered as. The modern
tier drew its description in a ReqoreP <p>; it is a div like the
classic tier's, with its effect memoized for the memoized paragraph.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant