-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
close #202 Co-authored-by: Kia Ishii <[email protected]>
- Loading branch information
1 parent
29e84fb
commit f6661ed
Showing
15 changed files
with
1,089 additions
and
140 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
<script setup lang="ts"> | ||
import { ref } from 'vue' | ||
import SInputAddon from 'sefirot/components/SInputAddon.vue' | ||
import SInputText from 'sefirot/components/SInputText.vue' | ||
|
||
const input = ref<string | null>(null) | ||
</script> | ||
|
||
# SInputAddon | ||
|
||
`<SInputAddon>` is a special component that can be used to add extra label or action to other input. | ||
|
||
<Showcase | ||
path="/components/SInputText.vue" | ||
story="/stories-components-sinputtext-02-addons-story-vue" | ||
> | ||
<SInputText placeholder="johndoe" v-model="input"> | ||
<template #addon-before> | ||
<SInputAddon label="@" :clickable="false" /> | ||
</template> | ||
</SInputText> | ||
</Showcase> | ||
## Usage | ||
|
||
In order to use `<SInputAddon>`, you must inject it through slots. Currently, the supported components are listed below. | ||
|
||
- `<SInputText>` | ||
- [`<SInputNumber>`](/components/input-number) | ||
|
||
You may choose the position of the addon to be injected using the slot name either `#addon-before` or `#addon-after`. | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { ref } from 'vue' | ||
import SInputAddon from '@globalbrain/sefirot/lib/components/SInputAddon.vue' | ||
import SInputText from '@globalbrain/sefirot/lib/components/SInputText.vue' | ||
const input = ref<string | null>(null) | ||
</script> | ||
<template> | ||
<SInputNumber placeholder="johndoe" v-model="input"> | ||
<!-- Inject addon before the input box. --> | ||
<template #addon-before> | ||
<SInputAddon label="@" /> | ||
</template> | ||
<!-- Inject addon after the input box. --> | ||
<template #addon-after> | ||
<SInputAddon label="@" /> | ||
</template> | ||
</SInputNumber> | ||
</template> | ||
``` | ||
|
||
## Props | ||
|
||
Here are the list of props you may pass to the component. | ||
|
||
### `:label` | ||
|
||
Defines the label of the addon button. | ||
|
||
```ts | ||
import { IconifyIcon } from '@iconify/vue/dist/offline' | ||
|
||
interface Props { | ||
label?: string | IconifyIcon | ||
} | ||
``` | ||
|
||
```vue-html | ||
<SInputAddon label="@" /> | ||
``` | ||
|
||
### `:clickable` | ||
|
||
Defines whether the button should be clickable. Defaults to `true`. | ||
|
||
```ts | ||
interface Props { | ||
clickable?: boolean | ||
} | ||
``` | ||
|
||
```vue-html | ||
<SInputAddon label="@" :clickable="false" /> | ||
``` | ||
|
||
### `:dropdown` | ||
|
||
Defines dropdown option. When this prop is set, the dropdown will pop up when user clicks the addon button. In addition, if `:label` is not defined, the selected option's label will be used for label value. | ||
|
||
```ts | ||
import { DropdownSection } from '@globalbrain/sefirot/lib/composables/Dropdown' | ||
|
||
interface Props { | ||
dropdown?: DropdownSection[] | ||
} | ||
``` | ||
|
||
```vue | ||
<script setup lang="ts"> | ||
import { createDropdown } from '@globalbrain/sefirot/lib/composables/Dropdown' | ||
const dropdown = createDropdown([ | ||
{ | ||
type: 'filter', | ||
selected: 1, | ||
options: [ | ||
{ label: 'Item 01', value: 1, onClick: () => {} }, | ||
{ label: 'Item 02', value: 2, onClick: () => {} } | ||
] | ||
} | ||
]) | ||
</script> | ||
<template> | ||
<SInputAddon :dropdown="dropdown" /> | ||
</template> | ||
``` | ||
|
||
### `:dropdown-caret` | ||
|
||
Whether to show caret icon when `:dropdown` is defined. Defaults to `true`. | ||
|
||
```ts | ||
interface Props { | ||
dropdownCaret?: boolean | ||
} | ||
``` | ||
|
||
```vue-html | ||
<SInputAddon :dropdown="dropdown" :dropdown-caret="false" /> | ||
``` | ||
|
||
### `:dropdown-position` | ||
|
||
Fix the dropdown dialog position. If it's not defined, the dialog will be placed based on window space. | ||
|
||
```ts | ||
interface Props { | ||
dropdowpPosition?: 'top' | 'bottom' | ||
} | ||
``` | ||
|
||
```vue-html | ||
<SInputAddon :dropdown="dropdown" :dropdown-position="bottom" /> | ||
``` | ||
|
||
### `:disabled` | ||
|
||
Disable the addon action. When this prop is set, no events are emitted. | ||
|
||
```ts | ||
interface Props { | ||
disabled?: boolean | ||
} | ||
``` | ||
|
||
```vue-html | ||
<SInputAddon label="Button" disabled /> | ||
``` | ||
|
||
## Events | ||
|
||
Here are the list of events the component may emit. | ||
|
||
### `@click` | ||
|
||
Emits when the user clicks the addon button. It will not be emitted when `:clickable` is set to `false`, or `:disabled` is set. | ||
|
||
```ts | ||
interface Emits { | ||
(e: 'click'): void | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
<script setup lang="ts"> | ||
import IconCaretDown from '@iconify-icons/ph/caret-down-bold' | ||
import { | ||
DropdownSection, | ||
getSelectedOption, | ||
useManualDropdownPosition | ||
} from 'sefirot/composables/Dropdown' | ||
import { useFlyout } from 'sefirot/composables/Flyout' | ||
import { computed, ref } from 'vue' | ||
import { isString } from '../support/Utils' | ||
import SDropdown from './SDropdown.vue' | ||
import SIcon from './SIcon.vue' | ||
const props = withDefaults(defineProps<{ | ||
label?: any | ||
clickable?: boolean | ||
dropdown?: DropdownSection[] | ||
dropdownCaret?: boolean | ||
dropdowpPosition?: 'top' | 'bottom' | ||
disabled?: boolean | ||
}>(), { | ||
clickable: true, | ||
dropdown: () => [], | ||
dropdownCaret: true | ||
}) | ||
const emit = defineEmits<{ | ||
(e: 'click'): void | ||
}>() | ||
const container = ref<any>(null) | ||
const isFocused = ref(false) | ||
const classes = computed(() => [ | ||
{ clickable: props.clickable }, | ||
{ focused: isFocused.value }, | ||
{ disabled: props.disabled } | ||
]) | ||
const selectedOptionLabel = computed(() => { | ||
return getSelectedOption(props.dropdown)?.label ?? null | ||
}) | ||
const { isOpen, open } = useFlyout(container) | ||
const { position, update: updatePosition } = useManualDropdownPosition(container) | ||
function handleFocus() { | ||
if (!props.disabled) { | ||
isFocused.value = true | ||
} | ||
} | ||
function handleBlur() { | ||
if (!props.disabled) { | ||
isFocused.value = false | ||
} | ||
} | ||
function handleClickButton() { | ||
if (!props.disabled) { | ||
emit('click') | ||
if (props.dropdown.length) { | ||
updatePosition() | ||
open() | ||
} | ||
} | ||
} | ||
</script> | ||
|
||
<template> | ||
<div class="SInputAddon" :class="classes" ref="container" @click.stop> | ||
<component | ||
:is="clickable ? 'button' : 'div'" | ||
class="action" | ||
:disabled="clickable ? props.disabled : null" | ||
@focus="handleFocus" | ||
@blur="handleBlur" | ||
@click="handleClickButton" | ||
> | ||
<span class="action-label"> | ||
<SIcon | ||
v-if="props.label && !isString(props.label)" | ||
class="action-icon" | ||
:icon="props.label" | ||
/> | ||
<span v-else> | ||
{{ props.label ?? selectedOptionLabel }} | ||
</span> | ||
</span> | ||
|
||
<SIcon | ||
v-if="props.dropdown.length && props.dropdownCaret" | ||
class="caret" | ||
:icon="IconCaretDown" | ||
/> | ||
</component> | ||
|
||
<div v-if="isOpen" class="dialog" :class="position"> | ||
<SDropdown :sections="dropdown" /> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<style scoped lang="postcss"> | ||
.SInputAddon { | ||
position: relative; | ||
} | ||
|
||
.action { | ||
display: flex; | ||
align-items: center; | ||
height: 100%; | ||
background-color: var(--button-fill-mute-bg-color); | ||
transition: background-color 0.25s; | ||
|
||
.SInputAddon.clickable &:hover, | ||
.SInputAddon.clickable.focused & { | ||
background-color: var(--button-fill-mute-hover-bg-color); | ||
} | ||
|
||
.SInputAddon.clickable &:active { | ||
background-color: var(--button-fill-mute-active-bg-color); | ||
} | ||
|
||
.SInputAddon.disabled &, | ||
.SInputAddon.disabled.clickable &:hover, | ||
.SInputAddon.disabled.clickable &:active, | ||
.SInputAddon.disabled.clickable.focused & { | ||
background-color: var(--button-fill-mute-bg-color); | ||
cursor: not-allowed; | ||
} | ||
} | ||
|
||
.dialog { | ||
position: absolute; | ||
z-index: var(--z-index-dropdown); | ||
|
||
&.top { bottom: calc(100% + 8px); } | ||
&.bottom { top: calc(100% + 8px); } | ||
} | ||
</style> |
Oops, something went wrong.