# Changelog

All notable changes to this project are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).

## [0.0.78] - 2026-05-29

- Added `ExpandableCheckbox` — checkbox row with collapsible long label and Read more / Read less toggle.
- Added `FullscreenModal` — full-screen modal with parallax hero, close button, disclaimer, and action footer slots.
- Added `MessageField` — multi-line textarea with FormField States (Idle / Active / Read Only / Error / Disabled) and Form context integration.
- Added `SuggestiveSearch` — search input with inline suggestion dropdown.
- `Accordion` — new `contained` variant and hover state handling.
- `NavArrow` — pressable when `onPress` is provided; 44 pt touch target.
- `Checkbox` — 44 pt touch target; checkmark rendered inside the box view.
- `FormField` / `TextInput` — tap anywhere in the input row to focus on first tap (Android double-tap fix).
- `ActionFooter` — Android keyboard counter-shift so the footer stays behind the keyboard instead of jumping up.
- `ListItem` — title/support text use `AppearanceBrand: 'Neutral'` without forcing it onto slot children.
- `Stepper` / `Step` / `StepLabel` — `showLine`, `metaText`, `meta` props; larger indicator sizing; public type exports.
- `Title` — line-height clamp to prevent descender clipping on Android.

---

## [0.0.77] - 2026-05-25

### Added

- **Skeleton / shimmer loading system** — new `src/skeleton/` module exported from the package root. One context provider (`<SkeletonGroup loading>`) flips an entire subtree into shape-preserving placeholders. The following primitives auto-skeletonize when inside an active group: `Text`, `Image`, `Badge`, `Button`, `IconButton`, `Avatar`, and `Icon`. Each one also accepts a per-instance `loading?: boolean` override. The atomic `<Skeleton kind="text|image|badge|other" width height />` block is available for non-primitive layouts. Honours `prefers-reduced-motion` automatically; one Reanimated clock per group; gradient angle locked to 135° on any aspect ratio.
- **`LottiePlayer`** — new component that renders Lottie animations using `lottie-react-native` (native) and `lottie-react` (web) via platform extensions (`LottiePlayer.web.tsx`). Both libraries are declared as **optional peer dependencies**, so installing `jfs-components` does not pull them in. Sizing is token-driven via `media/width` / `media/height` (`Media / Output` mode → `L | M | S` = `117 / 70 / 20`) — same contract `PageHero` and `LottieIntroBlock` already use for their media slots.
- New on-device dev-client scaffold under `example/` (Expo dev client + Storybook + Playground host), with its native deps pinned independently from the library.

### Changed

- `ActionFooter` rewritten (~240 lines): `IconButton` children keep their intrinsic square size, every other child is auto-stretched to share the remaining horizontal space; modes now propagate to all slot children via `cloneChildrenWithModes`.
- `PageHero` API expansion (+65 lines): improved Figma alignment, new examples in stories/MDX.
- `RechargeCard` re-aligned to Figma node `2235:937`: container now draws the `rechargeCard/strokeWidth` + `rechargeCard/stroke/color` border, fallback colors for title/spec/disclaimer reset to `#000000`, `minWidth` falls back to `312`, and background fallback is now `#ffffff`. Hardcoded label/style overrides removed — colors come purely from tokens.
- `RechargeCard` now ships sensible slot mode defaults (overridable via the consumer's `modes` prop):
  - inner `MoneyValue` defaults to `Context3: 'Balance & Cards'` (36 px / 900-weight Figma scale).
  - inner `ButtonGroup` defaults to `AppearanceBrand: 'Secondary'`, `Button / Size: 'S'`, `Emphasis: 'High'`. The previous typo (`Appearance.Brand`) is fixed to match the actual `AppearanceBrand` collection, and defaults are spread *before* `modes` so any key can be overridden externally.
- Figma/token sync pass across `ActionFooter`, `DropdownInput`, `FormField`, `HStack`, `InputSearch`, `Nudge`, `Section`, `Stepper/StepLabel`, `SupportText/SupportTextIcon`, `VStack`, plus refreshed Coin Variables tokens, `.token-metadata.json`, and icons registry.
- Storybook config: babel-transpile `storybook-assets/` alongside `src/` so Lottie + platform-extension demo files resolve cleanly; `tsconfig.build.json` now excludes `storybook-assets/`.

### Library usage notes

#### Lottie animations

`LottiePlayer` is the only Lottie surface — `lottie-react-native` / `lottie-react` are optional peers and must be installed by the consumer:

```sh
# React Native (iOS / Android)
npm install lottie-react-native
cd ios && pod install

# Web (or react-native-web)
npm install lottie-react
```

Then pass a **parsed JSON object** (URI sources are intentionally not supported — web players need data pre-parsed):

```tsx
import { LottiePlayer, PageHero, LottieIntroBlock } from 'jfs-components'
import animation from './assets/loader.json'

<LottiePlayer source={animation} />                          // 117×117 (default)
<LottiePlayer source={animation} size={70} />                // explicit size
<LottiePlayer source={animation} modes={{ 'Media / Output': 'S' }} /> // 20×20 via token
<PageHero media={<LottiePlayer source={animation} />} />     // any media slot
<LottieIntroBlock mediaSlot={<LottiePlayer source={animation} />} />
```

#### Skeleton / shimmer loading

Wrap any subtree in `<SkeletonGroup loading={!data}>` — **no other changes to the screen are required**. All instrumented JFS primitives inside (`Text`, `Image`, `Badge`, `Button`, `IconButton`, `Avatar`, `Icon`) automatically swap themselves for a shape-preserving shimmer placeholder while `loading` is `true`, and revert to their real content when it flips to `false`. Layout never jumps because the placeholder reuses the same measured box as the real element.

```tsx
import { SkeletonGroup, Card, Image, Text, Badge, Button } from 'jfs-components'

function Profile({ user }) {
  return (
    <SkeletonGroup loading={!user}>
      <Card media={<Image imageSource={user?.cover} ratio={16 / 9} />}>
        <Card.Title>{user?.name}</Card.Title>
        <Card.SupportText>{user?.role}</Card.SupportText>
        <Badge label={user?.tier ?? 'Member'} />
        <Button label="Follow" />
      </Card>
    </SkeletonGroup>
  )
}
```

Two escape hatches when the auto-instrumentation isn't enough:

```tsx
// 1) Bind a single primitive to its own load event, ignoring the group:
<Image imageSource={uri} ratio={16/9} loading={!loaded} onLoad={() => setLoaded(true)} />

// 2) Drop in atomic <Skeleton/> blocks for layouts not built on JFS primitives.
//    Inside an active <SkeletonGroup> they shimmer with the same clock; outside one they render nothing.
import { Skeleton } from 'jfs-components'
<SkeletonGroup loading>
  <View style={{ flexDirection: 'row', gap: 12 }}>
    <Skeleton kind="other" width={48} height={48} />
    <View style={{ flex: 1, gap: 6 }}>
      <Skeleton kind="text" width="80%" height={16} />
      <Skeleton kind="text" width="60%" height={14} />
    </View>
  </View>
</SkeletonGroup>
```

Reduced motion is auto-detected from the OS (`AccessibilityInfo` on native, `prefers-reduced-motion` on web); override via `<SkeletonGroup reducedMotion>` for testing or forced behaviour.

---

## [0.0.74] - 2026-05-19

- Added new account and onboarding components: `AccountCard` (connected/add variants), `PageHero`, `LottieIntroBlock`, and `PoweredByLabel`.
- Added new selection primitives: `Dropdown` + `DropdownItem` (compound API) and `DropdownInput` (options-based form control).
- Added Storybook coverage for the existing `Icon` primitive (no API change).
- Major `FormField` rewrite — re-bound directly to React Native `TextInput` (no longer wraps `TextInput` component), gained `number` / `phone` / `url` types, `Form` context integration, and improved Figma/token alignment.
- Compound `Tooltip` upgrade — `TooltipContent` now acts as a vertical slot with `gap` and `alignItems` props, and exports `TooltipPlacement` / trigger / content types from the package barrel.
- `Text` now accepts JSX `children` (drop-in `<Text>label</Text>` works) in addition to the `text` prop; `AppBar` middle slot lays out via `flex: 1` with `space-between` fallback when absent.
- `CheckboxItem` gained a `control` prop (`leading` / `trailing`) that swaps the checkbox and the end slot to either edge of the row.
- `CardBankAccount` ships sensible default `Button / Size` + Appearance modes so the footer button matches Figma out of the box.
- Figma/token sync pass across `FormField`, `CardBankAccount`, `CardFinancialCondition`, `CheckboxItem`, `CircularProgressBar`, `CoverageBarComparison`, `DonutChart`, `DonutChartSummary`, `Gauge`, `LinearProgress`, `MetricLegendItem`, `MonthlyStatusGrid`, `Nudge`, `RangeTrack`, `SavingsGoalSummary`, and `SegmentedTrack`; refreshed Coin Variables tokens, `.token-metadata.json`, and icons registry.
- Repo cleanup: removed the legacy `example/` app, `.expo/` artifacts, and the `example` / `example:install` scripts; updated `tsconfig.build.json` and `scripts/generate-component-docs.js`; added `.tgz` and `.expo/` to `.gitignore`.

---

## [0.0.73] - 2026-05-15

- Added new chart, progress, and coverage components: `DonutChart`, `DonutChartSummary`, `LinearProgress`, `SavingsGoalSummary`, `CoverageRing`, `CoverageBarComparison`, `MonthlyStatusGrid`, `MetricLegendItem`, `RangeTrack`, `SegmentedTrack`, `StatGroup`, `StrengthIndicator`, and `SummaryTile`.
- Added new form/selection primitives: `AccordionCheckbox`, `CheckboxGroup`, `CheckboxItem`, and `Radio` (renamed from `RadioButton`; the old name is kept as a deprecated re-export).
- Added new card and badge components: `CardBankAccount`, `CardInsight`, `ProductOverview`, and `BrandChip`.
- Major `OTP` overhaul (new API, stories, and MDX) and `LinearMeter` refactor; `Carousel` pagination/maxHeight refresh and `HoldingsCard` migration to `Radio`.
- Enhanced `CircularProgressBar` with an in-ring `supportText` caption (driven by `circularProgressBar/supportText/*` tokens) and a centered text stack; reworked `StatItem` API and stories.
- Token surface refresh: added `XS` to `Button / Size` (surfaced on `Button`, `CardCTA`, `IconButton`); split `LinearProgress` / `SavingsGoalSummary` `Appearance / DataViz` into `Appearance Type` + `AppearanceBrand` + `AppearanceSystem` and renamed `Emphasis / DataViz` → `Emphasis`; added `Auto` status + `FormField States` to `SupportText`, `SupportTextIcon`, `InputSearch`; added `Error` state and `Color Mode` to `FormField`; added `Page type` (MainPage / SubPage) to `Title`.
- New `number-utils` (+ tests) exported from `src/utils`; refreshed icons registry, Coin Variables tokens, and `.token-metadata.json`.
- Expanded Storybook and MDX coverage for all new and updated components, plus docs for `HStack`, `VStack`, and `Section`.

---

## [0.0.72] - 2026-05-04

High-level summary (one line per point):

- **`RadioButton` renamed to `Radio`** — naming aligned with the rest of the library; the old `RadioButton` import is preserved as a deprecated alias so consumer code keeps working without changes.
- **`Carousel` pagination is now fully token-driven** — dot size, active width, and active/inactive colors come from design tokens (`carousel/pagination/indicator/{size,activeWidth,activeColor,inactiveColor}`) instead of hardcoded values, with token names normalized to camelCase.
- **`Carousel` outer container height is now controlled by a token** — new `carousel/maxHeight` token enforces the Figma-aligned max height of the carousel viewport.
- **`HoldingsCard` migrated to `Radio`** — internal-only swap, no API change for consumers.

### Changed

- `Carousel.Pagination` now reads `carousel/pagination/indicator/size`, `carousel/pagination/indicator/activeWidth`, `carousel/pagination/indicator/activeColor`, and `carousel/pagination/indicator/inactiveColor` from design tokens. Previous lowercase names (`activecolor`, `inactivecolor`) and hardcoded dot dimensions (6×6 / 16×6) are replaced.
- `Carousel` outer container respects a new `carousel/maxHeight` token (defaults to `280`) applied as `maxHeight` on the outer view.
- `HoldingsCard` now imports the new `Radio` component instead of `RadioButton` (no behavioral change).

### Added

- New `Radio` component (`src/components/Radio/Radio.tsx`) with its own stories and MDX docs. Exported from the package barrel as `Radio` / `RadioProps`.

### Deprecated

- `RadioButton` is deprecated. It is kept as a thin re-export of `Radio` for backward compatibility and will be removed in a future major release. Migrate to `import { Radio } from 'jfs-components'`.

---

## [0.0.71] - 2026-05-04

### Added

- **`CardAdvisory`** — new card component with stories and MDX docs.
- **`CardFinancialCondition`** — new card component with stories and MDX docs.
- **`CircularProgressBar`** — new circular progress component with stories and MDX docs.
- **`CircularProgressBarDoted`** — new dotted variant of the circular progress component with stories and MDX docs.
- **`CircularRating`** — new circular rating display component with stories and MDX docs.
- **`Gauge`** — new gauge component with stories and MDX docs.
- **`InstitutionBadge`** — new institution badge component with stories and MDX docs.
- All new components are exported from the package barrel with their public types.

### Changed

- **`Nudge`** — significant rewrite (props, layout, and token usage refreshed); story file simplified.
- **`CardCTA`** — expanded API and visual variants; story coverage extended.
- **`MediaCard`** — minor doc/story refresh; no breaking changes.
- **`ListGroup`** — small adjustments to internal layout; no API change.
- Refreshed component token metadata and the icon registry; updated `Coin Variables` design-token JSON.
- Storybook plumbing: added `.storybook/storybook-test-mock.js` and registered it in `.storybook/main.js` to stabilize Storybook test runs.

---

## [0.0.70] - 2026-04-23

### Fixed

- **`MediaCard.Footer` blur on bare React Native:** `0.0.69` shipped the glass footer using `expo-blur`, which silently required consumers to integrate Expo Modules autolinking (`use_expo_modules!` in `Podfile`, `ExpoModulesPackage` on Android). Bare React Native apps that just installed the library and ran `pod install` hit two failures: a runtime "`Cannot read property 'BlurView' of undefined`" red box on Android and an iOS Xcode build failure after `pod install`. **Replaced `expo-blur` with [`@react-native-community/blur`](https://github.com/Kureev/react-native-blur)** — a regular React Native native module handled by standard autolinking. No Expo runtime required.

### Changed

- **`MediaCard.Footer` glass implementation:** Native blur now lives in a small platform-split helper, `MediaCard/GlassFill.tsx` (iOS + Android via the community blur module) and `MediaCard/GlassFill.web.tsx` (`backdrop-filter` via inline style). Metro picks the correct file per platform, so the web bundle never imports the native-only blur module. The Footer's API and design-token contract (`blur/minimal/background`, `blur/minimal`, `Contrast Context` mode) are unchanged.

### Removed

- **`expo-blur`** is no longer a runtime dependency.

### Dependencies

- **`@react-native-community/blur`** (`>=4.4.0`) added as a **peer dependency**. Consumers must install it once: `npm install @react-native-community/blur && cd ios && pod install`. On Expo, use `npx expo install @react-native-community/blur` and prebuild.

---

## [0.0.69] - 2026-04-22

### Changed

- **`MediaCard.Header` / `MediaCard.Footer`:** Both are now **absolutely positioned overlays**. The footer is pinned to the bottom of the card with `zIndex: 2`, so a title that wraps to any number of lines (or overflows the card entirely) **never pushes the footer**. The footer is also painted above the header. Header/footer wrappers use `pointerEvents="box-none"` so taps still land on interactive children inside.
- **`MediaCard.Footer` glass effect:** Replaced the previous solid-color "glass" with a real native blur via `expo-blur`'s `BlurView`. iOS uses `UIVisualEffectView` (true OS-level live blur). Android opts into `experimentalBlurMethod="dimezisBlurView"` for hardware-accelerated `RenderEffect` blur on Android 12+, with an automatic tinted-scrim fallback below that — plus a subtle additive overlay on Android only to add texture and avoid a flat look. Web continues to use `backdrop-filter` (now via the same `BlurView` API). Tint adapts to the `Contrast Context` mode.

### Added

- **`MediaCard.LongTitleDoesNotPushFooter` story:** Stress test demonstrating that a wrapping multi-line title leaves the footer pinned to the bottom.

### Dependencies

- **`expo-blur`** added as a runtime dependency (compatible with Expo SDK 54). Required by the new `MediaCard.Footer` glass implementation. Consumers using the prebuilt iOS/Android binaries from Expo SDK 54 already have the native module available; bare React Native consumers need to run a dev client / pod install after upgrading.

---

## [0.0.68] - 2026-04-22

### Added

- **`Image`:** New shared raster primitive (`imageSource`, optional `ratio`, defaults to `16 / 9` so layout has a stable height without a magic `minHeight`). Supports explicit `width` + `height` to opt out of `aspectRatio` and fill a parent. Exported from the package barrel.

### Changed

- **`MediaCard`:** Background uses the shared `Image` via `imageSource` and `ratio`; card height follows the image in normal flow (no fixed `minHeight`). Title/footer content is an `absoluteFill` overlay with `pointerEvents="box-none"`. Optional `media` escape hatch unchanged (custom node sizes itself).

---

## [0.0.67] - 2026-04-22

### Fixed

- **`MediaCard` in `Carousel` (Yoga / Android):** `MediaCard.Header` no longer uses `flex: 1` shorthand (`flexBasis: 0`), which made the header size to zero on the first layout pass when the card sat in a height-unbounded parent (e.g. horizontal carousel content). Replaced with explicit `flexGrow: 1`, `flexShrink: 0`, `flexBasis: 'auto'` so the header has a content-based floor, matching the Yoga fix pattern used for `CardCTA` right-wrap.

---

## [0.0.65] - 2026-04-21

### Added

- **`Drawer` state callback:** Optional `onStateChange?: (state: 'collapsed' | 'expanded') => void` runs when the drawer settles into a new snap state (after gestures), so parents can react programmatically.

---

## [0.0.64] - 2026-04-20

### Added

- **`Section.Bento` — expandable grid:** New built-in expand/collapse UX. Pass `collapsedCount` (default `4`) plus the full set of cells in `navSlot`, and `Section.Bento` auto-injects a toggle cell, owns the `expanded` boolean (uncontrolled), and animates extra cells in/out with a staggered fade cascade. Supports controlled mode via `expanded` + `onExpandedChange`, customizable labels (`toggleMoreLabel`, `toggleLessLabel`) and icons (`toggleMoreIcon`, `toggleLessIcon`), and a `renderToggle` escape hatch for non-`ListItem` toggles. The toggle is only injected when `navSlot.length > collapsedCount`, so existing usage at or below the threshold renders unchanged.
- **Drawer drop-shadow tokens:** New design tokens `drawer/shadow/primary/{offsetX,offsetY,blur,color}` and `drawer/shadow/secondary/{offsetX,color}` resolve a layered Figma drop shadow. Web stacks both shadows via `boxShadow`; iOS applies the primary shadow natively; Android uses elevation.

### Changed

- **`SlotGrid` sizing — first-row-anchored:** `SlotGrid` (used by `Section.Bento`) now measures only the first row's cells once on mount and applies that max width to every cell in every row. Previously every cell was measured every render, which caused width jumps when the cell count changed (expand/collapse) and could cancel `Animated.View` `entering` cascades by re-rendering them in the same React batch as the mount. Result: stable cell width across toggles, no layout shift, animations play uninterrupted. Edge-flush behavior (first cell flush-left, last cell flush-right) is preserved via `justify-content: space-between`.
- **`Section.Bento` height transition:** The section grows and shrinks via an explicit measured-height spring (`overflow: 'hidden'` clip + `withSpring`) instead of `LinearTransition`. Cells inside are never resized during the animation — only the container's clip rectangle interpolates.
- **Drawer gesture policy:** Pan now requires a 10px vertical drag to activate (`activeOffsetY([-10, 10])`) and surrenders the gesture entirely after ~16px of horizontal movement (`failOffsetX([-16, 16])`). A defense-in-depth check in `onUpdate` skips Y translation on frames where horizontal motion dominates. This lets horizontal children (carousels, sliders, horizontal `FlatList`s) inside the drawer scroll cleanly without the sheet also translating.

### Fixed

- **Drawer iOS shadow clipping:** The sheet now renders content inside an inner clip layer so `overflow: 'hidden'` no longer trims the outer view's drop shadow on iOS.

### Accessibility

- All new `Section.Bento` animations honor the OS reduce-motion setting via Reanimated's `ReduceMotion.System`.

---

## [0.0.63] - 2026-04-20

### Performance

- **Pressable / scroll flicker (native):** `Button`, `Section` (header), `ListItem`, `Avatar`, `IconCapsule`, `NavArrow`, `UpiHandle`, `IconButton`, and `BottomNavItem` no longer mirror transient press state in React. Pressed visuals use `Pressable`’s `style` callback (host-view updates). On iOS, `unstable_pressDelay={130}` reduces accidental “pressed” flashes when a parent `ScrollView` cancels the gesture.
- **Token resolution:** `getVariableByName` caches serialized mode keys per `modes` object via a `WeakMap` in `serializeModes`, avoiding repeated sort/join work when the same `modes` reference is used for many lookups in one render.
- **Stable default modes:** Shared frozen `EMPTY_MODES` from `src/utils/react-utils.ts` replaces inline `modes = {}` defaults across components so default props do not allocate a new object every render (better `React.memo` hits and resolver cache locality).
- **Memoization:** Heavy token reads are batched in `useMemo` where refactored; several interactive components are wrapped in `React.memo`.

### Fixed

- Intermittent visual flicker on interactive rows (e.g. People / Setup style cards) during vertical scroll on native devices.

### Changed

- **Breaking (none intended):** Public prop types and default behavior are preserved. Some components now ignore unused `accessibilityLabel` props on inner nodes (documented via `_accessibilityLabel` where applicable); screen-reader behavior should match prior intentional `accessibilityLabel={undefined}` usage.

### Removed

- Dead code: duplicate `usePressableWebSupport` usage in `ListItem`; unused computed labels in a few components; redundant pressed-style paths in `IconButton`.

### Repository / tooling

- Storybook and example app layout updates; addon and config changes as reflected in the repo.

---

## [0.0.62] and earlier

See git history for releases prior to this changelog.
