This document outlines the standard structure and component usage patterns for creating interactive calculation/estimation tools within the marketing site, based on the example of the “SaaS Valuation Multiple Estimator”.
To create reusable, consistent, and maintainable interactive tools that integrate seamlessly with the Astro marketing site structure. The core interaction is typically handled by React components loaded client-side.
A typical tool will reside in its own directory within apps/marketing/src/pages/tools/
, for example, saas-valuation-multiple-estimator/
. The key files are:
index.astro
: The main Astro page file. Sets up the overall page layout, loads static content, and embeds the interactive React component.calculator.tsx
(or similar name): The main React component handling the tool’s logic, state, inputs, and results display.description.mdx
: An MDX file containing detailed descriptive text, methodology, explanations, etc., about the tool. This is rendered within the Astro layout.common/
directory (Shared): Located at apps/marketing/src/pages/tools/common/
. Contains reusable React components and hooks used across different tools.
inputs.tsx
: Provides standardized Input
, InputGroup
components, and the useQueryParamState
hook.summary.tsx
: Provides a standardized Summary
component for displaying results.index.astro
)ToolLayout
(e.g., ../../../layouts/ToolLayout.astro
) to ensure consistent page structure, navigation, and SEO metadata.<h1>
title and introductory <p>
paragraph directly in the Astro file.<Calculator />
) using an Astro directive like client:load
to ensure it hydrates and becomes interactive on the client-side..mdx
file (e.g., <Description />
) usually below the interactive component, often within a prose
styled container for good readability.calculator.tsx
)useState
, useEffect
).useQueryParamState
hook (from common/inputs.tsx
) for input fields. This synchronizes the input field’s state with URL query parameters (e.g., ?arr=10000000&growth=40
).
const [value, setValue] = useQueryParamState('paramName', 'defaultValue');
useState
for calculated results or error messages specific to the component.InputGroup
component (from common/inputs.tsx
) to wrap multiple Input
components, providing a responsive grid layout (e.g., sm:col-span-2
).Input
component (from common/inputs.tsx
) for each user input field.Input
props extensively:
label
: Visible label text.id
: HTML id
attribute (should match label
’s htmlFor
).type
: Set to "number"
for numeric inputs (the component handles underlying type/inputmode and formatting).value
: Bound to the state variable from useQueryParamState
.onChange
: Uses the custom onChange
handler provided by the Input
component. This handler receives the event and a parsedValue
string (cleaned of non-numeric characters except decimal/minus for type="number"
). The component’s state setter (from useQueryParamState
) should be called with this parsedValue
.leadingAddon
/trailingAddon
: For symbols like $
or %
.placeholder
: Example input value.error
: Bound to an error state variable to display validation errors.tooltip
: Adds an info icon with a tooltip next to the label (via the Input
component’s internal handling).useEffect
hook that depends on the state variables holding the parsed input values (e.g., arr
, growthRate
, profitMargin
).isNaN
, positive numbers) before attempting calculations.calculatedMultiple
, valuationRange
).Summary
component (from common/summary.tsx
) to display the key calculated results in a structured format.SummaryItem
objects to pass to the Summary
component’s items
prop. Each item object includes:
label
: Text or ReactNode for the label (can include tooltips via Summary
’s internal handling if needed, though often tooltips are on the Input
instead).value
: The calculated value, often formatted using helper functions (e.g., formatCurrencyCompact
from summary.tsx
). Display ’-’ or similar if not calculated.highlightColor
(optional): Tailwind text color class (e.g., 'text-indigo-600'
, 'text-green-600'
) to emphasize certain results.tooltip
(optional): Adds an info icon with a tooltip next to the summary item label.Summary
like title
(for screen readers), mainTitle
, and mainDescription
.common/
)inputs.tsx
Input
Component:
type="number"
, it displays formatted numbers (commas) but manages the underlying state as a clean numeric string via the parsedValue
in its onChange
. It also attempts to maintain cursor position during formatting.focus-within
on the container).InputGroup
Component:
Input
components responsively. Child Input
components need grid column classes (e.g., sm:col-span-2
).useQueryParamState
Hook:
useState
. The setter updates both the React state and the URL (using history.replaceState
to avoid excessive history entries).typeof window === 'undefined'
).popstate
events to update state if the user navigates via browser buttons.summary.tsx
Summary
Component:
Tooltip
internally to provide optional tooltips for list item labels via the tooltip
property in the items
array.items
array (SummaryItem[]
) defining the label-value pairs to display.highlightColor
property.formatCurrencyCompact
).description.mdx
)index.astro
and rendered, usually styled using @tailwindcss/typography
(prose
classes)./tools/tool-name/
URL. index.astro
renders the layout, static text, and placeholders for React components.Calculator
) specified with client:load
(or similar) hydrates.useQueryParamState
hooks within Calculator
read initial values from the URL query parameters (or use defaults). The component renders with these initial values.Input
fields.Input
component’s onChange
handler provides the parsedValue
to the Calculator
. The Calculator
’s state setter (from useQueryParamState
) updates the React state and the URL query parameter.useEffect
hook in Calculator
, dependent on the input state variables, re-runs. It performs calculations based on the new input values.Summary
component.Calculator
component, updating the Input
values (if formatted) and the Summary
display with the new results.