mirror of
https://github.com/lights0123/n-link.git
synced 2026-08-07 18:23:28 +00:00
feat(ui): add AppButton component
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
1e2a8c5b1e
commit
ebf7435221
35
n-link-core/components/AppButton.spec.ts
Normal file
35
n-link-core/components/AppButton.spec.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { describe, it, expect } from 'vitest';
|
||||
import { mount } from '@vue/test-utils';
|
||||
import AppButton from './AppButton.vue';
|
||||
|
||||
describe('AppButton', () => {
|
||||
it('renders slot content', () => {
|
||||
const wrapper = mount(AppButton, { slots: { default: 'Download' } });
|
||||
expect(wrapper.text()).toBe('Download');
|
||||
});
|
||||
|
||||
it('applies the primary accent style by default', () => {
|
||||
const wrapper = mount(AppButton, { slots: { default: 'Go' } });
|
||||
expect(wrapper.get('button').classes()).toContain('bg-accent');
|
||||
});
|
||||
|
||||
it('applies the danger variant', () => {
|
||||
const wrapper = mount(AppButton, {
|
||||
props: { variant: 'danger' }, slots: { default: 'Delete' },
|
||||
});
|
||||
expect(wrapper.get('button').classes()).toContain('bg-danger');
|
||||
});
|
||||
|
||||
it('emits click when enabled', async () => {
|
||||
const wrapper = mount(AppButton, { slots: { default: 'Go' } });
|
||||
await wrapper.get('button').trigger('click');
|
||||
expect(wrapper.emitted('click')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('reflects the disabled prop', () => {
|
||||
const wrapper = mount(AppButton, {
|
||||
props: { disabled: true }, slots: { default: 'Go' },
|
||||
});
|
||||
expect(wrapper.get('button').attributes('disabled')).toBeDefined();
|
||||
});
|
||||
});
|
||||
31
n-link-core/components/AppButton.vue
Normal file
31
n-link-core/components/AppButton.vue
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<template>
|
||||
<button class="rounded font-bold focus:outline-none focus-visible:ring-2
|
||||
focus-visible:ring-accent transition-colors disabled:opacity-75
|
||||
disabled:cursor-not-allowed"
|
||||
:class="[variantClass, sizeClass]" :disabled="disabled">
|
||||
<slot/>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
variant?: 'primary' | 'secondary' | 'danger' | 'success';
|
||||
size?: 'md' | 'sm';
|
||||
disabled?: boolean;
|
||||
}>(), {
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
disabled: false,
|
||||
});
|
||||
|
||||
const variantClass = computed(() => ({
|
||||
primary: 'bg-accent text-accent-fg',
|
||||
secondary: 'bg-raised text-fg border border-edge',
|
||||
danger: 'bg-danger text-white',
|
||||
success: 'bg-success text-white',
|
||||
}[props.variant]));
|
||||
|
||||
const sizeClass = computed(() => (props.size === 'sm' ? 'px-3 py-2 text-sm' : 'px-6 py-2.5'));
|
||||
</script>
|
||||
Loading…
Reference in a new issue