Skip to content

UI slider animation update#56

Merged
Its4Nik merged 4 commits intodevfrom
ui-slider-animation-update
Jan 8, 2026
Merged

UI slider animation update#56
Its4Nik merged 4 commits intodevfrom
ui-slider-animation-update

Conversation

@Its4Nik
Copy link
Copy Markdown
Owner

@Its4Nik Its4Nik commented Jan 8, 2026

Summary by Sourcery

Refactor the Slides UI component into smaller, reusable pieces with improved slide animation behavior and internal state management, while adding internal host tracking for Docker client events and tightening typings and exports across UI and Docker client modules.

New Features:

  • Introduce a reusable slides state hook and supporting header, button row, and content components to handle slide selection, collapse, and animated transitions.
  • Add internal Docker client event handling to automatically track host IDs on host-added and host-removed events.
  • Export the table Column type and centralize Slides and Table exports for easier consumption in the UI library.

Bug Fixes:

  • Ensure worker host IDs are consistently added to the wrapper when creating hosts so internal tracking stays in sync.

Enhancements:

  • Refine slide animation behavior using measured content height and improved framer-motion variants for smoother transitions.
  • Simplify WorkersTable column render functions by relying on generic value typing with explicit casting where needed.
  • Remove redundant event registry logging in DockerClientManagerCore to reduce log noise.
  • Align Docker client typings for host events and clarify Slides props by moving them into a dedicated types module.

Its4Nik and others added 3 commits January 8, 2026 16:37
Refactors the `Slides` component into a more maintainable and composable architecture.

This commit introduces:
- A new `useSlidesState` hook to centralize all slide state management, including active slide, animation direction, and collapse logic.
- `ButtonRow` component for rendering slide navigation buttons, supporting both connected and disconnected styles.
- `SlidesHeader` and `MinimalSlidesHeader` components to encapsulate header and button row rendering for default and minimal variants, respectively.
- `SlideContent` component (implied for content rendering) to handle slide transitions and collapse animations using Framer Motion.
- Extraction of Framer Motion variants into `animations.ts` for better organization and improved animation handling (e.g., `position: absolute` for transitions).
- Dedicated `types.ts` file for component-specific types.

This refactoring significantly improves the separation of concerns, making the `Slides` component easier to understand, maintain, and extend. It also lays the groundwork for more robust and dynamic slide content and collapse animations.
* feat(multiple): Introduce SlideContent component and enhance Docker client host tracking

This commit introduces a new `SlideContent` component for animated UI transitions and enhances the Docker client manager's ability to track host IDs.

- **UI Components:**
  - Added a new `SlideContent` component to `@ui/components/Slides` that utilizes `framer-motion` for animated transitions between different content slides, improving user experience in dynamic interfaces.
  - Re-exported the `Column` type from `@ui/components/Table` to facilitate easier type imports for consumers.
- **Docker Client Manager:**
  - Implemented `internalListeners` in `DockerClientManagerCore` to proactively manage `WorkerWrapper`'s `hostIds` set.
  - This new mechanism tracks `host:init`, `host:added`, and `host:removed` events, ensuring the `hostIds` set accurately reflects connected hosts.
  - The `host:init` event type has been added to `@typings/docker-client`.
- **Dockstat Application:**
  - Refactored `WorkersTable` by removing redundant explicit type annotations from `render` function parameters, leveraging TypeScript's type inference.
  - Explicit type assertions (`as Type`) were introduced internally within the `render` functions for clarity and type safety where inference was not sufficient.

* chore(ci): Lint [skip ci]

* Update packages/docker-client/src/manager/core.ts

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Signed-off-by: ItsNik <info@itsnik.de>

* Update packages/ui/src/components/Slides/SlideContent.tsx

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Signed-off-by: ItsNik <info@itsnik.de>

* chore(ci): Lint [skip ci]

* refactor(docker-client): Remove 'host:init' event and clarify host lifecycle logging

The 'host:init' event type has been removed from `DockerClientEvents` and its handling logic from `DockerClientManagerCore`. This simplifies the event API and ensures consistency with host lifecycle management.

Additionally, debug logging for 'host:added' and 'host:removed' events has been enhanced to include the specific host ID and the exact Set operation (`.add()` or `.delete()`) being performed, improving observability during host lifecycle changes.

---------

Signed-off-by: ItsNik <info@itsnik.de>
Co-authored-by: actions-user <its4nik@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@Its4Nik Its4Nik self-assigned this Jan 8, 2026
@Its4Nik Its4Nik added this to DockStat Jan 8, 2026
@Its4Nik Its4Nik added the enhancement New feature or request label Jan 8, 2026
@github-project-automation github-project-automation Bot moved this to Todo in DockStat Jan 8, 2026
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 8, 2026

Reviewer's Guide

Refactors the Slides UI component into smaller composable pieces with a shared slide-state hook and updated animation model, adds internal event handling for host add/remove in the Docker client manager, slightly adjusts host tracking in Hosts, loosens table cell render typings and re-exports Column, and makes minor typings/logging tweaks across the app.

Sequence diagram for slide change handling and animation

sequenceDiagram
  actor User
  participant ButtonRow
  participant Slides
  participant useSlidesState
  participant SlideContent
  participant FramerMotion

  User->>ButtonRow: Click slide button (key)
  ButtonRow->>useSlidesState: changeSlide(key)
  useSlidesState->>useSlidesState: Determine hideable, collapsed, index
  alt hideable and key is activeSlide
    useSlidesState->>useSlidesState: set isCollapsed = true
    useSlidesState->>useSlidesState: set internalSlide = null (uncontrolled)
    useSlidesState-->>Slides: onSlideChange(null)
  else expanding from collapsed or no active
    useSlidesState->>useSlidesState: set isCollapsed = false
    useSlidesState->>useSlidesState: set animationDirection = 1
    useSlidesState->>useSlidesState: set internalSlide = key (uncontrolled)
    useSlidesState-->>Slides: onSlideChange(key)
  else switching between slides
    useSlidesState->>useSlidesState: compute newIndex, currentIndex
    useSlidesState->>useSlidesState: set animationDirection
    useSlidesState->>useSlidesState: update internalSlide
    useSlidesState-->>Slides: onSlideChange(key)
  end

  Slides->>SlideContent: Render with updated state
  SlideContent->>SlideContent: hasContent and show flags
  SlideContent->>FramerMotion: motion.div with collapseVariants(contentHeight)
  FramerMotion-->>User: Container expand/collapse animation

  par Active slide transition
    SlideContent->>FramerMotion: motion.div with slideVariants(animationDirection)
    FramerMotion-->>User: Slide-in / slide-out horizontal animation
  and Height measurement
    SlideContent->>SlideContent: measure active slide DOM height
    SlideContent->>useSlidesState: setContentHeight(height)
  end
Loading

Sequence diagram for Docker host event processing

sequenceDiagram
  participant DockerWorker as DockerWorker
  participant DockerClientManagerCore as DockerClientManagerCore
  participant internalListeners as internalListeners
  participant WorkerWrapper as WorkerWrapper
  participant PluginHooks as PluginHooks

  DockerWorker-->>DockerClientManagerCore: EventMessage(type host:added, ctx)
  DockerClientManagerCore->>DockerClientManagerCore: listener(message)
  alt message is defined
    DockerClientManagerCore->>internalListeners: internalListeners(wrapper, message)
    alt type is host:added
      internalListeners->>WorkerWrapper: wrapper.hostIds.add(hostId)
      internalListeners-->>DockerClientManagerCore: updated wrapper
    else type is host:removed
      internalListeners->>WorkerWrapper: wrapper.hostIds.delete(hostId)
      internalListeners-->>DockerClientManagerCore: updated wrapper
    end
    DockerClientManagerCore->>PluginHooks: triggerHooks(message)
    PluginHooks-->>DockerClientManagerCore: Execute server hooks
  else message is undefined
    DockerClientManagerCore-->>DockerWorker: ignore
  end
Loading

Class diagram for refactored Slides UI components

classDiagram
  class SlidesProps {
    +Record~string, React.ReactNode~ children
    +string header
    +string description
    +ButtonRowPosition buttonPosition
    +boolean connected
    +string defaultSlide
    +string selectedSlide
    +string controlledSlide
    +function onSlideChange(slideKey string|null)
    +boolean hideable
    +SlideVariant variant
    +string className
  }

  class UseSlideReturn {
    +string[] slideKeys
    +string activeSlide
    +number animationDirection
    +boolean isCollapsed
    +number contentHeight
    +Record~string, HTMLDivElement~ contentRefs
    +boolean isControlled
    +function changeSlide(newSlide string)
  }

  class Slides {
    +function Slides(props SlidesProps)
  }

  class useSlidesState {
    +function useSlidesState(children Record~string, React.ReactNode~, controlledSlide string, defaultSlide string, hideable boolean, onSlideChange function)
  }

  class SlideContent {
    +function SlideContent(state UseSlideReturn, children Record~string, React.ReactNode~)
  }

  class SlidesHeader {
    +function SlidesHeader(header string, description string, buttonPosition ButtonRowPosition, connected boolean, state UseSlideReturn)
  }

  class MinimalSlidesHeader {
    +function MinimalSlidesHeader(header string, description string, connected boolean, state UseSlideReturn)
  }

  class ButtonRow {
    +function ButtonRow(slideKeys string[], activeSlide string, isCollapsed boolean, connected boolean, onSlideChange function, wrapperClass string)
  }

  class animations {
    +function slideVariants(direction number)
    +function collapseVariants(contentHeight number)
  }

  Slides --> SlidesProps
  Slides --> UseSlideReturn : uses
  Slides ..> useSlidesState : calls
  Slides ..> SlidesHeader : renders
  Slides ..> MinimalSlidesHeader : renders
  Slides ..> SlideContent : renders

  SlidesHeader --> ButtonRow : uses
  MinimalSlidesHeader --> ButtonRow : uses

  SlideContent --> UseSlideReturn : uses
  SlideContent ..> animations : uses

  useSlidesState --> SlidesProps : derives configuration
  ButtonRow --> UseSlideReturn : consumes subset

  class ButtonRowPosition {
  }

  class SlideVariant {
  }
Loading

File-Level Changes

Change Details Files
Refactor Slides component into modular subcomponents and a shared slide state hook with updated animation behavior.
  • Replaces inline slide state/animation logic in Slides.tsx with a dedicated useSlidesState hook that manages active slide, collapse state, animation direction, measured content height, and click behavior for hideable/controlled modes.
  • Moves header rendering into SlidesHeader and MinimalSlidesHeader components that use a shared ButtonRow for slide controls and accept the shared slide state object.
  • Extracts slide transition rendering into a reusable SlideContent component that uses framer-motion with new slide and collapse variants for height-based, percentage-based animations.
  • Defines shared slide props types in a new types.ts file and updates Slides.tsx to consume them, simplifying the main component implementation.
  • Updates UI components index exports to only export Slides from Slides/Slides and not its internal types, while Table now re-exports its Column type.
packages/ui/src/components/Slides/Slides.tsx
packages/ui/src/components/Slides/useSlideState.ts
packages/ui/src/components/Slides/SlidesHeader.tsx
packages/ui/src/components/Slides/SlideContent.tsx
packages/ui/src/components/Slides/ButtonRow.tsx
packages/ui/src/components/Slides/animations.ts
packages/ui/src/components/Slides/MinimalSlidesHeader.tsx
packages/ui/src/components/Slides/types.ts
packages/ui/src/components/index.ts
packages/ui/src/components/Table/Table.tsx
Adjust WorkersTable cell render typings to be looser and perform casts internally for better compatibility with table generics.
  • Removes explicit parameter types from column render functions and replaces them with internal casts where needed (e.g., to boolean, number, or WorkerMetrics memory type).
  • Keeps the rendered UI behavior the same while making the render signature align with a generic, untyped value parameter expected by the table component.
apps/dockstat/src/components/clients/WorkersTable.tsx
Add internal host add/remove bookkeeping within DockerClientManagerCore and tweak host tracking logging in Hosts.
  • Introduces an internalListeners method on DockerClientManagerCore that listens for host:added and host:removed events, logs them, and mutates the WorkerWrapper.hostIds set accordingly.
  • Calls internalListeners from the message listener before triggerHooks to ensure internal state is kept in sync with host events.
  • Removes a noisy debug log of total registered events from triggerHooks to reduce log noise.
  • Adds an informational log in Hosts.create when a wrapper and host id exist before adding the host id to the wrapper.
packages/docker-client/src/manager/core.ts
packages/docker-client/src/manager/hosts.ts
Minor typings and formatting cleanups for Docker client event types.
  • Adds a blank line separator in DockerClientEvents definition around host events for readability without changing behavior.
packages/typings/src/docker-client.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 5 issues, and left some high level feedback:

  • In SlideContent, slideVariants is called with state.contentHeight, but the slideVariants helper expects a direction (e.g. state.animationDirection), which will cause the animation to behave incorrectly.
  • The contentHeight in useSlidesState is only measured on activeSlide changes, so if the content of a slide changes height while it remains active (e.g. async data loads), the container height and animation may not update; consider using a ResizeObserver or recalculating when content changes.
  • The SlidesProps type exposes both selectedSlide and controlledSlide, which can be confusing for consumers; it would be clearer to expose a single controlled prop and keep any aliasing internal to the Slides component.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `SlideContent`, `slideVariants` is called with `state.contentHeight`, but the `slideVariants` helper expects a direction (e.g. `state.animationDirection`), which will cause the animation to behave incorrectly.
- The `contentHeight` in `useSlidesState` is only measured on `activeSlide` changes, so if the content of a slide changes height while it remains active (e.g. async data loads), the container height and animation may not update; consider using a `ResizeObserver` or recalculating when content changes.
- The `SlidesProps` type exposes both `selectedSlide` and `controlledSlide`, which can be confusing for consumers; it would be clearer to expose a single controlled prop and keep any aliasing internal to the `Slides` component.

## Individual Comments

### Comment 1
<location> `packages/ui/src/components/Slides/SlideContent.tsx:13` </location>
<code_context>
+  children: Record<string, React.ReactNode>
+}) => {
+  const activeKey = state.activeSlide
+  const hasContent = activeKey in children && children[activeKey] != null
+  const show = hasContent || !state.isCollapsed
+
</code_context>

<issue_to_address>
**issue (bug_risk):** Guard against `activeSlide` being null before using the `in` operator to avoid runtime errors.

If `state.activeSlide` is `null`, `activeKey in children` will throw a `TypeError` because the left-hand side of `in` cannot be `null` or `undefined`. Consider a pattern like:

```ts
const hasContent =
  activeKey != null &&
  Object.prototype.hasOwnProperty.call(children, activeKey) &&
  children[activeKey] != null
```

and derive `show` from `hasContent` to avoid a possible runtime crash while preserving the current behavior.
</issue_to_address>

### Comment 2
<location> `packages/ui/src/components/Slides/SlideContent.tsx:19-28` </location>
<code_context>
+  return (
+    <AnimatePresence initial={false}>
+      {show && (
+        <motion.div
+          initial="collapsed"
+          animate="expanded"
+          exit="collapsed"
+          variants={collapseVariants(state.contentHeight)}
+          transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
+          className="overflow-hidden"
+        >
+          <div className="relative">
+            <AnimatePresence initial={false} custom={state.animationDirection}>
+              {state.activeSlide && !state.isCollapsed && (
+                <motion.div
+                  key={state.activeSlide}
+                  custom={state.animationDirection}
+                  variants={slideVariants(state.contentHeight)}
+                  initial="enter"
+                  animate="center"
</code_context>

<issue_to_address>
**issue (bug_risk):** The `slideVariants` helper is being called with `contentHeight` instead of a direction, which breaks the `custom`-driven animation logic.

Previously, `slideVariants` consumed `animationDirection` via Framer Motion’s `custom` prop. After the refactor it’s typed as `(direction: number): Variants` but is now called with `state.contentHeight` while `custom={state.animationDirection}` remains on the `motion.div`.

This means:
- The argument passed to `slideVariants` is a height, not a direction, so the slide direction can be incorrect or inconsistent.
- The `custom` value is effectively unused, so changing `animationDirection` no longer affects the animation.

To fix this, either:
- Pass `state.animationDirection` into `slideVariants` and remove reliance on `custom`, or
- Restore the functional variants pattern where `slideVariants` uses `direction` via the `custom` prop, so `animationDirection` again drives the slide direction.
</issue_to_address>

### Comment 3
<location> `packages/ui/src/components/Slides/useSlideState.ts:21` </location>
<code_context>
-  const [internalSlide, setInternalSlide] = useState<string | null>(initialSlide)
-  const [animationDirection, setAnimationDirection] = useState<1 | -1>(1)
-  const [isCollapsed, setIsCollapsed] = useState(hideable)
-  const previousSlideIndex = useRef(slideKeys.indexOf(initialSlide || ""))
-
-  const isControlled = controlledSlide !== undefined
</code_context>

<issue_to_address>
**issue (complexity):** Consider removing the unused `previousSlideIndex` ref and narrowing the hook’s returned API to only the fields and helpers that consumers actually need to reduce mental overhead.

You can trim a bit of complexity without changing behaviour:

1. **Remove `previousSlideIndex` (it's write-only)**

`previousSlideIndex` is never read, only assigned in `changeSlide`, so it can be safely removed:

```ts
// Before
const previousSlideIndex = useRef(slideKeys.indexOf(initialSlide || ""));

const changeSlide = (newSlide: string) => {
  // ...
  const newIndex = slideKeys.indexOf(newSlide);
  const currentIndex = slideKeys.indexOf(activeSlide);

  setAnimationDirection(newIndex > currentIndex ? 1 : -1);
  previousSlideIndex.current = currentIndex;

  if (!isControlled) {
    setInternalSlide(newSlide);
  }
  onSlideChange?.(newSlide);
};

// After
const changeSlide = (newSlide: string) => {
  // ...
  const newIndex = slideKeys.indexOf(newSlide);
  const currentIndex = slideKeys.indexOf(activeSlide);

  setAnimationDirection(newIndex > currentIndex ? 1 : -1);

  if (!isControlled) {
    setInternalSlide(newSlide);
  }
  onSlideChange?.(newSlide);
};
```

And delete the `useRef` line entirely.

2. **Narrow the hook API to hide internals where possible**

If `Slides.tsx` doesn’t need direct access to some internals (e.g. `isControlled`, `contentRefs`, `contentHeight`), you can keep them inside the hook and expose smaller, intent-focused primitives. For example:

```ts
// Before
return {
  slideKeys,
  activeSlide,
  animationDirection,
  isCollapsed,
  contentHeight,
  contentRefs,
  isControlled,
  changeSlide,
};

// After (example: only if callers don't need the removed fields)
return {
  slideKeys,
  activeSlide,
  animationDirection,
  isCollapsed,
  changeSlide,
};
```

If you still need refs for measurement, consider exposing a single `getContentRef` callback instead of the whole `contentRefs` map:

```ts
const contentRefs = useRef<Record<string, HTMLDivElement | null>>({});

const getContentRef = (key: string) => (el: HTMLDivElement | null) => {
  contentRefs.current[key] = el;
};

return {
  slideKeys,
  activeSlide,
  animationDirection,
  isCollapsed,
  contentHeight,
  getContentRef,
  changeSlide,
};
```

This keeps the same functionality but reduces the surface area and mental load for consumers.
</issue_to_address>

### Comment 4
<location> `packages/ui/src/components/Slides/SlideContent.tsx:9` </location>
<code_context>
+  header?: string
+  description?: string
+  connected: boolean
+  state: UseSlideReturn
+}
+
</code_context>

<issue_to_address>
**issue (complexity):** Consider narrowing the `SlideContent` props to a small, explicit state shape and delegating ref management to the hook to reduce coupling and cognitive load.

You can reduce coupling and cognitive load by narrowing the props and hiding the ref/measurement details behind a small API on the hook, instead of passing the entire `UseSlideReturn` and mutating `contentRefs` in the component.

For example, in your hook, expose only what the UI actually needs:

```ts
// useSlideState.ts
export type SlideState = {
  activeKey: string | null
  isCollapsed: boolean
  animationDirection: 1 | -1
  contentHeight: number | null
  setContentRef: (key: string, el: HTMLDivElement | null) => void
}

export const useSlideState = (...): SlideState => {
  const contentRefs = useRef<Record<string, HTMLDivElement | null>>({})
  const [contentHeight, setContentHeight] = useState<number | null>(null)

  const setContentRef = (key: string, el: HTMLDivElement | null) => {
    contentRefs.current[key] = el
    // existing measurement logic using contentRefs + setContentHeight
  }

  return {
    activeKey: activeSlide,
    isCollapsed,
    animationDirection,
    contentHeight,
    setContentRef,
  }
}
```

Then update `SlideContent` to depend only on these primitives, not the full hook state shape or its internal refs:

```tsx
import { AnimatePresence, motion } from "framer-motion"
import { collapseVariants, slideVariants } from "./animations"
import type { SlideState } from "./useSlideState"

export const SlideContent = ({
  state: { activeKey, isCollapsed, animationDirection, contentHeight, setContentRef },
  children,
}: {
  state: SlideState
  children: Record<string, React.ReactNode>
}) => {
  const hasContent = activeKey != null && activeKey in children && children[activeKey] != null
  const show = hasContent || !isCollapsed

  return (
    <AnimatePresence initial={false}>
      {show && (
        <motion.div
          initial="collapsed"
          animate="expanded"
          exit="collapsed"
          variants={collapseVariants(contentHeight)}
          transition={{ duration: 0.3, ease: [0.4, 0, 0.2, 1] }}
          className="overflow-hidden"
        >
          <div className="relative">
            <AnimatePresence initial={false} custom={animationDirection}>
              {activeKey && !isCollapsed && (
                <motion.div
                  key={activeKey}
                  custom={animationDirection}
                  variants={slideVariants(contentHeight)}
                  initial="enter"
                  animate="center"
                  exit="exit"
                  transition={{
                    x: { type: "spring", stiffness: 300, damping: 30 },
                  }}
                >
                  <div ref={(el) => setContentRef(activeKey, el)}>
                    {children[activeKey]}
                  </div>
                </motion.div>
              )}
            </AnimatePresence>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  )
}
```

This keeps all existing behavior (measured height, variants parameterization, nested `AnimatePresence`) but:

- Removes direct mutation of `contentRefs.current` from the UI layer.
- Makes `SlideContent` easier to understand by taking a small, explicit state shape.
- Localizes the knowledge of how refs are stored and used for measurement inside the hook.
</issue_to_address>

### Comment 5
<location> `packages/ui/src/components/Slides/animations.ts:3` </location>
<code_context>
+import type { Variants } from "framer-motion"
+
+const slideVariants = (direction: number): Variants => {
+  return {
+    enter: {
</code_context>

<issue_to_address>
**issue (complexity):** Consider tightening the function APIs by using a typed slide direction and an optional content height with defaults to make the animation helpers harder to misuse and clearer at call sites.

You can reduce the risk of mis‑wiring and make the abstractions clearer with stronger typing and safer defaults, without removing the new module.

**1. Make slide direction explicit and hard to misuse**

Constrain `direction` to the intended domain and make its meaning obvious:

```ts
type SlideDirection = 1 | -1;

const slideVariants = (direction: SlideDirection): Variants => ({
  enter: {
    x: direction > 0 ? "100%" : "-100%",
    position: "absolute" as const,
    top: 0,
    left: 0,
    right: 0,
  },
  center: {
    x: 0,
    position: "relative" as const,
  },
  exit: {
    x: direction > 0 ? "-100%" : "100%",
    position: "absolute" as const,
    top: 0,
    left: 0,
    right: 0,
  },
});
```

You can also add a small helper to make call sites self‑documenting:

```ts
export const SLIDE_FORWARD: SlideDirection = 1;
export const SLIDE_BACKWARD: SlideDirection = -1;

// usage:
// slideVariants(SLIDE_FORWARD)
```

This makes it much harder for someone to accidentally pass a height or other numeric value.

**2. Make `contentHeight` optional with a sane default**

Keep the ability to use a measured height, but don’t require it at every call site; default to `"auto"` to preserve the simpler behaviour:

```ts
const collapseVariants = (contentHeight?: number): Variants => ({
  collapsed: {
    height: 0,
    opacity: 0,
  },
  expanded: {
    height: contentHeight ?? "auto",
    opacity: 1,
  },
});
```

This keeps your current functionality (precise height when provided) while making usage simpler and less coupled to measurement logic in places that don’t need it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread packages/ui/src/components/Slides/SlideContent.tsx Outdated
Comment thread packages/ui/src/components/Slides/SlideContent.tsx Outdated
Comment thread packages/ui/src/components/Slides/useSlideState.ts Outdated
Comment thread packages/ui/src/components/Slides/SlideContent.tsx
Comment thread packages/ui/src/components/Slides/animations.ts Outdated
@Its4Nik
Copy link
Copy Markdown
Owner Author

Its4Nik commented Jan 8, 2026

@sourcery-ai resolve

- Refactor `slideVariants` to utilize Framer Motion's `custom` prop for passing animation direction, simplifying its usage in `SlideContent`.
- Introduce `mode="popLayout"` to `AnimatePresence` for more robust and smoother transitions, ensuring better layout management during animations.
- Enhance the `hasContent` check in `SlideContent.tsx` to explicitly account for `null` `activeKey` and use `Object.hasOwn` for precise property validation.
- Implement null safety when updating `contentRefs.current`, preventing potential runtime errors if `activeSlide` is `null`.
- Remove the redundant `previousSlideIndex` ref from `useSlideState`, streamlining state management.
@Its4Nik Its4Nik merged commit 36e0790 into dev Jan 8, 2026
3 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in DockStat Jan 8, 2026
@Its4Nik Its4Nik deleted the ui-slider-animation-update branch January 8, 2026 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant