Skip to content

[NO-ISSUE] feat: add InputGroup composition component with Addon sub-component#709

Open
isaquebock wants to merge 12 commits into
devfrom
feat/input-group
Open

[NO-ISSUE] feat: add InputGroup composition component with Addon sub-component#709
isaquebock wants to merge 12 commits into
devfrom
feat/input-group

Conversation

@isaquebock

Copy link
Copy Markdown
Contributor

Summary

Adds a new input-group webkit component implementing Figma node 3714-10802. Composition pattern with compound API — <InputGroup> root wraps a middle input primitive and accepts <InputGroup.Addon> sub-components on either side (position derived from source order).

  • Props on root: invalid, required (both boolean, default false).
  • Border states: default → --border-default; invalid--danger-border; invalid + required--warning-border.
  • Provides InputGroupContextKey (marker symbol) via inject/provide so future input primitives can render borderless inside the group.
  • Vertical seam between addons uses [&:not(:last-child)]:border-r [&:not(:first-child)]:border-l — no <Divider> element required, no v-node inspection.
  • Sub-component InputGroup.Addon: no props, default slot for icon/text/button content.
  • Middle input: v1 expects a raw <input> styled border-0 outline-none focus:ring-0. Follow-up PR will teach input-text to detect the group context and self-flatten its border (bump input-text spec_version).

Notes

New exports (flat, per .claude/rules/imports.md)

  • @aziontech/webkit/input-group — compound (leads the docs, Object.assign root + Addon)
  • @aziontech/webkit/input-group-root — tree-shakeable standalone root
  • @aziontech/webkit/input-group-addon — standalone sub-component

Files added

  • .specs/input-group.md (status: implemented)
  • packages/webkit/src/components/inputs/input-group/input-group.vue
  • packages/webkit/src/components/inputs/input-group/input-group-addon/input-group-addon.vue
  • packages/webkit/src/components/inputs/input-group/injection-key.ts
  • packages/webkit/src/components/inputs/input-group/index.ts
  • apps/storybook/src/stories/components/inputs/input-group/InputGroup.stories.js (6 stories: Default, WithLeftAddon, WithRightAddon, BothAddons, Invalid, InvalidRequired)

Pipeline results

  • spec-validator ✓ · reuse-auditor ✓ · scaffolder ✓ · storybook-writer ✓ · echo-reporter ✓ (parity)
  • pnpm webkit:lint ✓ · type-check ✓ · type-coverage 99.96% ✓ · storybook:build 11.41s ✓
  • No new dependencies. No breaking changes. Version bump: minor.

Follow-ups (separate PRs)

  1. Install @figma/code-connect and add input-group.figma.ts mapping.
  2. Bump input-text spec: inject(InputGroupContextKey, false) → render borderless when nested.

Test plan

  • pnpm storybook:dev — open Components/Inputs/InputGroup, verify all 6 stories render 1-to-1 with the Figma design.
  • Test each addon combination in the browser (no addons / left only / right only / both).
  • Confirm invalid alone shows red border; invalid + required shows yellow border.
  • Confirm "Show code" in Storybook = a runnable SFC identical to the canvas (.claude/rules/storybook-source.md).
  • Confirm tree-shaking: @aziontech/webkit/input-group-root imported alone does not pull in the addon.

…component

Implements Figma node 3714-10802. Composition pattern with compound API:
InputGroup root + InputGroup.Addon sub-component. States driven by
data-invalid / data-required attributes (danger border when invalid alone,
warning border when invalid + required). Provides InputGroupContextKey via
inject/provide so future input primitives can render borderless inside the
group in a follow-up PR.
@isaquebock isaquebock requested a review from a team as a code owner July 1, 2026 14:17
isaquebock added 10 commits July 1, 2026 11:35
…text pattern

Independent invalid/required semantics (invalid -> danger, required ->
warning), focus-within ring on the group root, hover border-strong, and
transition-colors with motion-reduce fallback. Spec States/Motion/Tokens/
Accessibility sections updated to match; checksum refreshed.
…ch addons

The stock <input> in the middle slot was rendering at browser default
(~16px) while the addons use text-label-sm (12px), producing a size
mismatch. Add text-label-sm + placeholder:text-[var(--text-muted)] so the
middle input matches both the addons and input-text's inner input font.
Spec and stories updated together; checksum refreshed.
…up addons

The <input> in the middle slot inherited the group's --bg-surface and
used --text-default, making it read visually distinct from the addons
(which use --bg-canvas and --text-muted). Align both so left addon,
middle input, and right addon share the same dark fill and muted text —
one continuous surface behind the group's outer border.
…he darker islands

Previous commit incorrectly aligned the middle input's background to
--bg-canvas alongside the addons, flattening the group visually. The
Figma design has the addons as darker --bg-canvas islands on either
side of the middle input, which inherits the root's --bg-surface. Also
restores --text-default for the input's typed value while keeping the
muted placeholder. Spec and stories updated together; checksum
refreshed.
…licit border color

The previous [&:not(:last-child)]:border-r / [&:not(:first-child)]:border-l
pattern relied on Tailwind arbitrary variants that were not producing a
visible seam. Switch to Tailwind's built-in first: and last: variants
(first-child addon = left, gets border-r toward the input; last-child
addon = right, gets border-l toward the input) and force color
interpretation with border-[color:var(--border-default)] so the border
color applies unambiguously. Spec updated to match.
…SlotRight sub-components

Instead of a single positional Addon sub-component that inferred left
or right from source order and drew its seam via first:/last: CSS
selectors, expose two explicit sub-components — InputGroupSlotLeft and
InputGroupSlotRight. Each hardcodes its own seam (SlotLeft: border-r,
SlotRight: border-l) and its own testid fallback, making position
unambiguous at the template level and eliminating any dependence on
CSS pseudo-class resolution.

Compound API becomes <InputGroup.SlotLeft>...</InputGroup.SlotLeft>
and <InputGroup.SlotRight>...</InputGroup.SlotRight>. The exports map
gains ./input-group-slot-left and ./input-group-slot-right in place
of ./input-group-addon. Spec, stories, and package.json all updated
together; checksum refreshed to aefeab87... No consumers have shipped
against the Addon API yet (pre-release, still on the open feat
branch).
… / #right slots

Align InputGroup's API with input-text, input-number, and field-text —
which all use named slots on the root (iconLeft/iconRight, prefix/
suffix) instead of a compound API with sub-components. The named-slot
approach eliminates:

- The compound layer (index.ts + Object.assign).
- The injection-key.ts marker (nobody was consuming it yet).
- The two sub-component files, their per-slot exports, and the
  tree-shaking -root export.
- The JIT scan surprise on newly-created files (all classes now live
  on a single .vue that Tailwind already tracks).

The visual behavior is unchanged — left slot still bg-canvas +
border-r toward the input, right slot bg-canvas + border-l, root
still owns focus-within ring / hover strong / invalid+required
border colors.

spec_version bumped to 2 (structure changed from composition to
monolithic), body re-authored, checksum refreshed to a875eb1a...
Stories updated to use <template #left> / <template #right>.
…WithIcon story

InputGroup gains a disabled prop (spec bump 2 to 3) with data-disabled
mirror, aria-disabled on the root, and disabled-state tokens borrowed
from input-text: bg-disabled fill, text-disabled color, not-allowed
cursor, focus-within ring suppressed, hover ignored. Story WithIcon
added showing PrimeIcons in the side slots (pi pi-globe and pi
pi-times) so consumers see how icons inhabit the slots at label-sm
size. Story Disabled added.

New FieldInputGroup component mirrors FieldText verbatim, adapted to
the InputGroup primitive: renders its own middle <input>, exposes only
left and right slots forwarded to InputGroup, computes helperKind via
disabled greater invalid greater required greater helper precedence,
auto-ids via useId, wires aria-describedby to HelperText. Six stories
Default, WithSlots, Required (reactive validation flow), Invalid,
Disabled, Icons.

Package export ./field-input-group added; lint, type-check, type-
coverage 99.96%, storybook:build all green.
… indicator

The Required tag (severity=warning size=small) is replaced by inline
text 'asterisk + Required', where the asterisk uses var(--primary)
(orange F3652B) and the word Required uses var(--text-muted). The
asterisk is decorative (aria-hidden=true) so screen readers announce
only 'Required' alongside the label text.

Applies uniformly across every consumer of Label with required=true:
field-text, field-password, field-input-group, field-checkbox,
field-radio, field-switch, field-textarea. Tag component is no longer
imported by label.vue.

Spec body updated (Purpose, Props JSDoc, Tokens, Accessibility);
checksum refreshed to e261b72f (also fixes a pre-existing checksum
drift on label.md that the enforce-spec-exists hook flagged when this
change tried to write).
…parentheses

Wrap the required word in parentheses and add a space between the
asterisk and the parenthesized text, producing '* (Required)' instead
of the previous '*Required'. The asterisk stays orange (var(--primary))
and (Required) stays muted (var(--text-muted)); the * remains
aria-hidden so screen readers still announce only '(Required)'.

Spec Purpose, Props JSDoc, Tokens, and Accessibility sections updated
to match; checksum refreshed.

@robsongajunior robsongajunior left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The input group the examples it's wrong.

  • The sample default is with slot.
  • Use the Primevue like example

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants