Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ description: All notable changes will be documented in this file.

## [Unreleased]

### Added

- **Presence**: Added support for skipping the initial animation when the component is mounted. This can be used in all
disclosure components (e.g., `Dialog`, `DatePicker`, `Menu` etc).

## [5.4.0] - 2025-03-28

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Presence } from '@ark-ui/react/presence'
import { useState } from 'react'

export const SkipAnimationOnMount = () => {
const [present, setPresent] = useState(true)
return (
<>
<button type="button" onClick={() => setPresent(!present)}>
Toggle
</button>
<Presence present={present} skipAnimationOnMount>
Content
</Presence>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export default meta
export { Basic } from './examples/basic'
export { LazyMount } from './examples/lazy-mount'
export { LazyMountAndUnmountOnExit } from './examples/lazy-mount-and-unmount-on-exit'
export { SkipAnimationOnMount } from './examples/skip-animation-on-mount'
export { UnmountOnExit } from './examples/unmount-on-exit'
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ import { createSplitProps } from '../../utils/create-split-props'
import type { UsePresenceProps } from './use-presence'

export const splitPresenceProps = <T extends UsePresenceProps>(props: T) =>
createSplitProps<UsePresenceProps>()(props, ['immediate', 'lazyMount', 'onExitComplete', 'present', 'unmountOnExit'])
createSplitProps<UsePresenceProps>()(props, [
'immediate',
'lazyMount',
'onExitComplete',
'present',
'skipAnimationOnMount',
'unmountOnExit',
])
12 changes: 9 additions & 3 deletions packages/react/src/components/presence/use-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import type { Optional } from '../../types'
import type { RenderStrategyProps } from '../../utils/render-strategy'
import { useEvent } from '../../utils/use-event'

export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {}
export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {
/**
* Whether to allow the initial presence animation.
* @default false
*/
skipAnimationOnMount?: boolean
}
export type UsePresenceReturn = ReturnType<typeof usePresence>

export const usePresence = (props: UsePresenceProps = {}) => {
const { lazyMount, unmountOnExit, present, ...rest } = props
const { lazyMount, unmountOnExit, present, skipAnimationOnMount = false, ...rest } = props
const wasEverPresent = useRef(false)
const machineProps: Partial<presence.Props> = {
...rest,
Expand All @@ -28,7 +34,7 @@ export const usePresence = (props: UsePresenceProps = {}) => {
(!api.present && !wasEverPresent.current && lazyMount) || (unmountOnExit && !api.present && wasEverPresent.current)

const getPresenceProps = () => ({
'data-state': present ? 'open' : 'closed',
'data-state': api.skip && skipAnimationOnMount ? undefined : present ? 'open' : 'closed',
hidden: !api.present,
})

Expand Down
5 changes: 5 additions & 0 deletions packages/solid/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ description: All notable changes will be documented in this file.

## [Unreleased]

### Added

- **Presence**: Added support for skipping the initial animation when the component is mounted. This can be used in all
disclosure components (e.g., `Dialog`, `DatePicker`, `Menu` etc).

## [5.4.0] - 2025-03-28

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Presence } from '@ark-ui/solid/presence'
import { createSignal } from 'solid-js'

export const SkipAnimationOnMount = () => {
const [present, setPresent] = createSignal(true)

return (
<>
<button type="button" onClick={() => setPresent(!present)}>
Toggle
</button>
<Presence present={present()} skipAnimationOnMount>
Content
</Presence>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export default meta
export { Basic } from './examples/basic'
export { LazyMount } from './examples/lazy-mount'
export { LazyMountAndUnmountOnExit } from './examples/lazy-mount-and-unmount-on-exit'
export { SkipAnimationOnMount } from './examples/skip-animation-on-mount'
export { UnmountOnExit } from './examples/unmount-on-exit'
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ import { createSplitProps } from '../../utils/create-split-props'
import type { UsePresenceProps } from './use-presence'

export const splitPresenceProps = <T extends UsePresenceProps>(props: T) =>
createSplitProps<UsePresenceProps>()(props, ['immediate', 'lazyMount', 'onExitComplete', 'present', 'unmountOnExit'])
createSplitProps<UsePresenceProps>()(props, [
'immediate',
'lazyMount',
'onExitComplete',
'present',
'skipAnimationOnMount',
'unmountOnExit',
])
12 changes: 9 additions & 3 deletions packages/solid/src/components/presence/use-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,17 @@ import type { MaybeAccessor, Optional } from '../../types'
import { type RenderStrategyProps, splitRenderStrategyProps } from '../../utils/render-strategy'
import { runIfFn } from '../../utils/run-if-fn'

export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {}
export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {
/**
* Whether to allow the initial presence animation.
* @default false
*/
skipAnimationOnMount?: boolean
}
export interface UsePresenceReturn extends ReturnType<typeof usePresence> {}

export const usePresence = (props: MaybeAccessor<UsePresenceProps>) => {
const [renderStrategyProps, context] = splitRenderStrategyProps(runIfFn(props))
const [renderStrategyProps, localProps] = splitRenderStrategyProps(runIfFn(props))
const [wasEverPresent, setWasEverPresent] = createSignal(false)

const service = useMachine(presence.machine, props)
Expand All @@ -28,7 +34,7 @@ export const usePresence = (props: MaybeAccessor<UsePresenceProps>) => {
ref: api().setNode,
presenceProps: {
hidden: !api().present,
'data-state': context.present ? 'open' : 'closed',
'data-state': api().skip && localProps.skipAnimationOnMount ? undefined : localProps.present ? 'open' : 'closed',
},
}))
}
2 changes: 1 addition & 1 deletion packages/svelte/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
},
"scripts": {
"build": "svelte-kit sync && svelte-package",
"dev": "vite dev",
"dev": "storybook dev -p 6006",
"storybook": "storybook dev -p 6006",
"test": "vitest",
"test:coverage": "vitest run --coverage",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script lang="ts">
import { Presence, type PresenceProps } from '@ark-ui/svelte/presence'

const props: PresenceProps = $props()
let present = $state(true)

function toggle() {
present = !present
}
</script>

<button onclick={toggle}>Toggle</button>

<Presence {...props} {present} skipAnimationOnMount>Content</Presence>
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Meta } from '@storybook/svelte'
import BasicExample from './examples/basic.svelte'
import LazyMountAndUnmountOnExitExample from './examples/lazy-mount-and-unmount-on-exit.svelte'
import LazyMountExample from './examples/lazy-mount.svelte'
import SkipAnimationOnMountExample from './examples/skip-animation-on-mount.svelte'
import UnmountOnExitExample from './examples/unmount-on-exit.svelte'

const meta = {
Expand Down Expand Up @@ -33,3 +34,9 @@ export const LazyMountAndUnmountOnExit = {
Component: LazyMountAndUnmountOnExitExample,
}),
}

export const SkipAnimationOnMount = {
render: () => ({
Component: SkipAnimationOnMountExample,
}),
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,11 @@ import { createSplitProps } from '$lib/utils/create-split-props'
import type { UsePresenceProps } from './use-presence.svelte'

export const splitPresenceProps = <T extends UsePresenceProps>(props: T) =>
createSplitProps<UsePresenceProps>()(props, ['immediate', 'lazyMount', 'onExitComplete', 'present', 'unmountOnExit'])
createSplitProps<UsePresenceProps>()(props, [
'immediate',
'lazyMount',
'onExitComplete',
'present',
'skipAnimationOnMount',
'unmountOnExit',
])
14 changes: 10 additions & 4 deletions packages/svelte/src/lib/components/presence/use-presence.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import type { Optional } from '$lib/types'
import { type RenderStrategyProps, splitRenderStrategyProps } from '$lib/utils/render-strategy'
import * as presence from '@zag-js/presence'
import { normalizeProps, reflect, useMachine } from '@zag-js/svelte'

export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {}
import { normalizeProps, useMachine } from '@zag-js/svelte'

export interface UsePresenceProps extends Optional<presence.Props, 'present'>, RenderStrategyProps {
/**
* Whether to allow the initial presence animation.
* @default false
*/
skipAnimationOnMount?: boolean
}
export interface UsePresenceReturn extends ReturnType<typeof usePresence> {}

export const usePresence = (props: UsePresenceProps) => {
Expand All @@ -28,7 +34,7 @@ export const usePresence = (props: UsePresenceProps) => {

const result = $derived(() => ({
getPresenceProps: () => ({
'data-state': props.present ? 'open' : 'closed',
'data-state': api.skip && props.skipAnimationOnMount ? undefined : props.present ? 'open' : 'closed',
hidden: !api.present,
}),
present: api.present,
Expand Down
5 changes: 5 additions & 0 deletions packages/vue/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ description: All notable changes will be documented in this file.

## [Unreleased]

### Added

- **Presence**: Added support for skipping the initial animation when the component is mounted. This can be used in all
disclosure components (e.g., `Dialog`, `DatePicker`, `Menu` etc).

## [5.4.0] - 2025-03-28

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<script setup lang="ts">
import { Presence } from '@ark-ui/vue/presence'
import { ref } from 'vue'

const isPresent = ref(true)
</script>

<template>
<div>
<button @click="isPresent = !isPresent">Toggle</button>
<Presence :present="isPresent" skip-animation-on-mount>Content</Presence>
</div>
</template>
4 changes: 4 additions & 0 deletions packages/vue/src/components/presence/presence.stories.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import Basic from './examples/basic.vue'
import LazyMountAndUnmountOnExit from './examples/lazy-mount-and-unmount-on-exit.vue'
import LazyMount from './examples/lazy-mount.vue'
import SkipAnimationOnMount from './examples/skip-animation-on-mount.vue'
import UnmountOnExit from './examples/unmount-on-exit.vue'
</script>
<template>
Expand All @@ -18,5 +19,8 @@ import UnmountOnExit from './examples/unmount-on-exit.vue'
<Variant title="LazyMountAndUnmountOnExit">
<LazyMountAndUnmountOnExit />
</Variant>
<Variant title="SkipAnimationOnMount">
<SkipAnimationOnMount />
</Variant>
</Story>
</template>
7 changes: 7 additions & 0 deletions packages/vue/src/components/presence/presence.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface RootProps {
/**
* Whether to synchronize the present change immediately or defer it to the next frame
* @default false
*/
immediate?: boolean
/**
Expand All @@ -10,8 +11,14 @@ export interface RootProps {
lazyMount?: boolean
/**
* Whether the node is present (controlled by the user)
* @default false
*/
present?: boolean
/**
* Whether to allow the initial presence animation.
* @default false
*/
skipAnimationOnMount?: boolean
/**
* Whether to unmount on exit.
* @default false
Expand Down
1 change: 1 addition & 0 deletions packages/vue/src/components/presence/presence.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const props = withDefaults(defineProps<PresenceProps>(), {
immediate: undefined,
lazyMount: undefined,
present: undefined,
skipAnimationOnMount: undefined,
unmountOnExit: undefined,
} satisfies BooleanDefaults<RootProps>)

Expand Down
8 changes: 7 additions & 1 deletion packages/vue/src/components/presence/use-presence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface UsePresenceProps extends Optional<presence.Props, 'present'> {
* @default false
*/
unmountOnExit?: boolean
/**
* Whether to allow the initial presence animation.
* @default false
*/
skipAnimationOnMount?: boolean
}

export type UsePresenceReturn = ReturnType<typeof usePresence>
Expand Down Expand Up @@ -61,7 +66,8 @@ export const usePresence = (props: MaybeRef<UsePresenceProps>, emit?: EmitFn<Roo
presenceProps: {
ref: nodeRef,
hidden: !api.value.present,
'data-state': localProps?.present ? 'open' : 'closed',
'data-state':
api.value.skip && localProps.skipAnimationOnMount ? undefined : localProps?.present ? 'open' : 'closed',
},
}
})
Expand Down
12 changes: 12 additions & 0 deletions website/src/content/types/react/color-picker.types.json
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,12 @@
},
"readOnly": { "type": "boolean", "isRequired": false, "description": "Whether the color picker is read-only" },
"required": { "type": "boolean", "isRequired": false, "description": "Whether the color picker is required" },
"skipAnimationOnMount": {
"type": "boolean",
"defaultValue": "false",
"isRequired": false,
"description": "Whether to allow the initial presence animation."
},
"unmountOnExit": {
"type": "boolean",
"defaultValue": "false",
Expand Down Expand Up @@ -333,6 +339,12 @@
"isRequired": false,
"description": "Whether the node is present (controlled by the user)"
},
"skipAnimationOnMount": {
"type": "boolean",
"defaultValue": "false",
"isRequired": false,
"description": "Whether to allow the initial presence animation."
},
"unmountOnExit": {
"type": "boolean",
"defaultValue": "false",
Expand Down
12 changes: 12 additions & 0 deletions website/src/content/types/react/combobox.types.json
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,12 @@
"isRequired": false,
"description": "The behavior of the combobox input when an item is selected\n\n- `replace`: The selected item string is set as the input value\n- `clear`: The input value is cleared\n- `preserve`: The input value is preserved"
},
"skipAnimationOnMount": {
"type": "boolean",
"defaultValue": "false",
"isRequired": false,
"description": "Whether to allow the initial presence animation."
},
"translations": {
"type": "IntlTranslations",
"isRequired": false,
Expand Down Expand Up @@ -373,6 +379,12 @@
"isRequired": false,
"description": "Whether the node is present (controlled by the user)"
},
"skipAnimationOnMount": {
"type": "boolean",
"defaultValue": "false",
"isRequired": false,
"description": "Whether to allow the initial presence animation."
},
"unmountOnExit": {
"type": "boolean",
"defaultValue": "false",
Expand Down
Loading