Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ConfigProvider): inject useId to provide composable from different framework #718

Merged
merged 12 commits into from
Mar 6, 2024
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
Loading
Loading