Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

Commit

Permalink
TS-ify VItemGroup and VPopover components
Browse files Browse the repository at this point in the history
  • Loading branch information
obulat committed Oct 6, 2022
1 parent 96ed423 commit 2405593
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 123 deletions.
19 changes: 10 additions & 9 deletions src/components/VItemGroup/VItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,26 @@
</div>
</template>

<script>
<script lang="ts">
import {
defineComponent,
inject,
ref,
computed,
watch,
PropType,
} from '@nuxtjs/composition-api'
import { warn } from '~/utils/console'
import VButton from '~/components/VButton.vue'
import VIcon from '~/components/VIcon/VIcon.vue'
import { VPopoverContentContextKey } from '~/components/VPopover/VPopoverContent.vue'
import {
VItemGroupContextKey,
VItemGroupFocusContextKey,
} from './VItemGroup.vue'
} from '~/models/item-group'
import VButton from '~/components/VButton.vue'
import VIcon from '~/components/VIcon/VIcon.vue'
import { VPopoverContentContextKey } from '~/components/VPopover/VPopoverContent.vue'
import checkmark from '~/assets/icons/checkmark.svg'
Expand Down Expand Up @@ -96,9 +97,9 @@ export default defineComponent({
* @variants 'button', 'VLink'
*/
as: {
type: String,
type: String as PropType<'button' | 'VLink'>,
default: 'button',
validator: (val) => ['button', 'VLink'].includes(val),
validator: (val: string) => ['button', 'VLink'].includes(val),
},
},
/**
Expand All @@ -116,7 +117,7 @@ export default defineComponent({
const isInPopover = inject(VPopoverContentContextKey, false)
const contextProps = inject(VItemGroupContextKey)
if (!contextProps) {
if (!contextProps || !focusContext) {
throw new Error(
'Do not use `VItem` outside of a `VItemGroup`. Use `VButton` instead.'
)
Expand Down
88 changes: 36 additions & 52 deletions src/components/VItemGroup/VItemGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,44 +19,31 @@
</div>
</template>

<script>
<script lang="ts">
import {
defineComponent,
provide,
ref,
readonly,
PropType,
} from '@nuxtjs/composition-api'
import { useI18n } from '~/composables/use-i18n'
import { keycodes } from '~/constants/key-codes'
import { ensureFocus } from '~/utils/reakit-utils/focus'
/**
* @typedef VItemGroupContext
* @property {'vertical' | 'horizontal'} direction
* @property {boolean} bordered
* @property {'menu' | 'radiogroup'} type
* @property {'small' | 'medium'} size
*/
/**
* @type {import('@nuxtjs/composition-api').InjectionKey<VItemGroupContext>}
*/
export const VItemGroupContextKey = Symbol('VItemGroupContext')
/**
* @typedef VItemGroupFocusContext
* @property {import('@nuxtjs/composition-api').Readonly<import('@nuxtjs/composition-api').Ref<boolean>>} isGroupFocused
* @property {(event: KeyboardEvent) => void} onItemKeyPress
* @property {import('@nuxtjs/composition-api').Readonly<import('@nuxtjs/composition-api').Ref<number>>} selectedCount
* @property {(selected: boolean, previousSelected: boolean) => void} setSelected
*/
/**
* @type {import('@nuxtjs/composition-api').InjectionKey<VItemGroupFocusContext>}
*/
export const VItemGroupFocusContextKey = Symbol('VItemGroupFocusContext')
import type {
ItemGroupDirection,
ItemGroupSize,
ItemGroupType,
} from '~/models/item-group'
import {
itemGroupDirections,
itemGroupSizes,
itemGroupTypes,
VItemGroupContextKey,
VItemGroupFocusContextKey,
} from '~/models/item-group'
const arrows = [
keycodes.ArrowUp,
Expand All @@ -74,11 +61,10 @@ export default defineComponent({
* @default 'vertical'
*/
direction: {
type: /** @type {import('@nuxtjs/composition-api').PropType<'vertical' | 'horizontal' | 'columns' >} */ (
String
),
type: String as PropType<ItemGroupDirection>,
default: 'vertical',
validate: (v) => ['vertical', 'horizontal', 'columns'].includes(v),
validate: (v: string) =>
(itemGroupDirections as unknown as string[]).includes(v),
},
/**
* Whether to render a bordered, separated list of items. When false each
Expand All @@ -105,26 +91,25 @@ export default defineComponent({
* @default 'menu'
*/
type: {
type: /** @type {import('@nuxtjs/composition-api').PropType<'menu' | 'radiogroup'>} */ (
String
),
type: String as PropType<ItemGroupType>,
default: 'menu',
validate: (v) => ['menu', 'radiogroup'].includes(v),
validate: (v: string) =>
(itemGroupTypes as unknown as string[]).includes(v),
},
/**
* Size of the item group corresponds to the size of the component.
*
* @default 'small'
*/
size: {
type: String,
type: String as PropType<ItemGroupSize>,
default: 'small',
validate: (val) => ['small', 'medium'].includes(val),
validate: (v: string) =>
(itemGroupSizes as unknown as string[]).includes(v),
},
},
setup(props) {
/** @type {import('@nuxtjs/composition-api').Ref<HTMLElement | undefined>} */
const nodeRef = ref()
const nodeRef = ref<HTMLElement | null>(null)
const isFocused = ref(false)
provide(VItemGroupContextKey, props)
Expand All @@ -135,29 +120,26 @@ export default defineComponent({
* because the DOM order gets reversed to be opposite the visual order relative to left/right movement.
*
* For vertical locales it should remain the same.
* @param {string} ltr
* @param {string} rtl
* @param ltr
* @param rtl
*/
const resolveArrow = (ltr, rtl) => {
const resolveArrow = (ltr: string, rtl: string) => {
return i18n.localeProperties.dir === 'rtl' &&
props.direction === 'horizontal'
? rtl
: ltr
}
/**
* @param {KeyboardEvent} event
*/
const onItemKeyPress = (event) => {
if (!arrows.includes(event.key) || !nodeRef.value) return
const onItemKeyPress = (event: KeyboardEvent): undefined | number => {
if (!(arrows as string[]).includes(event.key) || !nodeRef.value) return
event.preventDefault()
const target = event.target
// While VItem ultimately renders a button at the moment, that could change in the future, so using a data attribute selector makes it more flexible for the future
const items = Array.from(
nodeRef.value.querySelectorAll('[data-item-group-item]')
const items = Array.from<HTMLElement>(
nodeRef.value?.querySelectorAll('[data-item-group-item]')
)
const targetIndex = items.findIndex((item) => item === target)
Expand All @@ -175,16 +157,18 @@ export default defineComponent({
return ensureFocus(items[0])
}
return ensureFocus(items[targetIndex + 1])
default:
return
}
}
const selectedCount = ref(0)
/**
* @param {boolean} selected
* @param {boolean} previousSelected
* @param selected
* @param previousSelected
*/
const setSelected = (selected, previousSelected) => {
const setSelected = (selected: boolean, previousSelected: boolean) => {
if (previousSelected && !selected) selectedCount.value -= 1
if (!previousSelected && selected) selectedCount.value += 1
}
Expand Down
4 changes: 3 additions & 1 deletion src/components/VItemGroup/meta/VItemGroup.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { ref } from '@nuxtjs/composition-api'

import { itemGroupDirections } from '~/models/item-group'

import VItemGroup from '~/components/VItemGroup/VItemGroup.vue'
import VItem from '~/components/VItemGroup/VItem.vue'
import VIcon from '~/components/VIcon/VIcon.vue'
Expand All @@ -20,7 +22,7 @@ export default {
args: {
direction: {
type: 'radio',
options: ['vertical', 'horizontal'],
options: itemGroupDirections,
},
bordered: 'boolean',
},
Expand Down
22 changes: 10 additions & 12 deletions src/components/VPopover/VPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,20 @@
</div>
</template>

<script>
<script lang="ts">
import {
defineComponent,
ref,
watch,
reactive,
computed,
PropType,
} from '@nuxtjs/composition-api'
import VPopoverContent from '~/components/VPopover/VPopoverContent.vue'
import type { Placement, PositioningStrategy } from '@popperjs/core'
export default defineComponent({
name: 'VPopover',
components: { VPopoverContent },
Expand Down Expand Up @@ -97,9 +100,7 @@ export default defineComponent({
* @default 'bottom'
*/
placement: {
type: /** @type {import('@nuxtjs/composition-api').PropType<import('@popperjs/core').Placement>} */ (
String
),
type: String as PropType<Placement>,
},
/**
* The positioning strategy of the popover. If your reference element is in a fixed container
Expand All @@ -110,9 +111,7 @@ export default defineComponent({
* @default 'absolute'
*/
strategy: {
type: /** @type {import('@nuxtjs/composition-api').PropType<import('@popperjs/core').PositioningStrategy>} */ (
String
),
type: String as PropType<PositioningStrategy>,
},
/**
* The label of the popover content. Must be provided if `labelledBy` is empty.
Expand All @@ -133,7 +132,7 @@ export default defineComponent({
type: Number,
default: 50,
// TODO: extract valid z-indexes (these are from the tailwind config)
validator: (v) => [0, 10, 20, 30, 40, 50].includes(v),
validator: (v: number) => [0, 10, 20, 30, 40, 50].includes(v),
},
/**
* Whether the popover height should be clipped and made scrollable
Expand All @@ -153,8 +152,7 @@ export default defineComponent({
],
setup(_, { emit }) {
const visibleRef = ref(false)
/** @type {import('@nuxtjs/composition-api').Ref<HTMLElement | undefined>} */
const triggerContainerRef = ref()
const triggerContainerRef = ref<HTMLElement | null>(null)
const triggerA11yProps = reactive({
'aria-expanded': false,
Expand All @@ -163,11 +161,11 @@ export default defineComponent({
const triggerRef = computed(() =>
triggerContainerRef.value?.firstChild
? /** @type {HTMLElement} */ (triggerContainerRef.value.firstChild)
? (triggerContainerRef.value.firstChild as HTMLElement)
: undefined
)
watch([visibleRef], ([visible]) => {
watch(visibleRef, (visible) => {
triggerA11yProps['aria-expanded'] = visible
})
Expand Down
15 changes: 10 additions & 5 deletions src/components/VPopover/VPopoverContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { warn } from '~/utils/console'
import { defineEvent } from '~/types/emits'
import type { CSSProperties } from '@vue/runtime-dom'
import type { SetupContext } from 'vue'
export const VPopoverContentContextKey = Symbol(
'VPopoverContentContextKey'
Expand Down Expand Up @@ -74,17 +75,21 @@ export default defineComponent({
default: true,
},
triggerElement: {
type: (process.server ? Object : HTMLElement) as PropType<HTMLElement>,
type: (process.server
? Object
: HTMLElement) as PropType<HTMLElement | null>,
default: null,
},
placement: {
type: String as PropType<Placement>,
default: 'bottom-end',
validate: (v) => popoverPlacements.includes(v),
validate: (v: string) =>
(popoverPlacements as unknown as string[]).includes(v),
},
strategy: {
type: String as PropType<PositioningStrategy>,
default: 'absolute',
validate: (v) => ['absolute', 'fixed'].includes(v),
validate: (v: string) => ['absolute', 'fixed'].includes(v),
},
zIndex: {
type: Number,
Expand All @@ -111,12 +116,12 @@ export default defineComponent({
}
const propsRefs = toRefs(props)
const popoverRef = ref<HTMLElement | undefined>()
const popoverRef = ref<HTMLElement | null>(null)
const { onKeyDown, onBlur, maxHeightRef } = usePopoverContent({
popoverRef,
popoverPropsRefs: propsRefs,
emit,
emit: emit as SetupContext['emit'],
})
const heightProperties = computed(() => {
Expand Down
Loading

0 comments on commit 2405593

Please sign in to comment.