Browse Source

feat(component): add SongUI Tag/Button/Input/Card in TypeScript

main
Mohan 1 month ago
parent
commit
eb9e31c5d1
  1. 131
      src/components/SongButton.vue
  2. 55
      src/components/SongCard.vue
  3. 94
      src/components/SongInput.vue
  4. 55
      src/components/SongTag.vue

131
src/components/SongButton.vue

@ -0,0 +1,131 @@
<script setup lang="ts">
withDefaults(
defineProps<{
type?: 'primary' | 'secondary' | 'text' | 'danger'
size?: 'small' | 'medium' | 'large'
disabled?: boolean
block?: boolean
loading?: boolean
}>(),
{ type: 'primary', size: 'medium', disabled: false, block: false, loading: false }
)
defineOptions({ name: 'SongButton' })
</script>
<template>
<button
class="song-btn"
:class="[`song-btn--${type}`, `song-btn--${size}`, { 'song-btn--block': block }]"
:disabled="disabled || loading"
:aria-busy="loading"
>
<i v-if="loading" class="song-btn__ink" aria-hidden="true" />
<slot />
</button>
</template>
<style scoped>
.song-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
font-family: var(--song-font-sans);
border: 1px solid transparent;
border-radius: var(--song-radius-m);
cursor: pointer;
transition: background-color var(--song-dur-1) var(--song-ease),
color var(--song-dur-1) var(--song-ease),
border-color var(--song-dur-1) var(--song-ease);
user-select: none;
}
.song-btn--primary {
background: var(--song-btn-bg);
color: var(--song-btn-text);
}
.song-btn--primary:hover:not(:disabled) {
filter: brightness(1.06);
}
.song-btn--secondary {
background: transparent;
color: var(--ac-primary-ink);
border-color: var(--ac-border-strong);
}
.song-btn--secondary:hover:not(:disabled) {
background: var(--song-primary-bg);
}
.song-btn--text {
background: transparent;
color: var(--ac-primary-ink);
}
.song-btn--text:hover:not(:disabled) {
background: var(--song-primary-bg);
}
.song-btn--danger {
background: transparent;
color: var(--ac-danger-ink);
border-color: var(--ac-border-strong);
}
.song-btn--danger:hover:not(:disabled) {
background: var(--song-danger-bg);
}
.song-btn--small {
height: 28px;
padding: 0 12px;
font-size: 12px;
}
.song-btn--medium {
height: 34px;
padding: 0 18px;
font-size: 14px;
}
.song-btn--large {
height: 42px;
padding: 0 26px;
font-size: 15px;
font-family: var(--song-font-serif);
letter-spacing: 0.06em;
}
.song-btn--block {
width: 100%;
}
.song-btn:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.song-btn__ink {
width: 9px;
height: 9px;
border-radius: 50%;
background: currentColor;
opacity: 0.75;
animation: song-btn-ink-pulse 1.6s var(--song-ease) infinite;
}
@keyframes song-btn-ink-pulse {
0%,
100% {
transform: scale(0.55);
opacity: 0.35;
}
50% {
transform: scale(0.95);
opacity: 0.85;
}
}
</style>

55
src/components/SongCard.vue

@ -0,0 +1,55 @@
<script setup lang="ts">
withDefaults(
defineProps<{
title?: string
hover?: boolean
}>(),
{ title: '', hover: false }
)
defineOptions({ name: 'SongCard' })
</script>
<template>
<div class="song-card" :class="{ 'song-card--hover': hover }">
<div v-if="title || $slots.header" class="song-card__head">
<slot name="header">
<span class="song-card__title">{{ title }}</span>
</slot>
</div>
<div class="song-card__body">
<slot />
</div>
</div>
</template>
<style scoped>
.song-card {
background: var(--song-bg-elevated);
border: 1px solid var(--song-border);
border-radius: var(--song-radius-l);
box-shadow: var(--song-shadow-1);
transition: transform var(--song-dur-1) var(--song-ease),
box-shadow var(--song-dur-1) var(--song-ease);
}
.song-card--hover:hover {
transform: translateY(-2px);
box-shadow: var(--song-shadow-2);
}
.song-card__head {
padding: var(--song-space-4) var(--song-space-5);
border-bottom: 1px solid var(--song-border);
}
.song-card__title {
font-family: var(--song-font-serif);
font-size: 18px;
line-height: 26px;
font-weight: 600;
}
.song-card__body {
padding: var(--song-space-5);
}
</style>

94
src/components/SongInput.vue

@ -0,0 +1,94 @@
<script setup lang="ts">
withDefaults(
defineProps<{
label?: string
modelValue?: string | number
placeholder?: string
type?: string
required?: boolean
rows?: number
textarea?: boolean
maxlength?: number
}>(),
{ label: '', modelValue: '', placeholder: '', type: 'text', required: false, rows: 3, textarea: false, maxlength: undefined }
)
defineEmits<{ 'update:modelValue': [value: string] }>()
defineOptions({ name: 'SongInput' })
</script>
<template>
<label class="song-field">
<span v-if="label" class="song-field__label">
{{ label }}<span v-if="required" class="song-field__req">*</span>
</span>
<textarea
v-if="textarea"
class="song-field__input song-field__input--area"
:value="modelValue"
:placeholder="placeholder"
:rows="rows"
:maxlength="maxlength"
@input="$emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
/>
<input
v-else
class="song-field__input"
:type="type"
:value="modelValue"
:placeholder="placeholder"
:maxlength="maxlength"
@input="$emit('update:modelValue', ($event.target as HTMLInputElement).value)"
/>
</label>
</template>
<style scoped>
.song-field {
display: block;
}
.song-field__label {
display: block;
margin-bottom: 6px;
font-size: 13px;
color: var(--ac-text-secondary);
}
.song-field__req {
color: var(--ac-danger-ink);
margin-left: 2px;
}
.song-field__input {
width: 100%;
height: 34px;
padding: 0 12px;
font-family: var(--song-font-sans);
font-size: 14px;
color: var(--ac-text);
background: var(--song-bg-elevated);
border: 1px solid var(--ac-border-strong);
border-radius: var(--song-radius-m);
transition: border-color var(--song-dur-1) var(--song-ease);
}
.song-field__input--area {
height: auto;
padding: 8px 12px;
resize: vertical;
line-height: 22px;
}
.song-field__input::placeholder {
color: var(--ac-text-disabled);
}
.song-field__input:hover {
border-color: var(--song-border-strong);
}
.song-field__input:focus {
outline: none;
border-color: var(--song-primary);
}
</style>

55
src/components/SongTag.vue

@ -0,0 +1,55 @@
<script setup lang="ts">
withDefaults(
defineProps<{
type?: 'default' | 'primary' | 'success' | 'warning' | 'danger'
closable?: boolean
}>(),
{ type: 'default', closable: false }
)
defineOptions({ name: 'SongTag' })
</script>
<template>
<span class="song-tag" :class="`song-tag--${type}`">
<slot />
</span>
</template>
<style scoped>
.song-tag {
display: inline-flex;
align-items: center;
height: 22px;
padding: 0 10px;
font-size: 12px;
line-height: 20px;
border: 1px solid var(--song-border);
border-radius: var(--song-radius-s);
color: var(--ac-text-secondary);
background: transparent;
}
.song-tag--primary {
color: var(--ac-primary-ink);
border-color: var(--ac-primary);
background: var(--ac-primary-bg);
}
.song-tag--success {
color: var(--ac-success-ink);
border-color: var(--ac-success);
background: var(--ac-success-bg);
}
.song-tag--warning {
color: var(--ac-warning-ink);
border-color: var(--ac-warning);
background: var(--ac-warning-bg);
}
.song-tag--danger {
color: var(--ac-danger-ink);
border-color: var(--ac-danger);
background: var(--ac-danger-bg);
}
</style>
Loading…
Cancel
Save