Skip to content

Commit

Permalink
feat(ConfigProvider): inject useId to provide composable from diffe…
Browse files Browse the repository at this point in the history
…rent framework (#718)

* feat: id injection

* chore: add deterministicId

* refactor: replace ueUniqueId

* chore: add comment

* fix: some weird case

* test: fix broken test

* fix: revert collapsible

* refactor: useId instances

* docs: run docgen

* chore: cleanup component

* docs: populate config-provider
  • Loading branch information
zernonia committed Mar 6, 2024
1 parent 6c3b36d commit d0517a2
Show file tree
Hide file tree
Showing 52 changed files with 2,122 additions and 2,949 deletions.
6 changes: 3 additions & 3 deletions docs/components/demo/Menubar/tailwind/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const CHECK_ITEMS = ['Always Show Bookmarks Bar', 'Always Show Full URLs']
</MenubarPortal>
</MenubarMenu>

<MenubarMenu>
<MenubarMenu value="Edit">
<MenubarTrigger
class="py-2 px-3 outline-none select-none font-semibold leading-none rounded text-grass11 text-[13px] flex items-center justify-between gap-[2px] data-[highlighted]:bg-green4 data-[state=open]:bg-green4"
>
Expand Down Expand Up @@ -206,7 +206,7 @@ const CHECK_ITEMS = ['Always Show Bookmarks Bar', 'Always Show Full URLs']
</MenubarPortal>
</MenubarMenu>

<MenubarMenu>
<MenubarMenu value="View">
<MenubarTrigger
class="py-2 px-3 outline-none select-none font-semibold leading-none rounded text-grass11 text-[13px] flex items-center justify-between gap-[2px] data-[highlighted]:bg-green4 data-[state=open]:bg-green4"
>
Expand Down Expand Up @@ -276,7 +276,7 @@ const CHECK_ITEMS = ['Always Show Bookmarks Bar', 'Always Show Full URLs']
</MenubarPortal>
</MenubarMenu>

<MenubarMenu>
<MenubarMenu value="Profiles">
<MenubarTrigger
class="py-2 px-3 outline-none select-none font-semibold leading-none rounded text-grass11 text-[13px] flex items-center justify-between gap-[2px] data-[highlighted]:bg-green4 data-[state=open]:bg-green4"
>
Expand Down
6 changes: 6 additions & 0 deletions docs/content/meta/ComboboxViewport.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
'description': '<p>Change the default rendered element for the one passed as a child, merging their props and behavior.</p>\n<p>Read our <a href=\'https://www.radix-vue.com/guides/composition.html\'>Composition</a> guide for more details.</p>\n',
'type': 'boolean',
'required': false
},
{
'name': 'nonce',
'description': '',
'type': 'string',
'required': false
}
]" />
6 changes: 6 additions & 0 deletions docs/content/meta/ConfigProvider.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,11 @@
'type': 'boolean | ScrollBodyOption',
'required': false,
'default': 'true'
},
{
'name': 'useId',
'description': '<p>The global <code>useId</code> injection as a workaround for preventing hydration issue.</p>\n',
'type': '(() => string)',
'required': false
}
]" />
2 changes: 1 addition & 1 deletion docs/content/meta/DateRangeFieldRoot.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@
{
'name': 'modelValue',
'description': '',
'type': '{ start: CalendarDate | CalendarDateTime | ZonedDateTime; end: CalendarDate | CalendarDateTime | ZonedDateTime; }'
'type': '{ start: DateValue | undefined; end: DateValue | undefined; }'
},
{
'name': 'segments',
Expand Down
5 changes: 5 additions & 0 deletions docs/content/meta/DateRangePickerField.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@
'name': 'segments',
'description': '',
'type': '{ start: { part: SegmentPart; value: string; }[]; end: { part: SegmentPart; value: string; }[]; }'
},
{
'name': 'modelValue',
'description': '',
'type': '{ start: DateValue | undefined; end: DateValue | undefined; }'
}
]" />
8 changes: 8 additions & 0 deletions docs/content/meta/RangeCalendarCellTrigger.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@
'required': true
}
]" />

<SlotsTable :data="[
{
'name': 'text',
'description': '',
'type': 'string'
}
]" />
6 changes: 6 additions & 0 deletions docs/content/meta/ScrollAreaViewport.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
'description': '<p>Change the default rendered element for the one passed as a child, merging their props and behavior.</p>\n<p>Read our <a href=\'https://www.radix-vue.com/guides/composition.html\'>Composition</a> guide for more details.</p>\n',
'type': 'boolean',
'required': false
},
{
'name': 'nonce',
'description': '',
'type': 'string',
'required': false
}
]" />
6 changes: 6 additions & 0 deletions docs/content/meta/SelectViewport.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@
'description': '<p>Change the default rendered element for the one passed as a child, merging their props and behavior.</p>\n<p>Read our <a href=\'https://www.radix-vue.com/guides/composition.html\'>Composition</a> guide for more details.</p>\n',
'type': 'boolean',
'required': false
},
{
'name': 'nonce',
'description': '',
'type': 'string',
'required': false
}
]" />
43 changes: 25 additions & 18 deletions docs/content/utilities/config-provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,7 @@ When creating localized apps that require right-to-left (RTL) reading direction,
You can also change the global behavior of `bodylock` for components such as `Alert`, `DropdownMenu` and etc to fit your layout to prevent any [content shifts](https://github.com/radix-vue/radix-vue/issues/385).


<PropsTable
:data="[
{
name: 'dir',
required: false,
type: '&quot;ltr&quot; | &quot;rtl&quot;',
default: '&quot;ltr&quot;',
description: `The global reading direction of your application. This will be inherited by all primitives.`
},
{
name: 'scrollBody',
required: false,
type: 'boolean | ScrollBodyOption',
default: true,
description: `The global scroll body behavior of your application. This will be inherited by the related primitives.`
},
]"
/>
<!-- @include: @/meta/ConfigProvider.md -->

## Example

Expand All @@ -80,4 +63,28 @@ import { ConfigProvider } from 'radix-vue'
</ConfigProvider>
</template>
```


## Hydration issue (Vue < 3.5)

We expose a temporary workaround to allow current Nuxt (with version >3.10) project fix the current hydration issue by using [`useId`](https://nuxt.com/docs/api/composables/use-id) provided by Nuxt.

> Inspired by [Headless UI](https://github.com/tailwindlabs/headlessui/pull/2959)


```vue
<!-- in Nuxt's app.vue -->
<script setup lang="ts">
import { ConfigProvider } from 'radix-vue'
const useIdFunction = () => useId()
</script>
<template>
<ConfigProvider :use-id="useIdFunction">
</ConfigProvider>
</template>
```

4 changes: 2 additions & 2 deletions packages/radix-vue/src/Accordion/AccordionItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { ComputedRef, VNodeRef } from 'vue'
import type { CollapsibleRootProps } from '../Collapsible'
import { injectAccordionRootContext } from './AccordionRoot.vue'
import { createContext, useArrowNavigation, useForwardExpose, useId } from '@/shared'
import { createContext, useArrowNavigation, useForwardExpose } from '@/shared'
enum AccordionItemState {
Open = 'open',
Expand Down Expand Up @@ -83,7 +83,7 @@ provideAccordionItemContext({
dataState,
disabled,
dataDisabled,
triggerId: useId(),
triggerId: '',
currentRef,
currentElement,
value: computed(() => props.value),
Expand Down
2 changes: 2 additions & 0 deletions packages/radix-vue/src/Accordion/AccordionTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface AccordionTriggerProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { useId } from '@/shared'
import { injectAccordionItemContext } from './AccordionItem.vue'
import { injectAccordionRootContext } from './AccordionRoot.vue'
Expand All @@ -15,6 +16,7 @@ const props = defineProps<AccordionTriggerProps>()
const rootContext = injectAccordionRootContext()
const itemContext = injectAccordionItemContext()
itemContext.triggerId ||= useId(undefined, 'radix-vue-accordion-trigger')
function changeItem() {
if (itemContext.disabled.value)
return
Expand Down
3 changes: 2 additions & 1 deletion packages/radix-vue/src/Collapsible/CollapsibleContent.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { PrimitiveProps } from '@/Primitive'
import { useForwardExpose } from '@/shared'
import { useForwardExpose, useId } from '@/shared'
export interface CollapsibleContentProps extends PrimitiveProps {
/**
Expand All @@ -26,6 +26,7 @@ defineOptions({
const props = defineProps<CollapsibleContentProps>()
const rootContext = injectCollapsibleRootContext()
rootContext.contentId ||= useId(undefined, 'radix-vue-collapsible-content')
const presentRef = ref<InstanceType<typeof Presence>>()
const { forwardRef, currentElement } = useForwardExpose()
Expand Down
6 changes: 3 additions & 3 deletions packages/radix-vue/src/Collapsible/CollapsibleRoot.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import type { PrimitiveProps } from '@/Primitive'
import type { Ref } from 'vue'
import { createContext, useForwardExpose, useId } from '@/shared'
import { type Ref } from 'vue'
import { createContext, useForwardExpose } from '@/shared'
export interface CollapsibleRootProps extends PrimitiveProps {
/** The open state of the collapsible when it is initially rendered. <br> Use when you do not need to control its open state. */
Expand Down Expand Up @@ -54,7 +54,7 @@ const open = useVModel(props, 'open', emit, {
const disabled = useVModel(props, 'disabled')
provideCollapsibleRootContext({
contentId: useId(),
contentId: '',
disabled,
open,
onOpenToggle: () => {
Expand Down
4 changes: 3 additions & 1 deletion packages/radix-vue/src/Combobox/ComboboxContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,16 @@ export interface ComboboxContentProps extends ComboboxContentImplProps {
import { injectComboboxRootContext } from './ComboboxRoot.vue'
import ComboboxContentImpl from './ComboboxContentImpl.vue'
import { Presence } from '@/Presence'
import { useForwardExpose, useForwardPropsEmits } from '@/shared'
import { useForwardExpose, useForwardPropsEmits, useId } from '@/shared'
const props = defineProps<ComboboxContentProps>()
const emits = defineEmits<ComboboxContentEmits>()
const forwarded = useForwardPropsEmits(props, emits)
const { forwardRef } = useForwardExpose()
const rootContext = injectComboboxRootContext()
rootContext.contentId ||= useId(undefined, 'radix-vue-combobox-content')
</script>

<template>
Expand Down
2 changes: 1 addition & 1 deletion packages/radix-vue/src/Combobox/ComboboxGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Primitive } from '@/Primitive'
const props = defineProps<ComboboxGroupProps>()
const { currentRef, currentElement } = useForwardExpose()
const id = useId()
const id = useId(undefined, 'radix-vue-combobox-group')
const rootContext = injectComboboxRootContext()
const hasOptions = ref(false)
Expand Down
2 changes: 1 addition & 1 deletion packages/radix-vue/src/Combobox/ComboboxItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const isSelected = computed(() =>
)
const isFocused = computed(() => isEqual(rootContext.selectedValue.value, props.value))
const textId = useId()
const textId = useId(undefined, 'radix-vue-combobox-item')
const isInOption = computed(() =>
rootContext.isUserInputted.value
Expand Down
4 changes: 2 additions & 2 deletions packages/radix-vue/src/Combobox/ComboboxRoot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import type { Ref } from 'vue'
import type { Direction } from '@/shared/types'
import type { PrimitiveProps } from '@/Primitive'
import { createContext, useDirection, useFormControl, useForwardExpose, useId } from '@/shared'
import { createContext, useDirection, useFormControl, useForwardExpose } from '@/shared'
import { createCollection } from '@/Collection'
export type AcceptableValue = string | number | boolean | Record<string, any>
Expand Down Expand Up @@ -224,7 +224,7 @@ provideComboboxRootContext({
open,
onOpenChange,
filteredOptions,
contentId: useId(),
contentId: '',
inputElement,
onInputElementChange: val => inputElement.value = val,
onInputNavigation: async (val) => {
Expand Down
7 changes: 7 additions & 0 deletions packages/radix-vue/src/ConfigProvider/ConfigProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { createContext } from '@/shared'
interface ConfigProviderContextValue {
dir?: Ref<Direction>
scrollBody?: Ref<boolean | ScrollBodyOption>
useId?: () => string
}
export const [injectConfigProviderContext, provideConfigProviderContext]
Expand All @@ -22,6 +23,10 @@ export interface ConfigProviderProps {
* @type boolean | ScrollBodyOption
*/
scrollBody?: boolean | ScrollBodyOption
/**
* The global `useId` injection as a workaround for preventing hydration issue.
*/
useId?: () => string
}
</script>

Expand All @@ -31,13 +36,15 @@ import { toRefs } from 'vue'
const props = withDefaults(defineProps<ConfigProviderProps>(), {
dir: 'ltr',
scrollBody: true,
useId: undefined,
})
const { dir, scrollBody } = toRefs(props)
provideConfigProviderContext({
dir,
scrollBody,
useId: props.useId,
})
</script>

Expand Down
5 changes: 4 additions & 1 deletion packages/radix-vue/src/Dialog/DialogContentImpl.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type {
DismissableLayerEmits,
DismissableLayerProps,
} from '@/DismissableLayer'
import { useForwardExpose } from '@/shared'
import { useForwardExpose, useId } from '@/shared'
export type DialogContentImplEmits = DismissableLayerEmits & {
/**
Expand Down Expand Up @@ -47,6 +47,9 @@ const emits = defineEmits<DialogContentImplEmits>()
const rootContext = injectDialogRootContext()
const { forwardRef, currentElement: contentElement } = useForwardExpose()
rootContext.titleId ||= useId(undefined, 'radix-vue-dialog-title')
rootContext.descriptionId ||= useId(undefined, 'radix-vue-dialog-description')
onMounted(() => {
rootContext.contentElement = contentElement
})
Expand Down
8 changes: 4 additions & 4 deletions packages/radix-vue/src/Dialog/DialogRoot.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Ref } from 'vue'
import { createContext, useId } from '@/shared'
import { createContext } from '@/shared'
export interface DialogRootProps {
/** The controlled open state of the dialog. Can be binded as `v-model:open`. */
Expand Down Expand Up @@ -66,9 +66,9 @@ provideDialogRootContext({
onOpenToggle: () => {
open.value = !open.value
},
contentId: useId(),
titleId: useId(),
descriptionId: useId(),
contentId: '',
titleId: '',
descriptionId: '',
triggerElement,
contentElement,
})
Expand Down
5 changes: 3 additions & 2 deletions packages/radix-vue/src/Dialog/DialogTrigger.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<script lang="ts">
import type { PrimitiveProps } from '@/Primitive'
import { useForwardExpose } from '@/shared'
export interface DialogTriggerProps extends PrimitiveProps {}
</script>

<script setup lang="ts">
import { onMounted } from 'vue'
import { injectDialogRootContext } from './DialogRoot.vue'
import { useForwardExpose, useId } from '@/shared'
import { Primitive } from '@/Primitive'
const props = withDefaults(defineProps<DialogTriggerProps>(), {
Expand All @@ -16,6 +16,7 @@ const props = withDefaults(defineProps<DialogTriggerProps>(), {
const rootContext = injectDialogRootContext()
const { forwardRef, currentElement } = useForwardExpose()
rootContext.contentId ||= useId(undefined, 'radix-vue-dialog-content')
onMounted(() => {
rootContext.triggerElement = currentElement
})
Expand All @@ -28,7 +29,7 @@ onMounted(() => {
:type="as === 'button' ? 'button' : undefined"
aria-haspopup="dialog"
:aria-expanded="rootContext.open.value || false"
:aria-controls="rootContext.contentId"
:aria-controls="rootContext.open.value ? rootContext.contentId : undefined"
:data-state="rootContext.open.value ? 'open' : 'closed'"
@click="rootContext.onOpenToggle"
>
Expand Down

0 comments on commit d0517a2

Please sign in to comment.