Skip to content

Commit

Permalink
feat(hover-card): new component (#334)
Browse files Browse the repository at this point in the history
* feat(hovercard): new component

* fix: updated hovercard

* fix: fixed hovercard components

* fix: update code

* chore: refactor

* chore: fix lint

* fix: code

* fix: lint issues

* chore: story add

* chore: fix ref

* chore: add new stories

* fix: typecheck

* chore: added stories for hovercard

* chore: refactor

* fix: lock

* chore: fix

* chore: update read me

---------

Co-authored-by: productdevbook <hi@productdevbook.com>
  • Loading branch information
mustafa60x and productdevbook authored Sep 10, 2023
1 parent d2599bd commit e02c7e8
Show file tree
Hide file tree
Showing 33 changed files with 1,755 additions and 11 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Website: [Oku Website](https://oku-ui.com)

Please read our [contributing guide](https://github.com/oku-ui/primitives/blob/master/CONTRIBUTING.md)

# TODO Components - 17/28
# TODO Components - 18/28

Enter the component you want most in the components, leave the emojis and follow.

Expand All @@ -36,7 +36,7 @@ Enter the component you want most in the components, leave the emojis and follow
| [Dialog](https://github.com/oku-ui/primitives/issues/9) | A modal dialog that interrupts the user's workflow to get a response | 🚧 In Progress | - |
| [Dropdown Menu](https://github.com/oku-ui/primitives/issues/10) | A menu that appears when a user interacts with an element's trigger | Not Started | - |
| [Form](https://github.com/oku-ui/primitives/issues/11) | A group of form controls | Not Started | - |
| [Hover Card](https://github.com/oku-ui/primitives/issues/12) | A card that appears when a user hovers over an element | 🚧 In Progress | - |
| [Hover Card](https://oku-ui.com/primitives/components/hover-card) | <span><a href="https://www.npmjs.com/package/@oku-ui/hover-card "><img src="https://img.shields.io/npm/v/@oku-ui/hover-card?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/hover-card"> <img src="https://img.shields.io/npm/dm/@oku-ui/hover-card?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/hover-card"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Label](https://oku-ui.com/primitives/components/label) | <span><a href="https://www.npmjs.com/package/@oku-ui/label "><img src="https://img.shields.io/npm/v/@oku-ui/label?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/label"> <img src="https://img.shields.io/npm/dm/@oku-ui/label?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/label"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> |
| [Menubar](https://github.com/oku-ui/primitives/issues/13) | A menu that appears when a user interacts with an element's trigger | Not Started | - |
| [Navigation Menu](https://github.com/oku-ui/primitives/issues/14) | A menu that appears when a user interacts with an element's trigger | Not Started | - |
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@oku-ui/dismissable-layer": "workspace:^",
"@oku-ui/focus-guards": "workspace:^",
"@oku-ui/focus-scope": "workspace:^",
"@oku-ui/hover-card": "workspace:^",
"@oku-ui/label": "workspace:^",
"@oku-ui/popover": "workspace:^",
"@oku-ui/popper": "workspace:^",
Expand Down Expand Up @@ -128,6 +129,7 @@
"@oku-ui/dismissable-layer": "workspace:^",
"@oku-ui/focus-guards": "workspace:^",
"@oku-ui/focus-scope": "workspace:^",
"@oku-ui/hover-card": "workspace:^",
"@oku-ui/label": "workspace:^",
"@oku-ui/popover": "workspace:^",
"@oku-ui/popper": "workspace:^",
Expand Down
10 changes: 7 additions & 3 deletions packages/components/arrow/src/arrow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import type { OkuElement, PrimitiveProps } from '@oku-ui/primitive'
import { Primitive, primitiveProps } from '@oku-ui/primitive'
import { useForwardRef } from '@oku-ui/use-composable'

export type ArrowNaviteElement = OkuElement<'svg'>
export type ArrowNaviteElement = Omit<OkuElement<'svg'>, 'width' | 'height'> & {
width?: number
height?: number
}

export type ArrowElement = Omit<SVGSVGElement, 'width' | 'height'> & {
width: number
height: number
width?: number
height?: number
}

export interface ArrowProps extends PrimitiveProps {
Expand Down
15 changes: 15 additions & 0 deletions packages/components/hover-card/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Hover Card
For sighted users to preview content available behind a link.

![@oku-ui/toast](./../../../.github/assets/og/oku-hover-card.jpg)


<span><a href="https://www.npmjs.com/package/@oku-ui/hover-card "><img src="https://img.shields.io/npm/v/@oku-ui/hover-card?style=flat&colorA=18181B&colorB=28CF8D" alt="Version"></a> </span> | <span> <a href="https://www.npmjs.com/package/@oku-ui/hover-card"> <img src="https://img.shields.io/npm/dm/@oku-ui/hover-card?style=flat&colorA=18181B&colorB=28CF8D" alt="Downloads"> </a> </span> | <span> <a href="https://oku-ui.com/primitives/components/hover-card"><img src="https://img.shields.io/badge/Open%20Documentation-18181B" alt="Website"></a> </span>

## Installation

```sh
$ pnpm add @oku-ui/hover-card
```

[Documentation](https://oku-ui.com/primitives/components/hover-card)
7 changes: 7 additions & 0 deletions packages/components/hover-card/build.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { defineBuildConfig } from 'unbuild'

const isClean = (process.env.CLEAN || 'false') === 'true'
export default defineBuildConfig({
declaration: true,
clean: isClean,
})
58 changes: 58 additions & 0 deletions packages/components/hover-card/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"name": "@oku-ui/hover-card",
"type": "module",
"version": "0.2.3",
"license": "MIT",
"source": "src/index.ts",
"funding": "https://github.com/sponsors/productdevbook",
"homepage": "https://oku-ui.com/primitives",
"repository": {
"type": "git",
"url": "git+https://github.com/oku-ui/primitives.git",
"directory": "packages/components/hover-card"
},
"bugs": {
"url": "https://github.com/oku-ui/primitives/issues"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"engines": {
"node": ">=18"
},
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub",
"clean": "rimraf ./dist && rimraf ./node_modules"
},
"peerDependencies": {
"vue": "^3.3.0"
},
"dependencies": {
"@floating-ui/vue": "^1.0.2",
"@oku-ui/dismissable-layer": "0.4.0-alpha.3",
"@oku-ui/popper": "latest",
"@oku-ui/portal": "latest",
"@oku-ui/presence": "0.4.0-alpha.3",
"@oku-ui/primitive": "latest",
"@oku-ui/provide": "latest",
"@oku-ui/slot": "0.4.0-alpha.3",
"@oku-ui/use-composable": "latest",
"@oku-ui/utils": "latest",
"@oku-ui/visually-hidden": "0.4.0-alpha.3"
},
"devDependencies": {
"tsconfig": "workspace:^"
},
"publishConfig": {
"access": "public"
}
}
159 changes: 159 additions & 0 deletions packages/components/hover-card/src/hoverCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import type { PropType, Ref } from 'vue'
import { computed, defineComponent, h, onBeforeUnmount, ref, toRefs, useModel } from 'vue'
import { primitiveProps } from '@oku-ui/primitive'
import { useControllable } from '@oku-ui/use-composable'
import { createProvideScope } from '@oku-ui/provide'
import { OkuPopper, createPopperScope } from '@oku-ui/popper'
import { scopeHoverCardProps } from './utils'

export const HOVERCARD_NAME = 'OkuHoverCard'

const [createHoverCardProvider, createHoverCardScope] = createProvideScope(HOVERCARD_NAME, [
createPopperScope,
])

export const usePopperScope = createPopperScope()

type HoverCardProvideValue = {
open: Ref<boolean>
onOpenChange(open: boolean): void
onOpen(): void
onClose(): void
onDismiss(): void
hasSelectionRef: Ref<boolean>
isPointerDownOnContentRef: Ref<boolean>
}

export const [hoverCardProvide, useHoverCardInject]
= createHoverCardProvider<HoverCardProvideValue>(HOVERCARD_NAME)

export interface HoverCardProps {
open?: boolean
defaultOpen?: boolean
openDelay?: number
closeDelay?: number
}

export type HoverCardEmits = {
'update:modelValue': [open: boolean]
'openChange': [open: boolean]
}

export const hoverCardProps = {
props: {
modelValue: {
type: [Boolean] as PropType<boolean | undefined>,
default: undefined,
},
open: {
type: Boolean as PropType<boolean | undefined>,
default: undefined,
},
defaultOpen: {
type: Boolean as PropType<boolean>,
default: undefined,
},
openDelay: {
type: Number as PropType<number | undefined>,
default: 700,
},
closeDelay: {
type: Number as PropType<number | undefined>,
default: 300,
},
...primitiveProps,
},
emits: {
// eslint-disable-next-line unused-imports/no-unused-vars
'update:modelValue': (open: boolean) => true,
// eslint-disable-next-line unused-imports/no-unused-vars
'openChange': (open: boolean) => true,
},
}

const hoverCard = defineComponent({
name: HOVERCARD_NAME,
inheritAttrs: false,
props: {
...hoverCardProps.props,
...scopeHoverCardProps,
},
setup(props, { slots, emit }) {
const {
scopeOkuHoverCard,
open: openProp,
defaultOpen,
openDelay: openDelayProp,
closeDelay: closeDelayProp,
} = toRefs(props)

const popperScope = usePopperScope(scopeOkuHoverCard.value)
const openTimerRef = ref(0)
const closeTimerRef = ref(0)
const hasSelectionRef = ref(false)
const isPointerDownOnContentRef = ref(false)

const modelValue = useModel(props, 'modelValue')
const proxyChecked = computed({
get: () => modelValue.value !== undefined ? modelValue.value : openProp.value !== undefined ? openProp.value : undefined,
set: () => {
},
})

const { state, updateValue } = useControllable({
prop: computed(() => proxyChecked.value),
defaultProp: computed(() => defaultOpen.value),
onChange: (value) => {
emit('openChange', value)
modelValue.value = value
},
initialValue: false,
})

const handleOpen = () => {
clearTimeout(closeTimerRef.value)
openTimerRef.value = window.setTimeout(() => {
updateValue(true)
}, openDelayProp.value)
}

const handleClose = () => {
clearTimeout(openTimerRef.value)
if (!hasSelectionRef.value && !isPointerDownOnContentRef.value) {
closeTimerRef.value = window.setTimeout(() => {
updateValue(false)
}, closeDelayProp.value)
}
}

const handleDismiss = () => {
updateValue(false)
}

onBeforeUnmount(() => {
clearTimeout(openTimerRef.value)
clearTimeout(closeTimerRef.value)
})

hoverCardProvide({
scope: scopeOkuHoverCard.value,
open: computed(() => state.value || false),
onOpenChange: open => updateValue(open),
onOpen: () => handleOpen(),
onClose: () => handleClose(),
onDismiss: () => handleDismiss(),
hasSelectionRef,
isPointerDownOnContentRef,
})

return () => h(OkuPopper, {
...popperScope,
}, slots)
},
})

export const OkuHoverCard = hoverCard

export {
createHoverCardScope,
}
55 changes: 55 additions & 0 deletions packages/components/hover-card/src/hoverCardArrow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { defineComponent, h, mergeProps, reactive } from 'vue'
import { reactiveOmit, useForwardRef } from '@oku-ui/use-composable'
import { OkuPopperArrow, type PopperArrowElement, type PopperArrowNaviteElement, type PopperArrowProps, popperArrowProps } from '@oku-ui/popper'
import { usePopperScope } from './hoverCard'
import { scopeHoverCardProps } from './utils'

const ARROW_NAME = 'OkuHoverCardArrow'

export type HoverCardArrowNaviteElement = PopperArrowNaviteElement
export type HoverCardArrowElement = PopperArrowElement
export interface HoverCardArrowProps extends PopperArrowProps { }

export const hoverCardArrowProps = {
props: {
...popperArrowProps.props,
},
emits: {
...popperArrowProps.emits,
},
}

const hoverCardArrow = defineComponent({
name: ARROW_NAME,
components: {
OkuPopperArrow,
},
inheritAttrs: false,
props: {
...hoverCardArrowProps.props,
...scopeHoverCardProps,
},
emits: hoverCardArrowProps.emits,
setup(props, { attrs, slots }) {
const { scopeOkuHoverCard, ...arrowProps } = props

const _reactive = reactive(arrowProps)
const reactiveArrowProps = reactiveOmit(_reactive, (key, _value) => key === undefined)

const forwardedRef = useForwardRef()
const popperScope = usePopperScope(scopeOkuHoverCard)

// if the arrow is inside the `VisuallyHidden`, we don't want to render it all to
// prevent issues in positioning the arrow due to the duplicate
return () => h(OkuPopperArrow, {
...popperScope,
...mergeProps(attrs, reactiveArrowProps),
ref: forwardedRef,
}, slots)
},
})

export const OkuHoverCardArrow = hoverCardArrow as typeof hoverCardArrow &
(new () => {
$props: HoverCardArrowNaviteElement
})
Loading

0 comments on commit e02c7e8

Please sign in to comment.