diff --git a/packages/harmonium/src/components/AppShell/AppShell.tsx b/packages/harmonium/src/components/AppShell/AppShell.tsx index 6164c4a0..749e3a5d 100644 --- a/packages/harmonium/src/components/AppShell/AppShell.tsx +++ b/packages/harmonium/src/components/AppShell/AppShell.tsx @@ -24,8 +24,7 @@ export const AppShell = React.forwardRef( AppShell.displayName = 'AppShell' -export interface AppShellHeaderProps - extends React.HTMLAttributes {} +export type AppShellHeaderProps = React.HTMLAttributes export const AppShellHeader = React.forwardRef< HTMLElement, @@ -42,8 +41,7 @@ export const AppShellHeader = React.forwardRef< AppShellHeader.displayName = 'AppShellHeader' -export interface AppShellSidebarProps - extends React.HTMLAttributes {} +export type AppShellSidebarProps = React.HTMLAttributes export const AppShellSidebar = React.forwardRef< HTMLElement, @@ -60,8 +58,7 @@ export const AppShellSidebar = React.forwardRef< AppShellSidebar.displayName = 'AppShellSidebar' -export interface AppShellMainProps - extends React.HTMLAttributes {} +export type AppShellMainProps = React.HTMLAttributes export const AppShellMain = React.forwardRef( ({className, children, ...props}, ref) => ( @@ -77,8 +74,7 @@ export const AppShellMain = React.forwardRef( AppShellMain.displayName = 'AppShellMain' -export interface AppShellFooterProps - extends React.HTMLAttributes {} +export type AppShellFooterProps = React.HTMLAttributes export const AppShellFooter = React.forwardRef< HTMLElement, diff --git a/packages/harmonium/src/components/Card/Card.tsx b/packages/harmonium/src/components/Card/Card.tsx index ebb9e6a6..76cbb231 100644 --- a/packages/harmonium/src/components/Card/Card.tsx +++ b/packages/harmonium/src/components/Card/Card.tsx @@ -27,7 +27,7 @@ export const Card = React.forwardRef( Card.displayName = 'Card' -export interface CardSectionProps extends React.HTMLAttributes {} +export type CardSectionProps = React.HTMLAttributes export const CardHeader = React.forwardRef( ({className, children, ...props}, ref) => ( diff --git a/packages/harmonium/src/components/CommandPalette/CommandPalette.tsx b/packages/harmonium/src/components/CommandPalette/CommandPalette.tsx index 1e468dc0..6d6fc020 100644 --- a/packages/harmonium/src/components/CommandPalette/CommandPalette.tsx +++ b/packages/harmonium/src/components/CommandPalette/CommandPalette.tsx @@ -32,7 +32,7 @@ function buildGroupedItems(filtered: CommandItem[]): Map for (const item of filtered) { const group = item.group ?? '' if (!groups.has(group)) groups.set(group, []) - groups.get(group)!.push({item, flatIndex, group}) + groups.get(group)?.push({item, flatIndex, group}) flatIndex++ } return groups diff --git a/packages/harmonium/src/components/Dialog/Dialog.tsx b/packages/harmonium/src/components/Dialog/Dialog.tsx index 1c1af3c7..adc11e14 100644 --- a/packages/harmonium/src/components/Dialog/Dialog.tsx +++ b/packages/harmonium/src/components/Dialog/Dialog.tsx @@ -48,7 +48,7 @@ export const Dialog = React.forwardRef( Dialog.displayName = 'Dialog' -export interface DialogHeaderProps extends React.HTMLAttributes {} +export type DialogHeaderProps = React.HTMLAttributes export const DialogHeader = React.forwardRef( ({className, children, ...props}, ref) => ( @@ -59,7 +59,7 @@ export const DialogHeader = React.forwardRef( ) DialogHeader.displayName = 'DialogHeader' -export interface DialogBodyProps extends React.HTMLAttributes {} +export type DialogBodyProps = React.HTMLAttributes export const DialogBody = React.forwardRef( ({className, children, ...props}, ref) => ( @@ -70,7 +70,7 @@ export const DialogBody = React.forwardRef( ) DialogBody.displayName = 'DialogBody' -export interface DialogFooterProps extends React.HTMLAttributes {} +export type DialogFooterProps = React.HTMLAttributes export const DialogFooter = React.forwardRef( ({className, children, ...props}, ref) => ( diff --git a/packages/harmonium/src/components/Field/Field.tsx b/packages/harmonium/src/components/Field/Field.tsx index ec13f1e2..9e67653e 100644 --- a/packages/harmonium/src/components/Field/Field.tsx +++ b/packages/harmonium/src/components/Field/Field.tsx @@ -39,8 +39,7 @@ export const Field = React.forwardRef( Field.displayName = 'Field' -export interface FieldLabelProps - extends React.LabelHTMLAttributes {} +export type FieldLabelProps = React.LabelHTMLAttributes export const FieldLabel = React.forwardRef( ({className, children, ...props}, ref) => { @@ -61,8 +60,7 @@ export const FieldLabel = React.forwardRef( FieldLabel.displayName = 'FieldLabel' -export interface FieldDescriptionProps - extends React.HTMLAttributes {} +export type FieldDescriptionProps = React.HTMLAttributes export const FieldDescription = React.forwardRef< HTMLParagraphElement, @@ -84,8 +82,7 @@ export const FieldDescription = React.forwardRef< FieldDescription.displayName = 'FieldDescription' -export interface FieldErrorProps - extends React.HTMLAttributes {} +export type FieldErrorProps = React.HTMLAttributes export const FieldError = React.forwardRef< HTMLParagraphElement, diff --git a/packages/harmonium/src/components/Menu/Menu.tsx b/packages/harmonium/src/components/Menu/Menu.tsx index a889732c..f6f7cc3c 100644 --- a/packages/harmonium/src/components/Menu/Menu.tsx +++ b/packages/harmonium/src/components/Menu/Menu.tsx @@ -2,7 +2,7 @@ import * as React from 'react' import {clsx} from 'clsx' import styles from './Menu.module.css' -export interface MenuProps extends React.HTMLAttributes {} +export type MenuProps = React.HTMLAttributes export const Menu = React.forwardRef( ({className, children, ...props}, ref) => ( @@ -36,7 +36,7 @@ export const MenuItem = React.forwardRef( MenuItem.displayName = 'MenuItem' -export interface MenuSeparatorProps extends React.HTMLAttributes {} +export type MenuSeparatorProps = React.HTMLAttributes export const MenuSeparator = React.forwardRef( ({className, ...props}, ref) => ( @@ -51,7 +51,7 @@ export const MenuSeparator = React.forwardRef {} +export type MenuLabelProps = React.HTMLAttributes export const MenuLabel = React.forwardRef( ({className, children, ...props}, ref) => ( diff --git a/packages/harmonium/src/components/NumberInput/NumberInput.test.tsx b/packages/harmonium/src/components/NumberInput/NumberInput.test.tsx index bc09f967..af1d0e71 100644 --- a/packages/harmonium/src/components/NumberInput/NumberInput.test.tsx +++ b/packages/harmonium/src/components/NumberInput/NumberInput.test.tsx @@ -37,7 +37,6 @@ describe('NumberInput', () => { }) it('respects max boundary', async () => { - const user = userEvent.setup() const onChange = vi.fn() render() expect(screen.getByLabelText('Increase')).toBeDisabled() diff --git a/packages/harmonium/src/components/PricingTable/PricingTable.tsx b/packages/harmonium/src/components/PricingTable/PricingTable.tsx index 56007634..d74d2d07 100644 --- a/packages/harmonium/src/components/PricingTable/PricingTable.tsx +++ b/packages/harmonium/src/components/PricingTable/PricingTable.tsx @@ -53,8 +53,7 @@ export const PricingCard = React.forwardRef( PricingCard.displayName = 'PricingCard' -export interface PricingCardHeaderProps - extends React.HTMLAttributes {} +export type PricingCardHeaderProps = React.HTMLAttributes export const PricingCardHeader = React.forwardRef< HTMLDivElement, @@ -87,8 +86,7 @@ export const PricingCardPrice = React.forwardRef< PricingCardPrice.displayName = 'PricingCardPrice' -export interface PricingCardFeaturesProps - extends React.HTMLAttributes {} +export type PricingCardFeaturesProps = React.HTMLAttributes export const PricingCardFeatures = React.forwardRef< HTMLUListElement, @@ -126,8 +124,7 @@ export const PricingCardFeature = React.forwardRef< PricingCardFeature.displayName = 'PricingCardFeature' -export interface PricingCardFooterProps - extends React.HTMLAttributes {} +export type PricingCardFooterProps = React.HTMLAttributes export const PricingCardFooter = React.forwardRef< HTMLDivElement, diff --git a/packages/harmonium/src/components/Tabs/Tabs.tsx b/packages/harmonium/src/components/Tabs/Tabs.tsx index 37cd8b1b..eeec66ca 100644 --- a/packages/harmonium/src/components/Tabs/Tabs.tsx +++ b/packages/harmonium/src/components/Tabs/Tabs.tsx @@ -49,7 +49,7 @@ export const Tabs = React.forwardRef( Tabs.displayName = 'Tabs' -export interface TabsListProps extends React.HTMLAttributes {} +export type TabsListProps = React.HTMLAttributes export const TabsList = React.forwardRef( ({className, ...props}, ref) => ( diff --git a/packages/harmonium/src/components/Textarea/Textarea.tsx b/packages/harmonium/src/components/Textarea/Textarea.tsx index 44461e14..dd47555e 100644 --- a/packages/harmonium/src/components/Textarea/Textarea.tsx +++ b/packages/harmonium/src/components/Textarea/Textarea.tsx @@ -3,8 +3,7 @@ import {clsx} from 'clsx' import {useFieldContext} from '../Field' import styles from './Textarea.module.css' -export interface TextareaProps - extends React.TextareaHTMLAttributes {} +export type TextareaProps = React.TextareaHTMLAttributes export const Textarea = React.forwardRef( ({className, ...props}, ref) => { diff --git a/packages/harmonium/src/tokens/build.ts b/packages/harmonium/src/tokens/build.ts index 70398bea..d96a9d5b 100644 --- a/packages/harmonium/src/tokens/build.ts +++ b/packages/harmonium/src/tokens/build.ts @@ -102,7 +102,7 @@ async function build() { const flat = flattenTokens(allTokens) const tsLines = Object.entries(flat) - .map(([key, value]) => { + .map(([key]) => { const constName = key .replace(/[.-]/g, '_') .replace(/([a-z])([A-Z])/g, '$1_$2') diff --git a/packages/harmonium/src/utils/responsive.ts b/packages/harmonium/src/utils/responsive.ts index 01b47358..6d16b098 100644 --- a/packages/harmonium/src/utils/responsive.ts +++ b/packages/harmonium/src/utils/responsive.ts @@ -85,7 +85,7 @@ export function responsiveStyles( for (const bp of BREAKPOINTS) { if (value[bp] !== undefined) { - styles[`--${name}-${bp}`] = fmt(value[bp]!) + styles[`--${name}-${bp}`] = fmt(value[bp] as string | number) } } diff --git a/packages/harmonium_ex/lib/harmonium/components.ex b/packages/harmonium_ex/lib/harmonium/components.ex index 1c291975..57a8edb3 100644 --- a/packages/harmonium_ex/lib/harmonium/components.ex +++ b/packages/harmonium_ex/lib/harmonium/components.ex @@ -189,5 +189,508 @@ if Code.ensure_loaded?(Phoenix.Component) do
""" end + + # --- Stepper --- + + @doc "Renders a step indicator for multi-step flows." + attr :orientation, :string, default: "horizontal", values: ~w(horizontal vertical) + attr :rest, :global + slot :inner_block, required: true + + def hm_stepper(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a single step within a stepper." + attr :state, :string, default: "pending", values: ~w(pending active completed) + attr :rest, :global + slot :inner_block, required: true + + def hm_stepper_step(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a stepper connector line between steps." + attr :rest, :global + + def hm_stepper_connector(assigns) do + ~H""" +
+ """ + end + + # --- Progress --- + + @doc "Renders a progress bar." + attr :value, :integer, default: 0 + attr :max, :integer, default: 100 + attr :size, :string, default: "md", values: ~w(sm md lg) + attr :variant, :string, default: "primary", values: ~w(primary secondary success warning error) + attr :rest, :global + slot :inner_block, required: false + + def hm_progress(assigns) do + ~H""" +
+
+ + <%= render_slot(@inner_block) %> + +
+ """ + end + + # --- Dialog --- + + @doc "Renders a dialog/modal overlay." + attr :open, :boolean, default: false + attr :rest, :global + + def hm_dialog_overlay(assigns) do + ~H""" +
+ """ + end + + @doc "Renders a dialog/modal container." + attr :open, :boolean, default: false + attr :size, :string, default: "md", values: ~w(sm md lg full) + attr :rest, :global + slot :inner_block, required: true + + def hm_dialog(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a dialog header." + attr :rest, :global + slot :inner_block, required: true + + def hm_dialog_header(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a dialog body." + attr :rest, :global + slot :inner_block, required: true + + def hm_dialog_body(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a dialog footer." + attr :rest, :global + slot :inner_block, required: true + + def hm_dialog_footer(assigns) do + ~H""" + + """ + end + + # --- Empty State --- + + @doc "Renders an empty state placeholder." + attr :size, :string, default: "md", values: ~w(sm md lg) + attr :rest, :global + slot :inner_block, required: true + + def hm_empty_state(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders the empty state icon container." + attr :rest, :global + slot :inner_block, required: true + + def hm_empty_state_icon(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders the empty state title." + attr :rest, :global + slot :inner_block, required: true + + def hm_empty_state_title(assigns) do + ~H""" +

+ <%= render_slot(@inner_block) %> +

+ """ + end + + @doc "Renders the empty state description." + attr :rest, :global + slot :inner_block, required: true + + def hm_empty_state_description(assigns) do + ~H""" +

+ <%= render_slot(@inner_block) %> +

+ """ + end + + @doc "Renders the empty state action container." + attr :rest, :global + slot :inner_block, required: true + + def hm_empty_state_action(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Drawer --- + + @doc "Renders a drawer overlay." + attr :open, :boolean, default: false + attr :rest, :global + + def hm_drawer_overlay(assigns) do + ~H""" +
+ """ + end + + @doc "Renders a slide-out drawer panel." + attr :open, :boolean, default: false + attr :side, :string, default: "right", values: ~w(left right) + attr :size, :string, default: "md", values: ~w(sm md lg) + attr :rest, :global + slot :inner_block, required: true + + def hm_drawer(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Typography --- + + @doc "Renders styled text." + attr :size, :string, default: nil + attr :weight, :string, default: nil + attr :color, :string, default: nil + attr :truncate, :boolean, default: false + attr :rest, :global + slot :inner_block, required: true + + def hm_text(assigns) do + ~H""" + + <%= render_slot(@inner_block) %> + + """ + end + + @doc """ + Renders a styled heading. + + Renders as `

` by default. For other heading levels, use the CSS + class directly: `

`. + """ + attr :size, :string, default: "lg" + attr :weight, :string, default: "bold" + attr :color, :string, default: nil + attr :rest, :global + slot :inner_block, required: true + + def hm_heading(assigns) do + ~H""" +

+ <%= render_slot(@inner_block) %> +

+ """ + end + + # --- Data Grid --- + + @doc "Renders a data grid wrapper for horizontal scroll." + attr :rest, :global + slot :inner_block, required: true + + def hm_data_grid_wrapper(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a data grid (table)." + attr :striped, :boolean, default: false + attr :hoverable, :boolean, default: false + attr :rest, :global + slot :inner_block, required: true + + def hm_data_grid(assigns) do + ~H""" + + <%= render_slot(@inner_block) %> +
+ """ + end + + # --- File Upload --- + + @doc "Renders a file upload drop zone." + attr :disabled, :boolean, default: false + attr :rest, :global + slot :inner_block, required: true + + def hm_file_upload(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders the file upload placeholder content." + attr :rest, :global + slot :inner_block, required: true + + def hm_file_upload_placeholder(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Toast --- + + @doc "Renders a toast container." + attr :position, :string, default: "top-right", values: ~w(top-right top-left bottom-right bottom-left) + attr :rest, :global + slot :inner_block, required: true + + def hm_toast_container(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a toast notification." + attr :variant, :string, default: "info", values: ~w(info success warning error) + attr :rest, :global + slot :inner_block, required: true + + def hm_toast(assigns) do + ~H""" +
+
+ <%= render_slot(@inner_block) %> +
+
+ """ + end + + # --- Popover --- + + @doc "Renders a popover container." + attr :rest, :global + slot :inner_block, required: true + + def hm_popover(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders popover content." + attr :side, :string, default: "bottom", values: ~w(top bottom left right) + attr :rest, :global + slot :inner_block, required: true + + def hm_popover_content(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Tooltip --- + + @doc "Renders a tooltip container." + attr :rest, :global + slot :inner_block, required: true + + def hm_tooltip(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders tooltip content." + attr :side, :string, default: "top", values: ~w(top bottom left right) + attr :rest, :global + slot :inner_block, required: true + + def hm_tooltip_content(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Skeleton --- + + @doc "Renders a skeleton loading placeholder." + attr :variant, :string, default: "text", values: ~w(text circular rectangular) + attr :rest, :global + + def hm_skeleton(assigns) do + ~H""" +
+ """ + end + + # --- Stat --- + + @doc "Renders a stat display card." + attr :rest, :global + slot :inner_block, required: true + + def hm_stat(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders the stat value." + attr :rest, :global + slot :inner_block, required: true + + def hm_stat_value(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders the stat label." + attr :rest, :global + slot :inner_block, required: true + + def hm_stat_label(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + # --- Tabs --- + + @doc "Renders a tabbed interface container." + attr :rest, :global + slot :inner_block, required: true + + def hm_tabs(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a tab trigger list." + attr :rest, :global + slot :inner_block, required: true + + def hm_tabs_list(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + + @doc "Renders a tab trigger button." + attr :active, :boolean, default: false + attr :rest, :global + slot :inner_block, required: true + + def hm_tabs_trigger(assigns) do + ~H""" + + """ + end + + @doc "Renders tab content." + attr :active, :boolean, default: false + attr :rest, :global + slot :inner_block, required: true + + def hm_tabs_content(assigns) do + ~H""" +
+ <%= render_slot(@inner_block) %> +
+ """ + end + end end