Skip to content

Commit

Permalink
Merge pull request #141 from qwikerx/feat/bind-ref-to-dropdown-button
Browse files Browse the repository at this point in the history
feat: bind ref to dropdown trigger
  • Loading branch information
xmimiex authored Jun 24, 2024
2 parents 6091949 + bbf7f46 commit b6aa622
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 46 deletions.
3 changes: 1 addition & 2 deletions apps/web/src/generated-examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -869,8 +869,7 @@ export const examples: Record<string, Example[]> = {
},
{
title: 'User avatar',
description:
'You can use the `as` prop to set the trigger element to an image. The trigger passed as props should not be a button element as Dropdown use button element internally. This example can be used to show a list of menu items and options when a user is logged into your application.',
description: 'You can use the `as` prop to set the trigger element to an image.',
url: '/examples/[theme-rtl]/dropdown/06-dropdown-user-avatar',
content:
'import { component$ } from \'@builder.io/qwik\'\nimport { Dropdown } from \'flowbite-qwik\'\n\nexport default component$(() => {\n return (\n <div class="flex gap-5">\n <Dropdown\n as={\n <img\n class="w-8 h-8 rounded-full"\n src="https://res.cloudinary.com/dkht4mwqi/image/upload/f_auto,q_auto/v1718462568/flowbite-qwik/jpnykkz8ojq7ojgg4qta.jpg"\n alt="user photo"\n />\n }\n >\n <Dropdown.Item header>\n <span class="block text-sm">Bonnie Green</span>\n <span class="block truncate text-sm font-medium">[email protected]</span>\n </Dropdown.Item>\n <Dropdown.Item>Dashboard</Dropdown.Item>\n <Dropdown.Item>Settings</Dropdown.Item>\n <Dropdown.Item divider />\n <Dropdown.Item>Sign out</Dropdown.Item>\n </Dropdown>\n\n <Dropdown\n as={\n <img\n class="w-8 h-8 rounded-full"\n src="https://res.cloudinary.com/dkht4mwqi/image/upload/f_auto,q_auto/v1718462568/flowbite-qwik/jpnykkz8ojq7ojgg4qta.jpg"\n alt="user photo"\n />\n }\n >\n <Dropdown.Item header>\n <span class="block text-sm">Bonnie Green</span>\n <span class="block truncate text-sm font-medium">[email protected]</span>\n </Dropdown.Item>\n <Dropdown.Item>Dashboard</Dropdown.Item>\n <Dropdown.Item>Settings</Dropdown.Item>\n <Dropdown.Item divider />\n <Dropdown.Item>Sign out</Dropdown.Item>\n </Dropdown>\n\n <Dropdown\n as={\n <img\n class="w-8 h-8 rounded-full"\n src="https://res.cloudinary.com/dkht4mwqi/image/upload/f_auto,q_auto/v1718462568/flowbite-qwik/jpnykkz8ojq7ojgg4qta.jpg"\n alt="user photo"\n />\n }\n >\n <Dropdown.Item header>\n <span class="block text-sm">Bonnie Green</span>\n <span class="block truncate text-sm font-medium">[email protected]</span>\n </Dropdown.Item>\n <Dropdown.Item>Dashboard</Dropdown.Item>\n <Dropdown.Item>Settings</Dropdown.Item>\n <Dropdown.Item divider />\n <Dropdown.Item>Sign out</Dropdown.Item>\n </Dropdown>\n </div>\n )\n})',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/**
* title: User avatar
* description: You can use the `as` prop to set the trigger element to an image. The trigger passed as props should not be a button element as Dropdown use button element internally. This example can be used to show a list of menu items and options when a user is logged into your application.
* description: You can use the `as` prop to set the trigger element to an image.
*/

import { component$ } from '@builder.io/qwik'
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/routes/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export default component$(() => {
<Dropdown
title="Switch Flowbite theme"
as={
<Button square color="light" title="Switch Flowbite theme" tag="span">
<Button square color="light" title="Switch Flowbite theme">
<svg
class="w-4 h-4 text-gray-800 dark:text-white"
aria-hidden="true"
Expand Down
284 changes: 284 additions & 0 deletions apps/web/vite.config.ts.timestamp-1719213116855-dfc935387f531.mjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/lib/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "flowbite-qwik",
"version": "0.29.1",
"version": "0.29.2",
"description": "Unofficial Qwik components built for Flowbite and Tailwind CSS",
"keywords": [
"design-system",
Expand Down
2 changes: 1 addition & 1 deletion packages/lib/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export const Button = component$<ButtonProps>(
href={href}
target={href ? attrs.target : undefined}
//@ts-expect-error does not exist on other elements
disabled={ButtonComponent === 'button' ? disabled : undefined}
disabled={!href && ButtonComponent === 'button' ? disabled : undefined}
onClick$={attrs.onClick$}
{...attrs}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx'
import type { ButtonDuotoneGradient, ButtonGradient, ButtonMonochromeGradient, ButtonSize, ButtonVariant } from '../button-types'
import { ClassList, Component, HTMLAttributeAnchorTarget, Signal, useComputed$ } from '@builder.io/qwik'
import type { IconProps } from 'flowbite-qwik-icons'
Expand Down Expand Up @@ -249,43 +250,36 @@ export function useButtonClasses(props: UseButtonClassesProps) {
}
}

return twMerge(
[
backgroundClass,
hoverClass,
shadowClass,
props.pill.value && '!rounded-full',
props.disabled.value && 'cursor-not-allowed opacity-50',
isGradient && isOutline ? 'p-0.5' : sizeClasses.value,
(props.prefix || props.suffix || props.loading.value) && 'inline-flex items-center',
props.class?.value,
props.target?.value,
props.full.value && 'w-full',
'justify-center',
]
.filter((str) => str)
.join(' '),
)
return twMerge([
backgroundClass,
hoverClass,
shadowClass,
props.pill.value && '!rounded-full',
props.disabled.value && 'cursor-not-allowed opacity-50',
isGradient && isOutline ? 'p-0.5' : sizeClasses.value,
(props.prefix || props.suffix || props.loading.value) && 'inline-flex items-center',
clsx(props.class?.value),
props.target?.value,
props.full.value && 'w-full',
'justify-center',
])
})

const spanClasses = useComputed$(() => {
let classes = ''
let classes: string[] = []
if (!!props.gradient?.value && props.outline.value) {
// ONLY FOR GRADIENT OUTLINE BUTTON
classes = [
'relative bg-white dark:bg-gray-900 rounded-md inline-flex items-center',
sizeClasses.value,
!props.disabled.value ? 'group-hover:bg-opacity-0 transition-all ease-in duration-75' : '',
]
.filter((str) => str)
.join(' ')
}

return twMerge(classes)
})

return {
sizeClasses,
bindClasses,
spanClasses,
}
Expand Down
33 changes: 21 additions & 12 deletions packages/lib/src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
createElement,
Fragment,
Signal,
JSXNode,
} from '@builder.io/qwik'
import { getChild } from '~/utils/children-inspector'
import { Button } from '~/components/Button/Button'
Expand Down Expand Up @@ -255,19 +256,14 @@ type InnerTriggerAsProps = {
size: DropdownSize
inline: boolean
visible: boolean
triggerIsButton?: boolean
title?: string
ref: Signal<HTMLElement | undefined>
trigger?: JSXOutput
}
type CompProps = Partial<PropsOf<'button'>>

const InnerTriggerAs = component$<InnerTriggerAsProps>(({ size, trigger, ref, title, inline, visible }) => {
const { triggerInlineClasses } = useDropdownClasses(
useComputed$(() => size),
useComputed$(() => inline),
)
type CompProps = Partial<PropsOf<'button'>>

const InnerTriggerAs = component$<InnerTriggerAsProps>(({ trigger, ref, title, visible }) => {
const internalProps = useComputed$<CompProps>(() => {
return {
'aria-expanded': visible,
Expand All @@ -277,11 +273,24 @@ const InnerTriggerAs = component$<InnerTriggerAsProps>(({ size, trigger, ref, ti
}
})

return (
<button {...internalProps.value} class={trigger ? '' : triggerInlineClasses.value}>
{trigger}
</button>
)
const jsxNodeTrigger = trigger as JSXNode

const internalTrigger = useComputed$(() => {
const isButton =
jsxNodeTrigger.type === Button ? jsxNodeTrigger.props?.tag === 'button' || !jsxNodeTrigger.props?.tag : jsxNodeTrigger.type === 'button'

const type = isButton ? (jsxNodeTrigger.type as string) : 'button'
const triggerProps = jsxNodeTrigger.props || {}
const children = isButton ? jsxNodeTrigger.children : trigger

return createElement(type, {
...internalProps.value,
...triggerProps,
children,
})
})

return internalTrigger.value
})

/**
Expand Down
7 changes: 0 additions & 7 deletions packages/lib/src/composables/use-floating.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,8 @@ export function useFloating(placement: Placement = 'top', trigger = 'hover', noA
}[placement.split('-')[0]] as 'top' | 'right' | 'bottom' | 'left'
})

const updateElementsAccessibilityAttributes = $((val: boolean) => {
if (!triggerRef.value?.getAttribute('aria-expanded')) return

triggerRef.value.setAttribute('aria-expanded', val.toString())
})

const set$ = $((val: boolean) => {
isVisible.value = val
updateElementsAccessibilityAttributes(val)
})

useVisibleTask$(({ track, cleanup }) => {
Expand Down

0 comments on commit b6aa622

Please sign in to comment.