nexa-ui-kit 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/NBadge.nexa +40 -0
- package/dist/NBottomSheet.nexa +124 -0
- package/dist/NButton.nexa +123 -0
- package/dist/NCard.nexa +74 -0
- package/dist/NInput.nexa +116 -0
- package/dist/NModal.nexa +165 -0
- package/dist/NSelect.nexa +169 -0
- package/dist/NToastContainer.nexa +86 -0
- package/dist/NTooltip.nexa +115 -0
- package/dist/components/NAlert.js +134 -0
- package/dist/components/NAlert.nexa +115 -0
- package/dist/components/NAutocomplete.js +94 -0
- package/dist/components/NAutocomplete.nexa +58 -0
- package/dist/components/NAvatar.js +75 -0
- package/dist/components/NAvatar.nexa +67 -0
- package/dist/components/NBadge.js +74 -0
- package/dist/components/NBadge.nexa +61 -0
- package/dist/components/NBottomSheet.js +149 -0
- package/dist/components/NBottomSheet.nexa +145 -0
- package/dist/components/NButton.js +284 -0
- package/dist/components/NButton.nexa +275 -0
- package/dist/components/NCard.js +117 -0
- package/dist/components/NCard.nexa +100 -0
- package/dist/components/NCheckbox.js +108 -0
- package/dist/components/NCheckbox.nexa +90 -0
- package/dist/components/NChips.js +72 -0
- package/dist/components/NChips.nexa +57 -0
- package/dist/components/NDataTable.js +252 -0
- package/dist/components/NDataTable.nexa +186 -0
- package/dist/components/NDatepicker.js +379 -0
- package/dist/components/NDatepicker.nexa +367 -0
- package/dist/components/NForm.js +132 -0
- package/dist/components/NForm.nexa +133 -0
- package/dist/components/NFormField.js +173 -0
- package/dist/components/NFormField.nexa +171 -0
- package/dist/components/NInput.js +311 -0
- package/dist/components/NInput.nexa +311 -0
- package/dist/components/NInputNumber.js +202 -0
- package/dist/components/NInputNumber.nexa +199 -0
- package/dist/components/NModal.js +221 -0
- package/dist/components/NModal.nexa +221 -0
- package/dist/components/NMultiSelect.js +156 -0
- package/dist/components/NMultiSelect.nexa +77 -0
- package/dist/components/NPaginator.js +117 -0
- package/dist/components/NPaginator.nexa +77 -0
- package/dist/components/NPassword.js +193 -0
- package/dist/components/NPassword.nexa +178 -0
- package/dist/components/NProgressBar.js +127 -0
- package/dist/components/NProgressBar.nexa +111 -0
- package/dist/components/NRadio.js +96 -0
- package/dist/components/NRadio.nexa +81 -0
- package/dist/components/NSelect.js +468 -0
- package/dist/components/NSelect.nexa +452 -0
- package/dist/components/NSkeleton.js +98 -0
- package/dist/components/NSkeleton.nexa +74 -0
- package/dist/components/NSwitch.js +92 -0
- package/dist/components/NSwitch.nexa +76 -0
- package/dist/components/NTabs.js +129 -0
- package/dist/components/NTabs.nexa +113 -0
- package/dist/components/NTag.js +108 -0
- package/dist/components/NTag.nexa +93 -0
- package/dist/components/NToastContainer.js +242 -0
- package/dist/components/NToastContainer.nexa +221 -0
- package/dist/components/NTooltip.js +163 -0
- package/dist/components/NTooltip.nexa +166 -0
- package/dist/components/NTreeMenu.js +151 -0
- package/dist/components/NTreeMenu.nexa +142 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +34 -0
- package/dist/services/FloatingOverlay.d.ts +27 -0
- package/dist/services/FloatingOverlay.js +98 -0
- package/dist/services/FormValidation.d.ts +8 -0
- package/dist/services/FormValidation.js +46 -0
- package/dist/services/ToastService.d.ts +16 -0
- package/dist/services/ToastService.js +26 -0
- package/dist/styles/theme.d.ts +1 -0
- package/dist/styles/theme.js +144 -0
- package/package.json +32 -0
- package/src/components/NAlert.nexa +115 -0
- package/src/components/NAutocomplete.nexa +58 -0
- package/src/components/NAvatar.nexa +67 -0
- package/src/components/NBadge.nexa +61 -0
- package/src/components/NBottomSheet.nexa +145 -0
- package/src/components/NButton.nexa +275 -0
- package/src/components/NCard.nexa +100 -0
- package/src/components/NCheckbox.nexa +90 -0
- package/src/components/NChips.nexa +57 -0
- package/src/components/NDataTable.nexa +186 -0
- package/src/components/NDatepicker.nexa +367 -0
- package/src/components/NForm.nexa +133 -0
- package/src/components/NFormField.nexa +171 -0
- package/src/components/NInput.nexa +311 -0
- package/src/components/NInputNumber.nexa +199 -0
- package/src/components/NModal.nexa +221 -0
- package/src/components/NMultiSelect.nexa +77 -0
- package/src/components/NPaginator.nexa +77 -0
- package/src/components/NPassword.nexa +178 -0
- package/src/components/NProgressBar.nexa +111 -0
- package/src/components/NRadio.nexa +81 -0
- package/src/components/NSelect.nexa +452 -0
- package/src/components/NSkeleton.nexa +74 -0
- package/src/components/NSwitch.nexa +76 -0
- package/src/components/NTabs.nexa +113 -0
- package/src/components/NTag.nexa +93 -0
- package/src/components/NToastContainer.nexa +221 -0
- package/src/components/NTooltip.nexa +166 -0
- package/src/components/NTreeMenu.nexa +142 -0
- package/src/index.ts +36 -0
- package/src/services/FloatingOverlay.ts +133 -0
- package/src/services/FormValidation.ts +44 -0
- package/src/services/ToastService.ts +41 -0
- package/src/shims.d.ts +5 -0
- package/src/styles/theme.ts +146 -0
- package/src/styles/tokens.css +170 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { signal, computed, effect, onBeforeUnmount, h, hText, defineComponent, registerComponent, reloadComponent, injectStyle, Teleport } from 'nexa-framework'
|
|
2
|
+
import { trackFloatingOverlay } from '../services/FloatingOverlay.js'
|
|
3
|
+
|
|
4
|
+
const _sfc_main = defineComponent({
|
|
5
|
+
__scopeId: 'data-v-741257a1',
|
|
6
|
+
__hmrId: 'NAutocomplete_nexa',
|
|
7
|
+
props: { modelValue: { type: String, default: '' }, options: { type: Array, default: () => [] }, placeholder: { type: String, default: '' }, label: { type: String, default: '' }, disabled: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, clearable: { type: Boolean, default: false }, minLength: { type: Number, default: 1 }, delay: { type: Number, default: 200 }, loading: { type: Boolean, default: false }, dropdown: { type: Boolean, default: false }, emptyMessage: { type: String, default: 'No results' }, placement: { type: String, default: 'auto' } },
|
|
8
|
+
emits: ['update:modelValue', 'select', 'complete', 'clear'],
|
|
9
|
+
setup(props, setupContext) {
|
|
10
|
+
const { emit, slots, slots: $slots } = setupContext
|
|
11
|
+
const instanceId = `n-ac-${Math.random().toString(16).slice(2)}`
|
|
12
|
+
const listboxId = `${instanceId}-listbox`
|
|
13
|
+
const inputId = `${instanceId}-input`
|
|
14
|
+
const isOpen = signal(false)
|
|
15
|
+
const query = signal('')
|
|
16
|
+
const focusedIndex = signal(-1)
|
|
17
|
+
const rootEl = signal(null)
|
|
18
|
+
const popupStyle = signal({})
|
|
19
|
+
const resolvedPlacement = signal('bottom')
|
|
20
|
+
let stopTracking = null
|
|
21
|
+
let completeTimer = null
|
|
22
|
+
const normalizeOption = (opt) => { if (opt == null) return { label: '', value: null, raw: opt }; if (typeof opt === 'string' || typeof opt === 'number') return { label: String(opt), value: opt, raw: opt }; if (typeof opt === 'object') { const label = 'label' in opt ? String(opt.label) : String(opt.value ?? ''); return { label, value: 'value' in opt ? opt.value : label, raw: opt } } return { label: String(opt), value: opt, raw: opt } }
|
|
23
|
+
const normalizedOptions = computed(() => props.options.map(normalizeOption))
|
|
24
|
+
const filteredOptions = computed(() => { const q = query.value.trim().toLowerCase(); if (!q) return normalizedOptions.value; return normalizedOptions.value.filter(opt => opt.label.toLowerCase().includes(q)) })
|
|
25
|
+
const activeId = computed(() => { const idx = focusedIndex.value; if (idx < 0) return ''; const opt = filteredOptions.value[idx]; return opt ? `${instanceId}-opt-${idx}` : '' })
|
|
26
|
+
const close = () => { if (!isOpen.value) return; isOpen.value = false; focusedIndex.value = -1; if (stopTracking) { stopTracking(); stopTracking = null } }
|
|
27
|
+
const open = (e) => { if (props.disabled || props.readonly || isOpen.value) return; isOpen.value = true; const t = e?.currentTarget || e?.target; rootEl.value = t?.closest ? t.closest(`[data-autocomplete-root="${instanceId}"]`) : null; stopTracking = trackFloatingOverlay({ isOpen: () => isOpen.value, getAnchor: () => { const root = rootEl.value; return root ? root.querySelector('.n-ac-input-wrap') : null }, getPopup: () => document.querySelector(`[data-autocomplete-popup="${instanceId}"]`), placement: props.placement, align: 'start', matchWidth: true, minWidth: 240, gap: 8, margin: 8, zIndex: 9999, onUpdate: (r) => { popupStyle.value = r.style; resolvedPlacement.value = r.placement }, isEventInside: (ev) => { const el = ev.target; if (!el || typeof el.closest !== 'function') return false; return !!(el.closest(`[data-autocomplete-root="${instanceId}"]`) || el.closest(`[data-autocomplete-popup="${instanceId}"]`)) }, onOutside: () => close() }) }
|
|
28
|
+
const requestComplete = () => { clearTimeout(completeTimer); completeTimer = setTimeout(() => emit('complete', query.value), props.delay) }
|
|
29
|
+
const onInput = (e) => { const value = e.target.value; query.value = value; emit('update:modelValue', value); if (value.trim().length >= props.minLength) { open(e); requestComplete() } else close() }
|
|
30
|
+
const onFocus = (e) => { query.value = props.modelValue || ''; if ((props.modelValue || '').trim().length >= props.minLength) { open(e); requestComplete() } }
|
|
31
|
+
const onKeydown = (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); return } if (!isOpen.value) { if (e.key === 'ArrowDown') { e.preventDefault(); open(e); focusedIndex.value = 0 } return } const items = filteredOptions.value; if (e.key === 'ArrowDown') { e.preventDefault(); focusedIndex.value = Math.min(focusedIndex.value + 1, items.length - 1); return } if (e.key === 'ArrowUp') { e.preventDefault(); focusedIndex.value = Math.max(focusedIndex.value - 1, 0); return } if (e.key === 'Enter') { e.preventDefault(); const opt = items[focusedIndex.value]; if (!opt) return; emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() } }
|
|
32
|
+
const selectOption = (opt) => { emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() }
|
|
33
|
+
const clearValue = () => { if (props.disabled || props.readonly) return; emit('update:modelValue', ''); emit('clear'); query.value = ''; close() }
|
|
34
|
+
const openAll = (e) => { if (props.disabled || props.readonly) return; query.value = props.modelValue || ''; open(e); requestComplete() }
|
|
35
|
+
effect(() => { if (!isOpen.value) return; if (focusedIndex.value < 0 && filteredOptions.value.length > 0) focusedIndex.value = 0 })
|
|
36
|
+
onBeforeUnmount(() => { clearTimeout(completeTimer); close() })
|
|
37
|
+
return { instanceId, listboxId, inputId, isOpen, query, focusedIndex, rootEl, popupStyle, resolvedPlacement, stopTracking, completeTimer, normalizeOption, normalizedOptions, filteredOptions, activeId, close, open, requestComplete, onInput, onFocus, onKeydown, selectOption, clearValue, openAll, onBeforeUnmount, trackFloatingOverlay, $slots, emit }
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
// Injected render function
|
|
41
|
+
_sfc_main.render = function(ctx) {
|
|
42
|
+
const { instanceId, listboxId, inputId, isOpen, query, focusedIndex, rootEl, popupStyle, resolvedPlacement, stopTracking, completeTimer, normalizeOption, normalizedOptions, filteredOptions, activeId, close, open, requestComplete, onInput, onFocus, onKeydown, selectOption, clearValue, openAll, onBeforeUnmount, trackFloatingOverlay, $slots, emit, modelValue, options, placeholder, label, disabled, readonly, clearable, minLength, delay, loading, dropdown, emptyMessage, placement, Fragment: _ntc_Fragment, Teleport: _ntc_Teleport } = ctx
|
|
43
|
+
return h('div', { class: "n-ac", "data-autocomplete-root": instanceId, "data-v-741257a1": "" }, [
|
|
44
|
+
"\n ",
|
|
45
|
+
(label) ? h('label', { class: "n-ac-label", for: inputId, "data-v-741257a1": "" }, [
|
|
46
|
+
label
|
|
47
|
+
]) : null,
|
|
48
|
+
h('div', { class: ["n-ac-input-wrap", { 'is-disabled': disabled }], "data-v-741257a1": "" }, [
|
|
49
|
+
"\n ",
|
|
50
|
+
h('input', { class: "n-ac-input", id: inputId, value: modelValue, placeholder: placeholder, disabled: disabled, readonly: readonly, role: "combobox", "aria-autocomplete": "list", "aria-expanded": isOpen.value, "aria-controls": listboxId, "aria-activedescendant": activeId.value || undefined, onInput: onInput, onFocus: onFocus, onKeydown: onKeydown, "data-v-741257a1": "" }),
|
|
51
|
+
"\n ",
|
|
52
|
+
h('div', { class: "n-ac-actions", "data-v-741257a1": "" }, [
|
|
53
|
+
"\n ",
|
|
54
|
+
(clearable && modelValue) ? h('button', { class: "n-ac-action", type: "button", disabled: disabled || readonly, "aria-label": "Limpiar", onClick: clearValue, "data-v-741257a1": "" }, [
|
|
55
|
+
"✕"
|
|
56
|
+
]) : null,
|
|
57
|
+
(dropdown) ? h('button', { class: "n-ac-action", type: "button", disabled: disabled || readonly, "aria-label": "Abrir", onClick: openAll, "data-v-741257a1": "" }, [
|
|
58
|
+
"▾"
|
|
59
|
+
]) : null
|
|
60
|
+
]),
|
|
61
|
+
"\n "
|
|
62
|
+
]),
|
|
63
|
+
"\n ",
|
|
64
|
+
h(_ntc_Teleport, { to: "body", "data-v-741257a1": "" }, [
|
|
65
|
+
"\n ",
|
|
66
|
+
(isOpen.value) ? h('div', { class: ["n-ac-popup", { 'is-top': resolvedPlacement.value === 'top' }], "data-autocomplete-popup": instanceId, style: popupStyle.value, "data-v-741257a1": "" }, [
|
|
67
|
+
"\n ",
|
|
68
|
+
(loading) ? h('div', { class: "n-ac-loading", "data-v-741257a1": "" }, [
|
|
69
|
+
"Loading..."
|
|
70
|
+
]) : (true) ? h('div', { class: "n-ac-list", role: "listbox", id: listboxId, "data-v-741257a1": "" }, [
|
|
71
|
+
"\n ",
|
|
72
|
+
filteredOptions.value.map((opt, i) =>
|
|
73
|
+
h('button', { class: ["n-ac-option", { 'is-focused': i === focusedIndex.value }], key: opt.value, type: "button", role: "option", id: instanceId + '-opt-' + i, "aria-selected": (i === focusedIndex.value).toString(), onMouseenter: ($event) => { focusedIndex.value = i }, onClick: ($event) => { selectOption(opt) }, "data-v-741257a1": "" }, [
|
|
74
|
+
opt.label
|
|
75
|
+
])
|
|
76
|
+
),
|
|
77
|
+
"\n ",
|
|
78
|
+
(filteredOptions.value.length === 0) ? h('div', { class: "n-ac-empty", "data-v-741257a1": "" }, [
|
|
79
|
+
emptyMessage
|
|
80
|
+
]) : null
|
|
81
|
+
]) : null,
|
|
82
|
+
"\n "
|
|
83
|
+
]) : null
|
|
84
|
+
]),
|
|
85
|
+
"\n "
|
|
86
|
+
])
|
|
87
|
+
}
|
|
88
|
+
_sfc_main.__scopeId = 'data-v-741257a1'
|
|
89
|
+
_sfc_main.__hmrId = 'NAutocomplete_nexa'
|
|
90
|
+
|
|
91
|
+
export default _sfc_main
|
|
92
|
+
|
|
93
|
+
const __style = `.n-ac[data-v-741257a1]{display:flex;flex-direction:column;gap:var(--n-space-2);width:100%;font-family:var(--n-font-sans)}.n-ac-label{display:block;font-size:var(--n-text-sm);font-weight:var(--n-weight-medium);color:var(--n-color-text-secondary);margin-bottom:var(--n-space-2)}.n-ac-input-wrap{position:relative;display:flex;align-items:center;background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);transition:all var(--n-transition-fast)}.n-ac-input-wrap:focus-within{border-color:var(--n-color-primary);box-shadow:0 0 0 3px var(--n-color-primary-light)}.n-ac-input{width:100%;background:transparent;border:none;outline:none;padding:0.75rem 2.75rem 0.75rem 1rem;color:var(--n-color-text);font-size:var(--n-text-base);font-family:inherit}.n-ac-input::placeholder{color:var(--n-color-text-muted)}.n-ac-actions{position:absolute;right:0.5rem;display:flex;align-items:center;gap:0.15rem}.n-ac-action{background:transparent;border:none;color:var(--n-color-text-muted);cursor:pointer;padding:0.25rem;border-radius:var(--n-radius-sm);transition:all var(--n-transition-fast);line-height:1;display:flex;align-items:center}.n-ac-action:hover:not(:disabled){color:var(--n-color-text);background:var(--n-color-glass)}.n-ac-action:disabled{opacity:0.5;cursor:not-allowed}.n-ac-input-wrap.is-disabled{opacity:0.5;cursor:not-allowed;background:var(--n-color-surface-alt)}.n-ac-popup{background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);box-shadow:var(--n-shadow-lg);overflow:hidden;animation:n-ac-in .2s cubic-bezier(0,1,0,1)}.n-ac-popup.is-top{animation:n-ac-in-top .2s cubic-bezier(0,1,0,1)}@keyframes n-ac-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes n-ac-in-top{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.n-ac-loading{padding:var(--n-space-3) var(--n-space-4);color:var(--n-color-text-muted);font-size:var(--n-text-sm)}.n-ac-list{max-height:260px;overflow:auto;display:flex;flex-direction:column}.n-ac-option{text-align:left;padding:0.7rem 1rem;color:var(--n-color-text-secondary);background:transparent;border:none;cursor:pointer;transition:all var(--n-transition-fast)}.n-ac-option:hover,.n-ac-option.is-focused{background:var(--n-color-glass);color:var(--n-color-text)}.n-ac-empty{padding:var(--n-space-4);color:var(--n-color-text-muted);text-align:center;font-size:var(--n-text-sm)}`
|
|
94
|
+
injectStyle('data-v-741257a1', __style)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { signal, computed, effect, onBeforeUnmount } from 'nexa-framework'
|
|
3
|
+
import { trackFloatingOverlay } from '../services/FloatingOverlay.js'
|
|
4
|
+
const props = defineProps({ modelValue: { type: String, default: '' }, options: { type: Array, default: () => [] }, placeholder: { type: String, default: '' }, label: { type: String, default: '' }, disabled: { type: Boolean, default: false }, readonly: { type: Boolean, default: false }, clearable: { type: Boolean, default: false }, minLength: { type: Number, default: 1 }, delay: { type: Number, default: 200 }, loading: { type: Boolean, default: false }, dropdown: { type: Boolean, default: false }, emptyMessage: { type: String, default: 'No results' }, placement: { type: String, default: 'auto' } })
|
|
5
|
+
const emit = defineEmits(['update:modelValue', 'select', 'complete', 'clear'])
|
|
6
|
+
const instanceId = `n-ac-${Math.random().toString(16).slice(2)}`
|
|
7
|
+
const listboxId = `${instanceId}-listbox`
|
|
8
|
+
const inputId = `${instanceId}-input`
|
|
9
|
+
const isOpen = signal(false)
|
|
10
|
+
const query = signal('')
|
|
11
|
+
const focusedIndex = signal(-1)
|
|
12
|
+
const rootEl = signal(null)
|
|
13
|
+
const popupStyle = signal({})
|
|
14
|
+
const resolvedPlacement = signal('bottom')
|
|
15
|
+
let stopTracking = null
|
|
16
|
+
let completeTimer = null
|
|
17
|
+
const normalizeOption = (opt) => { if (opt == null) return { label: '', value: null, raw: opt }; if (typeof opt === 'string' || typeof opt === 'number') return { label: String(opt), value: opt, raw: opt }; if (typeof opt === 'object') { const label = 'label' in opt ? String(opt.label) : String(opt.value ?? ''); return { label, value: 'value' in opt ? opt.value : label, raw: opt } } return { label: String(opt), value: opt, raw: opt } }
|
|
18
|
+
const normalizedOptions = computed(() => props.options.map(normalizeOption))
|
|
19
|
+
const filteredOptions = computed(() => { const q = query.value.trim().toLowerCase(); if (!q) return normalizedOptions.value; return normalizedOptions.value.filter(opt => opt.label.toLowerCase().includes(q)) })
|
|
20
|
+
const activeId = computed(() => { const idx = focusedIndex.value; if (idx < 0) return ''; const opt = filteredOptions.value[idx]; return opt ? `${instanceId}-opt-${idx}` : '' })
|
|
21
|
+
const close = () => { if (!isOpen.value) return; isOpen.value = false; focusedIndex.value = -1; if (stopTracking) { stopTracking(); stopTracking = null } }
|
|
22
|
+
const open = (e) => { if (props.disabled || props.readonly || isOpen.value) return; isOpen.value = true; const t = e?.currentTarget || e?.target; rootEl.value = t?.closest ? t.closest(`[data-autocomplete-root="${instanceId}"]`) : null; stopTracking = trackFloatingOverlay({ isOpen: () => isOpen.value, getAnchor: () => { const root = rootEl.value; return root ? root.querySelector('.n-ac-input-wrap') : null }, getPopup: () => document.querySelector(`[data-autocomplete-popup="${instanceId}"]`), placement: props.placement, align: 'start', matchWidth: true, minWidth: 240, gap: 8, margin: 8, zIndex: 9999, onUpdate: (r) => { popupStyle.value = r.style; resolvedPlacement.value = r.placement }, isEventInside: (ev) => { const el = ev.target; if (!el || typeof el.closest !== 'function') return false; return !!(el.closest(`[data-autocomplete-root="${instanceId}"]`) || el.closest(`[data-autocomplete-popup="${instanceId}"]`)) }, onOutside: () => close() }) }
|
|
23
|
+
const requestComplete = () => { clearTimeout(completeTimer); completeTimer = setTimeout(() => emit('complete', query.value), props.delay) }
|
|
24
|
+
const onInput = (e) => { const value = e.target.value; query.value = value; emit('update:modelValue', value); if (value.trim().length >= props.minLength) { open(e); requestComplete() } else close() }
|
|
25
|
+
const onFocus = (e) => { query.value = props.modelValue || ''; if ((props.modelValue || '').trim().length >= props.minLength) { open(e); requestComplete() } }
|
|
26
|
+
const onKeydown = (e) => { if (e.key === 'Escape') { e.preventDefault(); close(); return } if (!isOpen.value) { if (e.key === 'ArrowDown') { e.preventDefault(); open(e); focusedIndex.value = 0 } return } const items = filteredOptions.value; if (e.key === 'ArrowDown') { e.preventDefault(); focusedIndex.value = Math.min(focusedIndex.value + 1, items.length - 1); return } if (e.key === 'ArrowUp') { e.preventDefault(); focusedIndex.value = Math.max(focusedIndex.value - 1, 0); return } if (e.key === 'Enter') { e.preventDefault(); const opt = items[focusedIndex.value]; if (!opt) return; emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() } }
|
|
27
|
+
const selectOption = (opt) => { emit('update:modelValue', opt.label); emit('select', opt.raw); query.value = opt.label; close() }
|
|
28
|
+
const clearValue = () => { if (props.disabled || props.readonly) return; emit('update:modelValue', ''); emit('clear'); query.value = ''; close() }
|
|
29
|
+
const openAll = (e) => { if (props.disabled || props.readonly) return; query.value = props.modelValue || ''; open(e); requestComplete() }
|
|
30
|
+
effect(() => { if (!isOpen.value) return; if (focusedIndex.value < 0 && filteredOptions.value.length > 0) focusedIndex.value = 0 })
|
|
31
|
+
onBeforeUnmount(() => { clearTimeout(completeTimer); close() })
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="n-ac" :data-autocomplete-root="instanceId">
|
|
36
|
+
<label v-if="label" class="n-ac-label" :for="inputId">{{ label }}</label>
|
|
37
|
+
<div class="n-ac-input-wrap" :class="{ 'is-disabled': disabled }">
|
|
38
|
+
<input class="n-ac-input" :id="inputId" :value="modelValue" :placeholder="placeholder" :disabled="disabled" :readonly="readonly" role="combobox" aria-autocomplete="list" :aria-expanded="isOpen.value" :aria-controls="listboxId" :aria-activedescendant="activeId.value || undefined" @input="onInput" @focus="onFocus" @keydown="onKeydown" />
|
|
39
|
+
<div class="n-ac-actions">
|
|
40
|
+
<button v-if="clearable && modelValue" type="button" class="n-ac-action" :disabled="disabled || readonly" aria-label="Limpiar" @click="clearValue">✕</button>
|
|
41
|
+
<button v-if="dropdown" type="button" class="n-ac-action" :disabled="disabled || readonly" aria-label="Abrir" @click="openAll">▾</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<Teleport to="body">
|
|
45
|
+
<div v-if="isOpen.value" class="n-ac-popup" :class="{ 'is-top': resolvedPlacement.value === 'top' }" :data-autocomplete-popup="instanceId" :style="popupStyle.value">
|
|
46
|
+
<div v-if="loading" class="n-ac-loading">Loading...</div>
|
|
47
|
+
<div v-else class="n-ac-list" role="listbox" :id="listboxId">
|
|
48
|
+
<button v-for="(opt, i) in filteredOptions.value" :key="opt.value" type="button" class="n-ac-option" role="option" :id="instanceId + '-opt-' + i" :aria-selected="(i === focusedIndex.value).toString()" :class="{ 'is-focused': i === focusedIndex.value }" @mouseenter="focusedIndex.value = i" @click="selectOption(opt)">{{ opt.label }}</button>
|
|
49
|
+
<div v-if="filteredOptions.value.length === 0" class="n-ac-empty">{{ emptyMessage }}</div>
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</Teleport>
|
|
53
|
+
</div>
|
|
54
|
+
</template>
|
|
55
|
+
|
|
56
|
+
<style scoped>
|
|
57
|
+
.n-ac{display:flex;flex-direction:column;gap:var(--n-space-2);width:100%;font-family:var(--n-font-sans)}.n-ac-label{display:block;font-size:var(--n-text-sm);font-weight:var(--n-weight-medium);color:var(--n-color-text-secondary);margin-bottom:var(--n-space-2)}.n-ac-input-wrap{position:relative;display:flex;align-items:center;background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);transition:all var(--n-transition-fast)}.n-ac-input-wrap:focus-within{border-color:var(--n-color-primary);box-shadow:0 0 0 3px var(--n-color-primary-light)}.n-ac-input{width:100%;background:transparent;border:none;outline:none;padding:0.75rem 2.75rem 0.75rem 1rem;color:var(--n-color-text);font-size:var(--n-text-base);font-family:inherit}.n-ac-input::placeholder{color:var(--n-color-text-muted)}.n-ac-actions{position:absolute;right:0.5rem;display:flex;align-items:center;gap:0.15rem}.n-ac-action{background:transparent;border:none;color:var(--n-color-text-muted);cursor:pointer;padding:0.25rem;border-radius:var(--n-radius-sm);transition:all var(--n-transition-fast);line-height:1;display:flex;align-items:center}.n-ac-action:hover:not(:disabled){color:var(--n-color-text);background:var(--n-color-glass)}.n-ac-action:disabled{opacity:0.5;cursor:not-allowed}.n-ac-input-wrap.is-disabled{opacity:0.5;cursor:not-allowed;background:var(--n-color-surface-alt)}.n-ac-popup{background:var(--n-color-surface);border:1px solid var(--n-color-border);border-radius:var(--n-radius-md);box-shadow:var(--n-shadow-lg);overflow:hidden;animation:n-ac-in .2s cubic-bezier(0,1,0,1)}.n-ac-popup.is-top{animation:n-ac-in-top .2s cubic-bezier(0,1,0,1)}@keyframes n-ac-in{from{opacity:0;transform:translateY(-8px)}to{opacity:1;transform:translateY(0)}}@keyframes n-ac-in-top{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.n-ac-loading{padding:var(--n-space-3) var(--n-space-4);color:var(--n-color-text-muted);font-size:var(--n-text-sm)}.n-ac-list{max-height:260px;overflow:auto;display:flex;flex-direction:column}.n-ac-option{text-align:left;padding:0.7rem 1rem;color:var(--n-color-text-secondary);background:transparent;border:none;cursor:pointer;transition:all var(--n-transition-fast)}.n-ac-option:hover,.n-ac-option.is-focused{background:var(--n-color-glass);color:var(--n-color-text)}.n-ac-empty{padding:var(--n-space-4);color:var(--n-color-text-muted);text-align:center;font-size:var(--n-text-sm)}
|
|
58
|
+
</style>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { computed, h, hText, effect, defineComponent, registerComponent, reloadComponent, injectStyle } from 'nexa-framework'
|
|
2
|
+
|
|
3
|
+
const _sfc_main = defineComponent({
|
|
4
|
+
__scopeId: 'data-v-147134',
|
|
5
|
+
__hmrId: 'NAvatar_nexa',
|
|
6
|
+
props: {
|
|
7
|
+
label: { type: String, default: '' },
|
|
8
|
+
size: { type: String, default: 'md' },
|
|
9
|
+
variant: { type: String, default: 'primary' },
|
|
10
|
+
image: { type: String, default: '' },
|
|
11
|
+
shape: { type: String, default: 'rounded' } // rounded, circle, square
|
|
12
|
+
},
|
|
13
|
+
setup(props, setupContext) {
|
|
14
|
+
const { emit, slots, slots: $slots } = setupContext
|
|
15
|
+
const initials = computed(() => {
|
|
16
|
+
if (!props.label) return '?'
|
|
17
|
+
return props.label
|
|
18
|
+
.split(' ')
|
|
19
|
+
.map(w => w[0])
|
|
20
|
+
.join('')
|
|
21
|
+
.toUpperCase()
|
|
22
|
+
.slice(0, 2)
|
|
23
|
+
})
|
|
24
|
+
return { initials, $slots }
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
// Injected render function
|
|
28
|
+
_sfc_main.render = function(ctx) {
|
|
29
|
+
const { initials, $slots, label, size, variant, image, shape, Fragment: _ntc_Fragment } = ctx
|
|
30
|
+
return h('div', { class: ['n-avatar', `is-${size}`, `is-${variant}`, `is-${shape}`], vBind: true, style: image ? { backgroundImage: `url(${image})` } : {}, "aria-label": label, "data-v-147134": "" }, [
|
|
31
|
+
"\n ",
|
|
32
|
+
(!image) ? h('span', { class: "n-avatar-initials", "data-v-147134": "" }, [
|
|
33
|
+
initials.value
|
|
34
|
+
]) : null
|
|
35
|
+
])
|
|
36
|
+
}
|
|
37
|
+
_sfc_main.__scopeId = 'data-v-147134'
|
|
38
|
+
_sfc_main.__hmrId = 'NAvatar_nexa'
|
|
39
|
+
|
|
40
|
+
export default _sfc_main
|
|
41
|
+
|
|
42
|
+
const __style = `.n-avatar[data-v-147134]{
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
background-size: cover;
|
|
48
|
+
background-position: center;
|
|
49
|
+
background-color: var(--n-color-surface-hover);
|
|
50
|
+
font-weight: var(--n-weight-bold);
|
|
51
|
+
color: var(--n-color-text);
|
|
52
|
+
user-select: none;
|
|
53
|
+
transition: all var(--n-transition-fast);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.is-sm[data-v-147134]{ width: 32px; height: 32px; font-size: var(--n-text-xs); }
|
|
57
|
+
.is-md[data-v-147134]{ width: 40px; height: 40px; font-size: var(--n-text-sm); }
|
|
58
|
+
.is-lg[data-v-147134]{ width: 56px; height: 56px; font-size: var(--n-text-lg); }
|
|
59
|
+
.is-xl[data-v-147134]{ width: 72px; height: 72px; font-size: var(--n-text-2xl); }
|
|
60
|
+
|
|
61
|
+
.is-circle[data-v-147134]{ border-radius: var(--n-radius-full); }
|
|
62
|
+
.is-rounded[data-v-147134]{ border-radius: var(--n-radius-md); }
|
|
63
|
+
.is-square[data-v-147134]{ border-radius: 0; }
|
|
64
|
+
|
|
65
|
+
.is-primary[data-v-147134]{ background-color: var(--n-color-primary); color: white; }
|
|
66
|
+
.is-success[data-v-147134]{ background-color: var(--n-color-success); color: white; }
|
|
67
|
+
.is-danger[data-v-147134]{ background-color: var(--n-color-danger); color: white; }
|
|
68
|
+
.is-warning[data-v-147134]{ background-color: var(--n-color-warning); color: white; }
|
|
69
|
+
.is-info[data-v-147134]{ background-color: var(--n-color-info); color: white; }
|
|
70
|
+
|
|
71
|
+
.n-avatar-initials[data-v-147134]{
|
|
72
|
+
line-height: 1;
|
|
73
|
+
font-weight: var(--n-weight-bold);
|
|
74
|
+
}`
|
|
75
|
+
injectStyle('data-v-147134', __style)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'nexa-framework'
|
|
3
|
+
|
|
4
|
+
const props = defineProps({
|
|
5
|
+
label: { type: String, default: '' },
|
|
6
|
+
size: { type: String, default: 'md' },
|
|
7
|
+
variant: { type: String, default: 'primary' },
|
|
8
|
+
image: { type: String, default: '' },
|
|
9
|
+
shape: { type: String, default: 'rounded' } // rounded, circle, square
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
const initials = computed(() => {
|
|
13
|
+
if (!props.label) return '?'
|
|
14
|
+
return props.label
|
|
15
|
+
.split(' ')
|
|
16
|
+
.map(w => w[0])
|
|
17
|
+
.join('')
|
|
18
|
+
.toUpperCase()
|
|
19
|
+
.slice(0, 2)
|
|
20
|
+
})
|
|
21
|
+
</script>
|
|
22
|
+
|
|
23
|
+
<template>
|
|
24
|
+
<div
|
|
25
|
+
:class="['n-avatar', `is-${size}`, `is-${variant}`, `is-${shape}`]"
|
|
26
|
+
v-bind:style="image ? { backgroundImage: `url(${image})` } : {}"
|
|
27
|
+
:aria-label="label"
|
|
28
|
+
>
|
|
29
|
+
<span v-if="!image" class="n-avatar-initials">{{ initials.value }}</span>
|
|
30
|
+
</div>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style scoped>
|
|
34
|
+
.n-avatar {
|
|
35
|
+
display: inline-flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
flex-shrink: 0;
|
|
39
|
+
background-size: cover;
|
|
40
|
+
background-position: center;
|
|
41
|
+
background-color: var(--n-color-surface-hover);
|
|
42
|
+
font-weight: var(--n-weight-bold);
|
|
43
|
+
color: var(--n-color-text);
|
|
44
|
+
user-select: none;
|
|
45
|
+
transition: all var(--n-transition-fast);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.is-sm { width: 32px; height: 32px; font-size: var(--n-text-xs); }
|
|
49
|
+
.is-md { width: 40px; height: 40px; font-size: var(--n-text-sm); }
|
|
50
|
+
.is-lg { width: 56px; height: 56px; font-size: var(--n-text-lg); }
|
|
51
|
+
.is-xl { width: 72px; height: 72px; font-size: var(--n-text-2xl); }
|
|
52
|
+
|
|
53
|
+
.is-circle { border-radius: var(--n-radius-full); }
|
|
54
|
+
.is-rounded { border-radius: var(--n-radius-md); }
|
|
55
|
+
.is-square { border-radius: 0; }
|
|
56
|
+
|
|
57
|
+
.is-primary { background-color: var(--n-color-primary); color: white; }
|
|
58
|
+
.is-success { background-color: var(--n-color-success); color: white; }
|
|
59
|
+
.is-danger { background-color: var(--n-color-danger); color: white; }
|
|
60
|
+
.is-warning { background-color: var(--n-color-warning); color: white; }
|
|
61
|
+
.is-info { background-color: var(--n-color-info); color: white; }
|
|
62
|
+
|
|
63
|
+
.n-avatar-initials {
|
|
64
|
+
line-height: 1;
|
|
65
|
+
font-weight: var(--n-weight-bold);
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { h, hText, effect, defineComponent, registerComponent, reloadComponent, injectStyle } from 'nexa-framework'
|
|
2
|
+
|
|
3
|
+
const _sfc_main = defineComponent({
|
|
4
|
+
__scopeId: 'data-v-ff3cf3e',
|
|
5
|
+
__hmrId: 'NBadge_nexa',
|
|
6
|
+
props: {
|
|
7
|
+
variant: { type: String, default: 'primary' },
|
|
8
|
+
size: { type: String, default: 'md' },
|
|
9
|
+
rounded: { type: Boolean, default: false },
|
|
10
|
+
dot: { type: Boolean, default: false },
|
|
11
|
+
position: { type: String, default: '' }
|
|
12
|
+
},
|
|
13
|
+
setup(props, setupContext) {
|
|
14
|
+
const { emit, slots, slots: $slots } = setupContext
|
|
15
|
+
return { $slots }
|
|
16
|
+
}
|
|
17
|
+
})
|
|
18
|
+
// Injected render function
|
|
19
|
+
_sfc_main.render = function(ctx) {
|
|
20
|
+
const { $slots, variant, size, rounded, dot, position, Fragment: _ntc_Fragment } = ctx
|
|
21
|
+
return (dot) ? h('span', { class: ["n-badge-dot", [`is-${variant}`, position ? `is-${position}` : '']], "data-v-ff3cf3e": "" }) : (true) ? h('span', { class: ['n-badge', `n-badge-${variant}`, `n-badge-${size}`, rounded ? 'is-rounded' : ''], "data-v-ff3cf3e": "" }, [
|
|
22
|
+
"\n ",
|
|
23
|
+
ctx.$slots.default ? ctx.$slots.default() : null,
|
|
24
|
+
"\n "
|
|
25
|
+
]) : null
|
|
26
|
+
}
|
|
27
|
+
_sfc_main.__scopeId = 'data-v-ff3cf3e'
|
|
28
|
+
_sfc_main.__hmrId = 'NBadge_nexa'
|
|
29
|
+
|
|
30
|
+
export default _sfc_main
|
|
31
|
+
|
|
32
|
+
const __style = `.n-badge[data-v-ff3cf3e]{
|
|
33
|
+
display: inline-flex;
|
|
34
|
+
align-items: center;
|
|
35
|
+
justify-content: center;
|
|
36
|
+
padding: 0.25rem 0.6rem;
|
|
37
|
+
font-size: var(--n-text-xs);
|
|
38
|
+
font-weight: var(--n-weight-bold);
|
|
39
|
+
border-radius: var(--n-radius-sm);
|
|
40
|
+
line-height: 1;
|
|
41
|
+
font-family: var(--n-font-sans);
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
letter-spacing: var(--n-tracking-wide);
|
|
44
|
+
transition: all var(--n-transition-fast);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.n-badge-primary[data-v-ff3cf3e]{ background: var(--n-color-primary); color: white; }
|
|
48
|
+
.n-badge-success[data-v-ff3cf3e]{ background: var(--n-color-success); color: white; }
|
|
49
|
+
.n-badge-danger[data-v-ff3cf3e]{ background: var(--n-color-danger); color: white; }
|
|
50
|
+
.n-badge-warning[data-v-ff3cf3e]{ background: var(--n-color-warning); color: white; }
|
|
51
|
+
.n-badge-info[data-v-ff3cf3e]{ background: var(--n-color-info); color: white; }
|
|
52
|
+
.n-badge-secondary[data-v-ff3cf3e]{ background: var(--n-color-surface-hover); color: var(--n-color-text-secondary); }
|
|
53
|
+
|
|
54
|
+
.n-badge-sm[data-v-ff3cf3e]{ padding: 0.15rem 0.4rem; font-size: 0.65rem; }
|
|
55
|
+
.n-badge-lg[data-v-ff3cf3e]{ padding: 0.4rem 0.8rem; font-size: var(--n-text-sm); }
|
|
56
|
+
|
|
57
|
+
.is-rounded[data-v-ff3cf3e]{ border-radius: var(--n-radius-full); }
|
|
58
|
+
|
|
59
|
+
.n-badge-dot[data-v-ff3cf3e]{
|
|
60
|
+
display: inline-block;
|
|
61
|
+
width: 8px;
|
|
62
|
+
height: 8px;
|
|
63
|
+
border-radius: var(--n-radius-full);
|
|
64
|
+
vertical-align: middle;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.n-badge-dot.is-primary[data-v-ff3cf3e]{ background: var(--n-color-primary); }
|
|
69
|
+
.n-badge-dot.is-success[data-v-ff3cf3e]{ background: var(--n-color-success); }
|
|
70
|
+
.n-badge-dot.is-danger[data-v-ff3cf3e]{ background: var(--n-color-danger); }
|
|
71
|
+
.n-badge-dot.is-warning[data-v-ff3cf3e]{ background: var(--n-color-warning); }
|
|
72
|
+
.n-badge-dot.is-info[data-v-ff3cf3e]{ background: var(--n-color-info); }
|
|
73
|
+
.n-badge-dot.is-secondary[data-v-ff3cf3e]{ background: var(--n-color-text-muted); }`
|
|
74
|
+
injectStyle('data-v-ff3cf3e', __style)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
const props = defineProps({
|
|
3
|
+
variant: { type: String, default: 'primary' },
|
|
4
|
+
size: { type: String, default: 'md' },
|
|
5
|
+
rounded: { type: Boolean, default: false },
|
|
6
|
+
dot: { type: Boolean, default: false },
|
|
7
|
+
position: { type: String, default: '' }
|
|
8
|
+
})
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
<template>
|
|
12
|
+
<span v-if="dot" class="n-badge-dot" :class="[`is-${variant}`, position ? `is-${position}` : '']"></span>
|
|
13
|
+
<span v-else :class="['n-badge', `n-badge-${variant}`, `n-badge-${size}`, rounded ? 'is-rounded' : '']">
|
|
14
|
+
<slot />
|
|
15
|
+
</span>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<style scoped>
|
|
19
|
+
.n-badge {
|
|
20
|
+
display: inline-flex;
|
|
21
|
+
align-items: center;
|
|
22
|
+
justify-content: center;
|
|
23
|
+
padding: 0.25rem 0.6rem;
|
|
24
|
+
font-size: var(--n-text-xs);
|
|
25
|
+
font-weight: var(--n-weight-bold);
|
|
26
|
+
border-radius: var(--n-radius-sm);
|
|
27
|
+
line-height: 1;
|
|
28
|
+
font-family: var(--n-font-sans);
|
|
29
|
+
text-transform: uppercase;
|
|
30
|
+
letter-spacing: var(--n-tracking-wide);
|
|
31
|
+
transition: all var(--n-transition-fast);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.n-badge-primary { background: var(--n-color-primary); color: white; }
|
|
35
|
+
.n-badge-success { background: var(--n-color-success); color: white; }
|
|
36
|
+
.n-badge-danger { background: var(--n-color-danger); color: white; }
|
|
37
|
+
.n-badge-warning { background: var(--n-color-warning); color: white; }
|
|
38
|
+
.n-badge-info { background: var(--n-color-info); color: white; }
|
|
39
|
+
.n-badge-secondary { background: var(--n-color-surface-hover); color: var(--n-color-text-secondary); }
|
|
40
|
+
|
|
41
|
+
.n-badge-sm { padding: 0.15rem 0.4rem; font-size: 0.65rem; }
|
|
42
|
+
.n-badge-lg { padding: 0.4rem 0.8rem; font-size: var(--n-text-sm); }
|
|
43
|
+
|
|
44
|
+
.is-rounded { border-radius: var(--n-radius-full); }
|
|
45
|
+
|
|
46
|
+
.n-badge-dot {
|
|
47
|
+
display: inline-block;
|
|
48
|
+
width: 8px;
|
|
49
|
+
height: 8px;
|
|
50
|
+
border-radius: var(--n-radius-full);
|
|
51
|
+
vertical-align: middle;
|
|
52
|
+
flex-shrink: 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.n-badge-dot.is-primary { background: var(--n-color-primary); }
|
|
56
|
+
.n-badge-dot.is-success { background: var(--n-color-success); }
|
|
57
|
+
.n-badge-dot.is-danger { background: var(--n-color-danger); }
|
|
58
|
+
.n-badge-dot.is-warning { background: var(--n-color-warning); }
|
|
59
|
+
.n-badge-dot.is-info { background: var(--n-color-info); }
|
|
60
|
+
.n-badge-dot.is-secondary { background: var(--n-color-text-muted); }
|
|
61
|
+
</style>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { signal, computed, effect, onMounted, onUnmounted, h, hText, defineComponent, registerComponent, reloadComponent, injectStyle, Teleport } from 'nexa-framework'
|
|
2
|
+
import { useGestures } from 'nexa-mobile'
|
|
3
|
+
|
|
4
|
+
const _sfc_main = defineComponent({
|
|
5
|
+
__scopeId: 'data-v-55f61938',
|
|
6
|
+
__hmrId: 'NBottomSheet_nexa',
|
|
7
|
+
props: {
|
|
8
|
+
show: { type: Boolean, default: false },
|
|
9
|
+
snapPoints: { type: Array, default: () => [50, 80] }
|
|
10
|
+
},
|
|
11
|
+
emits: ['close'],
|
|
12
|
+
setup(props, setupContext) {
|
|
13
|
+
const { emit, slots, slots: $slots } = setupContext
|
|
14
|
+
const yOffset = signal(0)
|
|
15
|
+
const isDragging = signal(false)
|
|
16
|
+
const currentSnap = signal(0)
|
|
17
|
+
let gestures = null
|
|
18
|
+
const sortedPoints = computed(() =>
|
|
19
|
+
[...props.snapPoints].sort((a, b) => a - b)
|
|
20
|
+
)
|
|
21
|
+
onMounted(() => {
|
|
22
|
+
const el = document.getElementById('n-bottom-sheet-content')
|
|
23
|
+
if (el) {
|
|
24
|
+
gestures = useGestures(el)
|
|
25
|
+
effect(() => {
|
|
26
|
+
if (gestures.isSwiping.value && gestures.swipeDirection.value === 'down') {
|
|
27
|
+
isDragging.value = true
|
|
28
|
+
yOffset.value = Math.max(0, gestures.deltaY.value)
|
|
29
|
+
} else if (!gestures.isPressed.value) {
|
|
30
|
+
if (isDragging.value) {
|
|
31
|
+
isDragging.value = false
|
|
32
|
+
const maxSnap = sortedPoints.value[sortedPoints.value.length - 1]
|
|
33
|
+
const threshold = window.innerHeight * (maxSnap / 100) * 0.3
|
|
34
|
+
if (yOffset.value > threshold) {
|
|
35
|
+
emit('close')
|
|
36
|
+
yOffset.value = 0
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
yOffset.value = 0
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
onUnmounted(() => {
|
|
46
|
+
gestures?.destroy()
|
|
47
|
+
})
|
|
48
|
+
const translateY = computed(() => {
|
|
49
|
+
if (!props.show) return '100%'
|
|
50
|
+
const base = window.innerHeight * (1 - sortedPoints.value[currentSnap.value] / 100)
|
|
51
|
+
return `${base + yOffset.value}px`
|
|
52
|
+
})
|
|
53
|
+
const overlayOpacity = computed(() => {
|
|
54
|
+
if (!props.show) return 0
|
|
55
|
+
const progress = 1 - (yOffset.value / (window.innerHeight * 0.5))
|
|
56
|
+
return Math.max(0, Math.min(1, progress))
|
|
57
|
+
})
|
|
58
|
+
return { yOffset, isDragging, currentSnap, gestures, sortedPoints, translateY, overlayOpacity, useGestures, $slots, emit }
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
// Injected render function
|
|
62
|
+
_sfc_main.render = function(ctx) {
|
|
63
|
+
const { yOffset, isDragging, currentSnap, gestures, sortedPoints, translateY, overlayOpacity, useGestures, $slots, emit, show, snapPoints, Fragment: _ntc_Fragment, Teleport: _ntc_Teleport } = ctx
|
|
64
|
+
return h(_ntc_Teleport, { to: "body", "data-v-55f61938": "" }, [
|
|
65
|
+
"\n ",
|
|
66
|
+
h('div', { class: ["n-bottom-sheet-root", { 'is-active': show || isDragging.value }], "data-v-55f61938": "" }, [
|
|
67
|
+
"\n ",
|
|
68
|
+
h('div', { class: "n-bottom-sheet-overlay", style: { opacity: overlayOpacity.value }, onClick: ($event) => { emit('close') }, "data-v-55f61938": "" }),
|
|
69
|
+
"\n ",
|
|
70
|
+
h('div', { class: "n-bottom-sheet-container", id: "n-bottom-sheet-content", style: {
|
|
71
|
+
transform: `translateY(${translateY.value})`,
|
|
72
|
+
transition: isDragging.value ? 'none' : 'transform 0.4s cubic-bezier(0.4, 0, 0.2, 1)'
|
|
73
|
+
}, "data-v-55f61938": "" }, [
|
|
74
|
+
"\n ",
|
|
75
|
+
h('div', { class: "n-bottom-sheet-handle", "data-v-55f61938": "" }),
|
|
76
|
+
"\n ",
|
|
77
|
+
h('div', { class: "n-bottom-sheet-inner", "data-v-55f61938": "" }, [
|
|
78
|
+
"\n ",
|
|
79
|
+
ctx.$slots.default ? ctx.$slots.default() : null,
|
|
80
|
+
"\n "
|
|
81
|
+
]),
|
|
82
|
+
"\n "
|
|
83
|
+
]),
|
|
84
|
+
"\n "
|
|
85
|
+
]),
|
|
86
|
+
"\n "
|
|
87
|
+
])
|
|
88
|
+
}
|
|
89
|
+
_sfc_main.__scopeId = 'data-v-55f61938'
|
|
90
|
+
_sfc_main.__hmrId = 'NBottomSheet_nexa'
|
|
91
|
+
|
|
92
|
+
export default _sfc_main
|
|
93
|
+
|
|
94
|
+
const __style = `.n-bottom-sheet-root[data-v-55f61938]{
|
|
95
|
+
position: fixed;
|
|
96
|
+
top: 0;
|
|
97
|
+
left: 0;
|
|
98
|
+
width: 100vw;
|
|
99
|
+
height: 100vh;
|
|
100
|
+
z-index: var(--n-z-modal);
|
|
101
|
+
pointer-events: none;
|
|
102
|
+
display: flex;
|
|
103
|
+
align-items: flex-end;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.n-bottom-sheet-root.is-active[data-v-55f61938]{
|
|
107
|
+
pointer-events: auto;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.n-bottom-sheet-overlay[data-v-55f61938]{
|
|
111
|
+
position: absolute;
|
|
112
|
+
top: 0;
|
|
113
|
+
left: 0;
|
|
114
|
+
width: 100%;
|
|
115
|
+
height: 100%;
|
|
116
|
+
background: var(--n-color-overlay);
|
|
117
|
+
backdrop-filter: blur(4px);
|
|
118
|
+
transition: opacity 0.3s ease;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.n-bottom-sheet-container[data-v-55f61938]{
|
|
122
|
+
position: relative;
|
|
123
|
+
width: 100%;
|
|
124
|
+
max-height: 90vh;
|
|
125
|
+
background: var(--n-color-surface);
|
|
126
|
+
border-top: 1px solid var(--n-color-border);
|
|
127
|
+
border-radius: var(--n-radius-2xl) var(--n-radius-2xl) 0 0;
|
|
128
|
+
box-shadow: 0 -10px 25px -5px rgba(0, 0, 0, 0.3);
|
|
129
|
+
padding-bottom: env(safe-area-inset-bottom, 20px);
|
|
130
|
+
touch-action: none;
|
|
131
|
+
display: flex;
|
|
132
|
+
flex-direction: column;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.n-bottom-sheet-handle[data-v-55f61938]{
|
|
136
|
+
width: 40px;
|
|
137
|
+
height: 4px;
|
|
138
|
+
background: var(--n-color-surface-hover);
|
|
139
|
+
border-radius: var(--n-radius-full);
|
|
140
|
+
margin: var(--n-space-3) auto;
|
|
141
|
+
flex-shrink: 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.n-bottom-sheet-inner[data-v-55f61938]{
|
|
145
|
+
padding: var(--n-space-4) var(--n-space-8) var(--n-space-8);
|
|
146
|
+
overflow-y: auto;
|
|
147
|
+
flex: 1;
|
|
148
|
+
}`
|
|
149
|
+
injectStyle('data-v-55f61938', __style)
|