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

Add NeTabs component and various fixes #36

Merged
merged 8 commits into from
Apr 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/components/NeBadge.vue
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ function onIconClick() {
<template v-if="icon">
<button
v-if="iconClickable"
type="button"
:class="[iconPosition == 'right' ? 'order-1' : '', iconButtonClasses, 'flex', 'rounded']"
@click="onIconClick"
>
Expand Down
15 changes: 12 additions & 3 deletions src/components/NeCombobox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,13 @@ const filteredOptions = computed(() => {
}

let results = allOptions.value.filter((option: NeComboboxOption) => {
return option.label.trim().toLowerCase().includes(query.value.trim().toLowerCase())
return (
// search in option label
option.label.trim().toLowerCase().includes(query.value.trim().toLowerCase()) ||
// search in option description
(option.description &&
option.description.trim().toLowerCase().includes(query.value.trim().toLowerCase()))
)
})

// user input
Expand Down Expand Up @@ -255,10 +261,13 @@ function selectMultipleOptionsFromModelValue() {
const selectedList: NeComboboxOption[] = []

for (const selectedOption of props.modelValue as NeComboboxOption[]) {
const optionFound = props.options.find((option) => option.id === selectedOption.id)
const optionFound = allOptions.value.find((option) => option.id === selectedOption.id)

if (optionFound) {
selectedList.push(optionFound)
} else if (props.acceptUserInput) {
userInputOptions.value.push(selectedOption)
selectedList.push(selectedOption)
}
}

Expand All @@ -282,7 +291,7 @@ function removeFromSelection(optionToRemove: NeComboboxOption) {
)
}

// detect clic outside to close the options list
// detect click outside to close the options list
onClickOutside(comboboxRef, () => onClickOutsideCombobox())
</script>

Expand Down
9 changes: 6 additions & 3 deletions src/components/NeInlineNotification.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,12 @@ const closeIconKindStyle: { [index: string]: string } = {
</button>
<button
v-if="secondaryButtonLabel"
:class="`ml-3 rounded-md px-2 py-1.5 text-sm font-medium transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
buttonsKindStyle[props.kind as string]
}`"
:class="[
`rounded-md px-2 py-1.5 text-sm font-medium transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 ${
buttonsKindStyle[props.kind as string]
}`,
{ 'ml-3': primaryButtonLabel }
]"
type="button"
@click="emit('secondaryClick')"
>
Expand Down
20 changes: 19 additions & 1 deletion src/components/NeLink.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,28 @@
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<script lang="ts" setup>
defineProps({
// useful on inverted backgrounds (e.g. inside a tooltip)
invertedTheme: {
type: Boolean,
default: false
}
})

const standardThemeStyle =
'text-primary-700 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200'

const invertedThemeStyle =
'text-primary-300 hover:text-primary-200 dark:text-primary-700 dark:hover:text-primary-800'
</script>
<template>
<a
rel="noreferrer"
class="cursor-pointer text-primary-700 hover:text-primary-800 dark:text-primary-300 dark:hover:text-primary-200"
:class="[
'cursor-pointer font-medium hover:underline',
invertedTheme ? invertedThemeStyle : standardThemeStyle
]"
>
<slot />
</a>
Expand Down
6 changes: 3 additions & 3 deletions src/components/NeRadioSelection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ const cardClasses: Record<RadioCardSize, string> = {
}

const iconClasses: Record<RadioCardSize, string> = {
md: 'h-7 w-7 pr-4',
lg: 'h-10 w-10 pr-5',
xl: 'h-12 w-12 pr-6'
md: 'h-7 w-7 pr-2',
lg: 'h-10 w-10 pr-3',
xl: 'h-12 w-12 pr-5'
}

const textClasses: Record<RadioCardSize, string> = {
Expand Down
107 changes: 107 additions & 0 deletions src/components/NeTabs.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!--
Copyright (C) 2024 Nethesis S.r.l.
SPDX-License-Identifier: GPL-3.0-or-later
-->

<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'

export interface Tab {
name: string
label: string
}

export interface Props {
tabs: Tab[]
selected?: string
srSelectTabLabel?: string
srTabsLabel?: string
}

const props = withDefaults(defineProps<Props>(), {
tabs: () => [],
selected: '',
srSelectTabLabel: 'Select a tab',
srTabsLabel: 'Tabs'
})

let currentTab = ref('')

onMounted(() => {
selectTabFromSelectedProp()
})

watch(currentTab, () => {
emit('selectTab', currentTab.value)
})

watch(
() => [props.selected],
() => {
selectTabFromSelectedProp()
}
)

function selectTabFromSelectedProp() {
let selectedTab = props.tabs[0]

if (props.selected) {
let selectedTabFound = props.tabs.find((tab: any) => tab.name === props.selected)

if (selectedTabFound) {
selectedTab = selectedTabFound
}
}
currentTab.value = selectedTab.name
}

function selectTab(tabName: any) {
currentTab.value = tabName
}

const emit = defineEmits(['selectTab'])
</script>

<template>
<div>
<!-- mobile tabs -->
<div class="sm:hidden">
<label for="tabs_select" class="sr-only">{{ srSelectTabLabel }}</label>
<select
id="tabs_select"
v-model="currentTab"
name="tabs_select"
class="block w-full rounded-md border-gray-300 bg-white py-2 pl-3 pr-10 text-base text-gray-700 focus:border-primary-500 focus:outline-none focus:ring-primary-500 dark:border-gray-700 dark:bg-gray-950 dark:text-gray-50 dark:focus:border-primary-500 dark:focus:ring-primary-500 sm:text-sm"
>
<option
v-for="tab in props.tabs"
:key="tab.name"
:selected="currentTab === tab.name"
:value="tab.name"
>
{{ tab.label }}
</option>
</select>
</div>
<!-- desktop tabs -->
<div class="hidden sm:block">
<div class="border-b border-gray-200 dark:border-gray-700">
<nav class="-mb-px flex space-x-8" :aria-label="srTabsLabel">
<a
v-for="tab in props.tabs"
:key="tab.name"
:class="[
currentTab === tab.name
? 'border-primary-600 text-primary-700 dark:border-primary-400 dark:text-primary-500'
: 'border-transparent text-gray-600 hover:border-gray-300 hover:text-gray-700 dark:text-gray-400 dark:hover:border-gray-700 dark:hover:text-gray-100',
'cursor-pointer whitespace-nowrap border-b-2 px-1 py-4 text-sm font-medium'
]"
:aria-current="currentTab === tab.name ? 'page' : undefined"
@click="selectTab(tab.name)"
>{{ tab.label }}</a
>
</nav>
</div>
</div>
</div>
</template>
2 changes: 2 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,12 @@ export { default as NeFormItemLabel } from '@/components/NeFormItemLabel.vue'
export { default as NeRadioSelection } from '@/components/NeRadioSelection.vue'
export { default as NePaginator } from '@/components/NePaginator.vue'
export { default as NeEmptyState } from '@/components/NeEmptyState.vue'
export { default as NeTabs } from '@/components/NeTabs.vue'

// types export
export type { NeComboboxOption } from '@/components/NeCombobox.vue'
export type { NePaginatorProps } from '@/components/NePaginator.vue'
export type { Tab } from '@/components/NeTabs.vue'

// library functions export
export {
Expand Down
27 changes: 25 additions & 2 deletions stories/NeLink.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ const meta: Meta<typeof NeLink> = {
title: 'Visual/NeLink',
component: NeLink,
tags: ['autodocs'],
args: {}
args: {
invertedTheme: false
}
}
export default meta
type Story = StoryObj<typeof meta>

const template = '<NeLink v-bind="args" class="text-sm">Go to Wikipedia</NeLink>'
const template =
'<NeLink v-bind="args" href="https://www.wikipedia.org/" target="_blank" class="text-sm">Go to Wikipedia</NeLink>'

export const Default: Story = {
render: (args) => ({
Expand All @@ -25,3 +28,23 @@ export const Default: Story = {
}),
args: {}
}

const invertedThemeTemplate = `<div class="text-sm p-4 bg-gray-800 dark:bg-gray-300">
<p class="mb-2 text-gray-300 dark:text-gray-800">
Inverted theme is useful on inverted backgrounds (e.g. inside a tooltip)
</p>
<NeLink v-bind="args" href="https://www.wikipedia.org/" target="_blank">
Go to Wikipedia
</NeLink>
</div>`

export const InvertedTheme: Story = {
render: (args) => ({
components: { NeLink },
setup() {
return { args }
},
template: invertedThemeTemplate
}),
args: { invertedTheme: true }
}
12 changes: 5 additions & 7 deletions stories/NeRadioSelection.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const Default: StoryObj<typeof NeRadioSelection> = {
return { args }
},
template: `
<NeRadioSelection v-model="args.modelValue" :label="args.label" :options="args.options" />
<NeRadioSelection v-bind="args" />
`
})
}
Expand All @@ -73,8 +73,7 @@ export const WithDescription: StoryObj<typeof NeRadioSelection> = {
return { args }
},
template: `
<NeRadioSelection v-model="args.modelValue" :description="args.description" :label="args.label"
:options="args.options" />
<NeRadioSelection v-bind="args" />
`
}),
args: {
Expand All @@ -89,8 +88,7 @@ export const CardPicker: StoryObj<typeof NeRadioSelection> = {
return { args }
},
template: `
<NeRadioSelection v-model="args.modelValue" :card="args.card" :grid-style="args.gridStyle"
:label="args.label" :options="args.options" />
<NeRadioSelection v-bind="args" />
`
}),
args: {
Expand Down Expand Up @@ -143,7 +141,7 @@ export const Disabled: StoryObj<typeof NeRadioSelection> = {
return { args }
},
template: `
<NeRadioSelection v-model="args.modelValue" :label="args.label" :options="args.options" />
<NeRadioSelection v-bind="args" />
`
}),
args: {
Expand All @@ -158,7 +156,7 @@ export const WithTooltip: StoryObj<typeof NeRadioSelection> = {
return { args }
},
template: `
<NeRadioSelection v-model="args.modelValue" :label="args.label" :options="args.options">\
<NeRadioSelection v-bind="args">\
<template #tooltip>\
<NeTooltip>\
<template #content>Tooltip</template>\
Expand Down
36 changes: 36 additions & 0 deletions stories/NeTabs.stories.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright (C) 2024 Nethesis S.r.l.
// SPDX-License-Identifier: GPL-3.0-or-later

import type { Meta, StoryObj } from '@storybook/vue3'

import { NeTabs } from '../src/main'

const meta = {
title: 'Visual/NeTabs',
component: NeTabs,
argTypes: {},
args: {
tabs: [
{ name: 'firstTab', label: 'First tab' },
{ name: 'secondTab', label: 'Second tab' },
{ name: 'thirdTab', label: 'Third tab' }
],
selected: 'secondTab'
} // default values
} satisfies Meta<typeof NeTabs>

export default meta
type Story = StoryObj<typeof meta>

const template = '<NeTabs v-bind="args" />'

export const Default: Story = {
render: (args) => ({
components: { NeTabs },
setup() {
return { args }
},
template: template
}),
args: {}
}