Skip to content

Commit

Permalink
feat(list): new component
Browse files Browse the repository at this point in the history
closes #11
  • Loading branch information
jd-solanki committed Sep 18, 2022
1 parent 4b5cba9 commit fe8fd9c
Show file tree
Hide file tree
Showing 13 changed files with 605 additions and 109 deletions.
2 changes: 1 addition & 1 deletion packages/anu-vue/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ export { ABtn } from './btn'
export { ACard } from './card'
export { ACheckbox } from './checkbox'
export { ADialog } from './dialog'

export { ADrawer } from './drawer'
export { AInput } from './input'
export { AList } from './list'
export { ARadio } from './radio'
export { ASelect } from './select'
export { ASwitch } from './switch'
Expand Down
187 changes: 187 additions & 0 deletions packages/anu-vue/src/components/list/AList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import type { PropType } from 'vue'
import { computed, defineComponent } from 'vue'
import { useGroupModel } from '../../composables'
import { ATypography } from '../typography'
import { useLayer, useProps as useLayerProps } from '@/composables/useLayer'

import { AAvatar, isAvatarUsed } from '@/components/avatar'
import type { AvatarOnlyProps } from '@/components/avatar/props'

// TODO: Reuse the existing props and its types. Maybe if we create AListItem component then we can reuse prop types.
interface ListItem extends AvatarOnlyProps {
title: string | string[]
subtitle?: string | string[]
text: string | string[]
src?: string
value?: any
disable?: boolean
$avatar?: { string: any }

// color: 'primary' | 'success' | 'info' | 'warning' | 'danger'
// variant: 'fill' | 'outline' | 'light' | 'text'
// states?: boolean
}

export const AList = defineComponent({
name: 'AList',
props: {
...useLayerProps({
variant: {
default: 'text',
},
states: {
default: true,
},
}),
items: {
type: Array as PropType<ListItem[]>,
required: true,
},
multi: {
type: Boolean,
default: false,
},
modelValue: {
type: [String, Number, Object],
default: null,
},
avatarAppend: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
setup(props, { slots, emit }) {
const { getLayerClasses } = useLayer()
const { options, select: selectListItem, value } = useGroupModel({
options: props.items[0].value ? props.items.map(i => i.value) : props.items.length,
multi: props.multi,
})
const isAvatarPropsUsed = computed(() => {
return isAvatarUsed(props.items[0])
})

// ℹ️ [Configurable UI] This is helper function to extract the content & classes from prop/value
// TODO: We might need to move this to utils
const getArrayValue = computed(() => (val: string | string[] | Object[]) => {
const [content, classes] = val === undefined
? []
: typeof val === 'string'
? [val]
: val

return { content, classes }
})

// 👉 Icon renderer
const iconRenderer = (icon: string | string[] | Object[]) => {
const { content: iconClass, classes } = getArrayValue.value(icon)

return <i class={[iconClass, classes]}></i>
}

// 👉 Avatar Renderer
const avatarRenderer = (
content: typeof props.items[number]['content'],
src: typeof props.items[number]['src'],
alt: typeof props.items[number]['alt'],
icon: typeof props.items[number]['icon'],
$avatar: typeof props.items[number]['$avatar'],
) => {
const _alt = alt || 'avatar'

return <AAvatar
content={content}
src={src}
alt={_alt}
icon={icon}
{...$avatar}
/>
}

const handleListItemClick = (index: number) => {
const itemValue = options.value[index].value
selectListItem(itemValue)
if (props.modelValue !== null)
emit('update:modelValue', value.value)
}

// 👉 List items
const listItems = computed(() => props.items.map((listItem, itemIndex) => {
// ℹ️ Reduce the size of title to 1rem. We did the same in ACard as well.
let titleProp: string[] | undefined
if (listItem.title) {
// if title property is string
if (typeof listItem.title === 'string') {
titleProp = [listItem.title, 'text-base']
}

// title property is array
else {
const [textContent, textClasses] = listItem.title
titleProp = [textContent, `${textClasses} uno-layer-base-text-sm`]
}
}

const isActive = computed(() => options.value[itemIndex].isSelected)

const layerProps = computed(() => {
return {
states: props.states,
color: isActive.value ? props.color || 'primary' : undefined,
variant: isActive.value ? props.variant || 'light' : 'text',
}
})

return <li
onClick={() => handleListItemClick(itemIndex)}
class={[
'a-list-item',
{ 'a-layer-active': value.value === itemIndex },
{ 'opacity-50 pointer-events-none': listItem.disable },
props.modelValue !== null
? [...getLayerClasses(layerProps.value, { statesClass: 'states:10' }), 'cursor-pointer']
: '',
'flex items-center gap-$a-list-item-gap m-$a-list-item-margin p-$a-list-item-padding min-h-$a-list-item-min-height',
]}>
{
slots.prepend
? slots.prepend({ listItem, itemIndex })
: isAvatarPropsUsed.value && !props.avatarAppend
? avatarRenderer(listItem.content, listItem.src, listItem.alt, listItem.icon, listItem.$avatar)
: null
}
<ATypography class="flex-grow" title={titleProp} subtitle={listItem.subtitle} text={listItem.text}></ATypography>
{
slots.append
? slots.append({ listItem, itemIndex })
: isAvatarPropsUsed.value && props.avatarAppend
? avatarRenderer(listItem.content, listItem.src, listItem.alt, listItem.icon, listItem.$avatar)
: null
}
</li>
}))

// 👉 Return
return () => <ul class="a-list grid gap-$a-list-gap">
{/* 👉 before slot */}
{
slots.before
? <li>{slots.before?.()}</li>
: null
}

{/* 👉 List items */}
{slots.default ? slots.default() : listItems.value}

{/* 👉 after slot */}
{
slots.after
? <li>{slots.after?.()}</li>
: null
}
</ul>
},
})

export type AList = InstanceType<typeof AList>
2 changes: 2 additions & 0 deletions packages/anu-vue/src/components/list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AList } from './AList'

8 changes: 8 additions & 0 deletions packages/anu-vue/src/components/list/props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ComponentObjectPropsOptions } from 'vue'

export const props: ComponentObjectPropsOptions = {
items: {
type: Array,
required: true,
},
} as const
Loading

0 comments on commit fe8fd9c

Please sign in to comment.