design-system-next 1.0.21

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.
@@ -0,0 +1,43 @@
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+
3
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
+
5
+ const BADGE_VARIANT = ['danger', 'disabled', 'information', 'brand'] as const;
6
+ const BADGE_SIZE = ['small', 'big', 'tiny'] as const;
7
+ const BADGE_POSITION = ['top', 'bottom', 'default'] as const;
8
+
9
+ export const badgePropTypes = {
10
+ /**
11
+ * @description Badge Label
12
+ */
13
+ text: {
14
+ type: String,
15
+ default: '0',
16
+ },
17
+ /**
18
+ * @description Badge variant
19
+ */
20
+ variant: {
21
+ type: String,
22
+ validator: (value: (typeof BADGE_VARIANT)[number]) => BADGE_VARIANT.includes(value),
23
+ default: 'brand',
24
+ },
25
+ /**
26
+ * @description Badge size
27
+ */
28
+ size: {
29
+ type: String,
30
+ validator: (value: (typeof BADGE_SIZE)[number]) => BADGE_SIZE.includes(value),
31
+ default: 'small',
32
+ },
33
+ /**
34
+ * @description Badge position (top, bottom, default)
35
+ */
36
+ position: {
37
+ type: String,
38
+ validator: (value: (typeof BADGE_POSITION)[number]) => BADGE_POSITION.includes(value),
39
+ default: 'default',
40
+ },
41
+ };
42
+
43
+ export type BadgePropTypes = ExtractPropTypes<typeof badgePropTypes>;
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <div class="tw-flex tw-gap-2">
3
+ <slot />
4
+ <div :class="[badgeElementWrapper]">
5
+ <section :class="[badgePositionClasses]">
6
+ <div :class="[badgeClasses, 'tw-flex tw-items-center tw-justify-center']">
7
+ {{ props.size === 'tiny' ? '' : text }}
8
+ </div>
9
+ </section>
10
+ </div>
11
+ </div>
12
+ </template>
13
+
14
+ <script setup lang="ts">
15
+ import { badgePropTypes } from './badge';
16
+ import { useBadge } from './use-badge';
17
+
18
+ const props = defineProps(badgePropTypes);
19
+ const { badgeClasses, badgePositionClasses, badgeElementWrapper } = useBadge(props);
20
+ </script>
@@ -0,0 +1,52 @@
1
+ import { computed } from 'vue';
2
+ import type { BadgePropTypes } from './badge';
3
+
4
+ import classNames from 'classnames';
5
+ export const useBadge = (props: BadgePropTypes) => {
6
+ const { position, size, variant } = props as {
7
+ size: 'big' | 'small' | 'tiny';
8
+ variant: 'danger' | 'disabled' | 'information' | 'brand';
9
+ position: 'top' | 'bottom' | 'default';
10
+ };
11
+
12
+ const badgeClasses = computed(() => {
13
+ const variantClasses = classNames({
14
+ 'tw-background-color-danger-base tw-text-color-inverted-strong': variant === 'danger',
15
+ 'tw-background-color-disabled tw-text-color-on-fill-disabled ': variant === 'disabled',
16
+ 'tw-background-color-information-base tw-text-color-inverted-strong': variant === 'information',
17
+ 'tw-background-color-brand-base tw-text-color-inverted-strong': variant === 'brand',
18
+ });
19
+
20
+ const sizeClasses = classNames({
21
+ 'tw-label-sm-medium tw-h-[20px] tw-min-w-[20px] tw-rounded-[32px]': size === 'big',
22
+ 'tw-label-xs-medium tw-h-[16px] tw-min-w-[16px] tw-rounded-[32px]': size === 'small',
23
+ 'tw-h-[10px] tw-min-w-[10px] tw-rounded-full': size === 'tiny',
24
+ });
25
+
26
+ return classNames(variantClasses, sizeClasses);
27
+ });
28
+
29
+ const badgePositionClasses = computed(() => {
30
+ return classNames({
31
+ 'tw-absolute tw--top-1 tw-right-1': position === 'top' && size === 'tiny',
32
+ ' tw-absolute tw--bottom-1 tw-right-1': position === 'bottom' && size === 'tiny',
33
+ 'tw-absolute tw--top-2 tw--right-1': position === 'top' && size === 'small',
34
+ ' tw-absolute tw--bottom-2 tw--right-1': position === 'bottom' && size === 'small',
35
+ 'tw-absolute tw--top-3 tw--right-2': position === 'top' && size === 'big',
36
+ ' tw-absolute tw--bottom-3 tw--right-2': position === 'bottom' && size === 'big',
37
+ });
38
+ });
39
+
40
+ const badgeElementWrapper = computed(() => {
41
+ return classNames({
42
+ 'tw-flex tw-items-center tw-gap-2 ': position === 'default',
43
+ 'tw-relative': position === 'top' || position === 'bottom',
44
+ });
45
+ });
46
+
47
+ return {
48
+ badgeClasses,
49
+ badgePositionClasses,
50
+ badgeElementWrapper,
51
+ };
52
+ };
@@ -0,0 +1,64 @@
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+
3
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
+
5
+ const BUTTON_TONES = ['neutral', 'success', 'danger'] as const;
6
+ const BUTTON_SIZES = ['small', 'medium', 'large'] as const;
7
+ const BUTTON_TYPES = ['button', 'submit', 'reset'] as const;
8
+ const BUTTON_STATES = ['base', 'hover', 'pressed', 'focus'] as const;
9
+ const BUTTON_VARIANTS = ['primary', 'secondary', 'tertiary'] as const;
10
+
11
+ export const buttonPropTypes = {
12
+ /**
13
+ * @description Button tone
14
+ */
15
+ tone: {
16
+ type: String as PropType<(typeof BUTTON_TONES)[number]>,
17
+ validator: (value: (typeof BUTTON_TONES)[number]) => BUTTON_TONES.includes(value),
18
+ default: 'neutral',
19
+ },
20
+ /**
21
+ * @description Button size
22
+ */
23
+ size: {
24
+ type: String as PropType<(typeof BUTTON_SIZES)[number]>,
25
+ validator: (value: (typeof BUTTON_SIZES)[number]) => BUTTON_SIZES.includes(value),
26
+ default: 'medium',
27
+ },
28
+ /**
29
+ * @description Native button type
30
+ */
31
+ type: {
32
+ type: String as PropType<(typeof BUTTON_TYPES)[number]>,
33
+ validator: (value: (typeof BUTTON_TYPES)[number]) => BUTTON_TYPES.includes(value),
34
+ default: 'button',
35
+ },
36
+ /**
37
+ * @description Button state
38
+ */
39
+ state: {
40
+ type: String as PropType<(typeof BUTTON_STATES)[number]>,
41
+ validator: (value: (typeof BUTTON_STATES)[number]) => BUTTON_STATES.includes(value),
42
+ default: 'base',
43
+ },
44
+ /**
45
+ * @description Button Variant
46
+ */
47
+ variant: {
48
+ type: String as PropType<(typeof BUTTON_VARIANTS)[number]>,
49
+ validator: (value: (typeof BUTTON_VARIANTS)[number]) => BUTTON_VARIANTS.includes(value),
50
+ default: 'primary',
51
+ },
52
+ disabled: {
53
+ type: Boolean,
54
+ default: false,
55
+ },
56
+ };
57
+
58
+ export const buttonEmitTypes = {
59
+ click: (evt: MouseEvent): evt is MouseEvent => evt instanceof MouseEvent,
60
+ };
61
+
62
+ export type ButtonPropTypes = ExtractPropTypes<typeof buttonPropTypes>;
63
+ export type ButtonEmitTypes = typeof buttonEmitTypes;
64
+ export type ButtonType = ButtonPropTypes['type'];
@@ -0,0 +1,25 @@
1
+ <template>
2
+ <button
3
+ ref="buttonRef"
4
+ v-bind="buttonProps"
5
+ :class="[
6
+ 'tw-inline-flex tw-w-fit tw-min-w-[56px] tw-cursor-pointer tw-items-center tw-justify-center tw-rounded-md tw-outline-none tw-duration-150 tw-ease-in-out',
7
+ 'hover:tw-shadow-button-hover',
8
+ 'active:tw-scale-95',
9
+ buttonClass,
10
+ ]"
11
+ @click="handleClick"
12
+ >
13
+ <slot />
14
+ </button>
15
+ </template>
16
+
17
+ <script lang="ts" setup>
18
+ import { buttonEmitTypes, buttonPropTypes } from './button';
19
+ import { useButton } from './use-button';
20
+
21
+ const props = defineProps(buttonPropTypes);
22
+ const emit = defineEmits(buttonEmitTypes);
23
+
24
+ const { buttonRef, buttonProps, buttonClass, handleClick } = useButton(props, emit);
25
+ </script>
@@ -0,0 +1,166 @@
1
+ import { computed, ref, ComputedRef } from 'vue';
2
+ import { useElementHover, useMousePressed, useFocus } from '@vueuse/core';
3
+
4
+ import classNames from 'classnames';
5
+
6
+ import type { SetupContext } from 'vue';
7
+ import type { ButtonEmitTypes, ButtonPropTypes } from './button';
8
+
9
+ export const useButton = (props: ButtonPropTypes, emit: SetupContext<ButtonEmitTypes>['emit']) => {
10
+ const buttonRef = ref<HTMLButtonElement | null>(null);
11
+ const isHovered = useElementHover(buttonRef);
12
+ const { pressed } = useMousePressed({ target: buttonRef });
13
+ const { focused } = useFocus(buttonRef);
14
+ const { state, type, size, tone, variant, disabled } = props;
15
+
16
+ const buttonProps: ComputedRef<Record<string, unknown>> = computed(() => {
17
+ return {
18
+ ...(disabled && { ariaDisabled: true }),
19
+ disabled: disabled,
20
+ autofocus: state === 'focus',
21
+ type: type ?? 'button',
22
+ };
23
+ });
24
+
25
+ const buttonSizeCssClass: ComputedRef<string> = computed(() =>
26
+ classNames({
27
+ 'tw-px-[4px] tw-py-[6px] tw-font-medium tw-font-size-100 tw-leading-100': size === 'small',
28
+ 'tw-p-[8px] tw-font-medium tw-font-size-100 tw-leading-100': size === 'medium',
29
+ 'tw-px-[8px] tw-py-[12px] tw-font-medium tw-font-size-200 tw-leading-300': size === 'large',
30
+ }),
31
+ );
32
+
33
+ const buttonTextCssClass: ComputedRef<string> = computed(() => {
34
+ if (variant === 'secondary' || variant === 'tertiary') {
35
+ return classNames({
36
+ 'tw-text-color-strong': tone === 'neutral',
37
+ 'tw-text-color-brand-base': tone === 'success',
38
+ 'tw-text-color-danger-base': tone === 'danger',
39
+ });
40
+ }
41
+
42
+ return classNames({
43
+ 'tw-text-color-strong': tone === 'neutral',
44
+ 'tw-text-color-inverted-strong': tone === 'success' || tone === 'danger',
45
+ });
46
+ });
47
+
48
+ // #region - Background Css Class
49
+ const buttonBackgroundCssClass: ComputedRef<string> = computed(() => {
50
+ if (variant === 'secondary') {
51
+ return isHovered.value ? 'tw-background-color-hover' : '';
52
+ }
53
+
54
+ if (variant === 'tertiary') {
55
+ return getTertiaryBackground();
56
+ }
57
+
58
+ return getBackgroundBasedOnState();
59
+ });
60
+
61
+ function getTertiaryBackground(): string {
62
+ if (pressed.value) {
63
+ return 'tw-background-color-pressed';
64
+ }
65
+
66
+ return isHovered.value ? 'tw-background-color-hover' : '';
67
+ }
68
+
69
+ function getBackgroundBasedOnState(): string {
70
+ if (pressed.value) {
71
+ return getPressedBackground();
72
+ }
73
+
74
+ if (isHovered.value) {
75
+ return getHoveredBackground();
76
+ }
77
+
78
+ return getDefaultBackground();
79
+ }
80
+
81
+ function getPressedBackground(): string {
82
+ const backgrounds: Record<string, string> = {
83
+ neutral: 'tw-background-color-pressed',
84
+ success: 'tw-background-color-brand-pressed',
85
+ danger: 'tw-background-color-danger-pressed',
86
+ };
87
+
88
+ return backgrounds[tone] || '';
89
+ }
90
+
91
+ function getHoveredBackground(): string {
92
+ const backgrounds: Record<string, string> = {
93
+ neutral: 'tw-background-color-hover',
94
+ success: 'tw-background-color-success-pressed',
95
+ danger: 'tw-background-color-danger-hover',
96
+ };
97
+
98
+ return backgrounds[tone] || '';
99
+ }
100
+
101
+ function getDefaultBackground(): string {
102
+ const backgrounds: Record<string, string> = {
103
+ neutral: 'tw-background-color-base',
104
+ success: 'tw-background-color-brand-base',
105
+ danger: 'tw-background-color-danger-base',
106
+ };
107
+
108
+ return backgrounds[tone] || '';
109
+ }
110
+ // #endregion - Background Css Class
111
+
112
+ const buttonBorderCssClass: ComputedRef<string> = computed(() => {
113
+ if (variant === 'primary' || variant === 'tertiary') {
114
+ if (focused.value) {
115
+ return 'tw-border-solid tw-border tw-border-white-50';
116
+ }
117
+
118
+ return 'tw-border-solid tw-border tw-border-transparent';
119
+ }
120
+
121
+ return classNames({
122
+ 'tw-border-solid tw-border tw-border-color-base': tone === 'neutral',
123
+ 'tw-border-solid tw-border tw-border-color-brand-base': tone === 'success',
124
+ 'tw-border-solid tw-border tw-border-color-danger-base': tone === 'danger',
125
+ });
126
+ });
127
+
128
+ const buttonToneCssClass: ComputedRef<string> = computed(() => {
129
+ return classNames(buttonBackgroundCssClass.value, buttonTextCssClass.value, buttonBorderCssClass.value);
130
+ });
131
+
132
+ const buttonShadowClass: ComputedRef<string> = computed(() => {
133
+ if (pressed.value) {
134
+ return 'tw-shadow-button';
135
+ } else if (focused.value) {
136
+ return 'tw-shadow-button-active';
137
+ }
138
+
139
+ return '';
140
+ });
141
+
142
+ const buttonAllCssClass: ComputedRef<string> = computed(() => {
143
+ if (disabled) {
144
+ return classNames(buttonSizeCssClass.value, 'tw-text-color-disabled');
145
+ }
146
+
147
+ return classNames(buttonSizeCssClass.value, buttonToneCssClass.value, buttonShadowClass.value);
148
+ });
149
+
150
+ const handleClick = (evt: MouseEvent) => {
151
+ if (disabled) {
152
+ evt.stopPropagation();
153
+
154
+ return;
155
+ }
156
+
157
+ emit('click', evt);
158
+ };
159
+
160
+ return {
161
+ buttonRef,
162
+ buttonProps,
163
+ buttonClass: buttonAllCssClass,
164
+ handleClick,
165
+ };
166
+ };
@@ -0,0 +1,57 @@
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+
3
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
+
5
+ const LOZENGE_TONE = ['pending', 'information', 'success', 'danger', 'neutral', 'caution'] as const;
6
+
7
+ export const lozengePropTypes = {
8
+ /**
9
+ * @description Lozenge Label
10
+ */
11
+ label: {
12
+ type: String,
13
+ default: 'label',
14
+ },
15
+ /**
16
+ * @description Lozenge tone
17
+ */
18
+ tone: {
19
+ type: String as PropType<(typeof LOZENGE_TONE)[number]>,
20
+ validator: (value: (typeof LOZENGE_TONE)[number]) => LOZENGE_TONE.includes(value),
21
+ default: 'plain',
22
+ },
23
+ /**
24
+ * @description Lozenge type (fill or outline)
25
+ */
26
+ fill: {
27
+ type: Boolean,
28
+ default: false,
29
+ },
30
+ /**
31
+ * @description Lozenge removable
32
+ */
33
+ removable: {
34
+ type: Boolean,
35
+ default: false,
36
+ },
37
+ /**
38
+ * @description avatar image url
39
+ */
40
+ url: {
41
+ type: String,
42
+ default: '',
43
+ },
44
+ /**
45
+ * @description handler if the lozenge is visible
46
+ */
47
+ visible: {
48
+ type: Boolean,
49
+ default: true,
50
+ },
51
+ };
52
+
53
+ export type LozengePropTypes = ExtractPropTypes<typeof lozengePropTypes>;
54
+ export const removeEmitTypes = {
55
+ onRemove: (evt: MouseEvent): evt is MouseEvent => evt instanceof MouseEvent,
56
+ };
57
+ export type RemoveEmitTypes = typeof removeEmitTypes;
@@ -0,0 +1,96 @@
1
+ <template>
2
+ <div v-if="visible" :class="[fill ? 'lozenge-fill' : 'lozenge']">
3
+ <div
4
+ :class="[
5
+ 'tw-label-xs-medium tw-inline-flex tw-items-center tw-gap-size-spacing-6xs tw-rounded-md tw-border tw-border-solid tw-p-size-spacing-5xs tw-text-xs tw-uppercase',
6
+ tone,
7
+ ]"
8
+ >
9
+ <div v-if="$slots.icon" class="tw-flex tw-h-3 tw-w-3 tw-items-center tw-overflow-hidden">
10
+ <slot name="icon" />
11
+ </div>
12
+
13
+ <div v-if="$slots.avatar" class="tw-flex tw-items-center">
14
+ <slot name="avatar" />
15
+ </div>
16
+
17
+ <div v-if="url && !$slots.avatar" class="tw-h-4 tw-w-4 tw-overflow-hidden">
18
+ <img class="tw-h-full tw-w-full tw-rounded-full tw-object-cover" :src="url" alt="avatar" />
19
+ </div>
20
+
21
+ <div>{{ label }}</div>
22
+ </div>
23
+ </div>
24
+ </template>
25
+
26
+ <script setup lang="ts">
27
+ import { lozengePropTypes } from './lozenge';
28
+
29
+ defineProps(lozengePropTypes);
30
+ </script>
31
+
32
+ <style lang="scss" scoped>
33
+ .lozenge {
34
+ @apply tw-flex tw-flex-wrap tw-rounded-md;
35
+
36
+ .pending {
37
+ @apply tw-text-color-pending-base tw-background-color-pending-weak tw-border-color-pending-base;
38
+ }
39
+
40
+ .information {
41
+ @apply tw-text-color-information-base tw-background-color-information-weak tw-border-color-information-base;
42
+ }
43
+
44
+ .success {
45
+ @apply tw-text-color-success-base tw-background-color-success-weak tw-border-color-success-base;
46
+ }
47
+
48
+ .neutral {
49
+ @apply tw-text-color-base tw-background-color-base tw-border-color-base;
50
+ }
51
+
52
+ .danger {
53
+ @apply tw-text-color-danger-base tw-background-color-danger-weak tw-border-color-danger-base;
54
+ }
55
+
56
+ .caution {
57
+ @apply tw-text-color-caution-base tw-background-color-caution-weak tw-border-color-caution-base;
58
+ }
59
+
60
+ .plain {
61
+ @apply tw-text-color-strong tw-border-color-base tw-background-color;
62
+ }
63
+ }
64
+
65
+ .lozenge-fill {
66
+ @apply tw-flex tw-flex-wrap;
67
+
68
+ .pending {
69
+ @apply tw-background-color-pending-base tw-text-color-strong tw-border-none;
70
+ }
71
+
72
+ .information {
73
+ @apply tw-background-color-information-base tw-text-color-inverted-strong tw-border-none;
74
+ }
75
+
76
+ .success {
77
+ @apply tw-background-color-success-base tw-text-color-inverted-strong;
78
+ }
79
+
80
+ .danger {
81
+ @apply tw-background-color-danger-base tw-text-color-inverted-strong tw-border-none;
82
+ }
83
+
84
+ .neutral {
85
+ @apply tw-background-color-base tw-text-color-strong tw-border-none;
86
+ }
87
+
88
+ .caution {
89
+ @apply tw-background-color-caution-base tw-text-color-strong tw-border-none;
90
+ }
91
+
92
+ .plain {
93
+ @apply tw-text-color-strong tw-background-color tw-border-none;
94
+ }
95
+ }
96
+ </style>
@@ -0,0 +1,12 @@
1
+ import type { SetupContext } from 'vue';
2
+ import type { RemoveEmitTypes } from './lozenge';
3
+
4
+ export const useLozenge = (emit: SetupContext<RemoveEmitTypes>['emit']) => {
5
+ const hanndleRemoveLozenge = (evt: MouseEvent) => {
6
+ emit('onRemove', evt);
7
+ };
8
+
9
+ return {
10
+ hanndleRemoveLozenge,
11
+ };
12
+ };
@@ -0,0 +1,54 @@
1
+ import type { PropType, ExtractPropTypes } from 'vue';
2
+
3
+ export const definePropType = <T>(val: unknown): PropType<T> => val as PropType<T>;
4
+
5
+ const RADIO_STATES = ['default', 'hover', 'disabled'] as const;
6
+
7
+ export const radioPropTypes = {
8
+ /**
9
+ * @description Radio state
10
+ */
11
+ state: {
12
+ type: String as PropType<(typeof RADIO_STATES)[number]>,
13
+ validator: (value: (typeof RADIO_STATES)[number]) => RADIO_STATES.includes(value),
14
+ default: 'default',
15
+ },
16
+ disabled: {
17
+ type: Boolean,
18
+ default: false,
19
+ },
20
+ /**
21
+ * @description Attribute id
22
+ */
23
+ id: {
24
+ type: String,
25
+ required: true,
26
+ },
27
+ /**
28
+ * @description Value for v-model
29
+ */
30
+ modelValue: {
31
+ type: [String, Number, Boolean],
32
+ required: true,
33
+ default: false,
34
+ },
35
+ /**
36
+ * @description Attribute name
37
+ */
38
+ name: {
39
+ type: String,
40
+ required: true,
41
+ },
42
+ /**
43
+ * @description Attribute value
44
+ */
45
+ value: {
46
+ type: [String, Number, Boolean],
47
+ required: true,
48
+ },
49
+ };
50
+
51
+ export const radioEmitTypes = ['update:modelValue'];
52
+
53
+ export type RadioPropTypes = ExtractPropTypes<typeof radioPropTypes>;
54
+ export type SwitchEmitTypes = typeof radioEmitTypes;
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <input
3
+ :id="props.id"
4
+ ref="radioRef"
5
+ v-model="proxyValue"
6
+ type="radio"
7
+ :name="props.name"
8
+ :value="props.value"
9
+ :disabled="props.disabled"
10
+ :class="radioClasses"
11
+ />
12
+ <label
13
+ ref="radioRef"
14
+ :for="props.id"
15
+ :disabled="props.disabled"
16
+ :class="[
17
+ 'tw-flex tw-items-center tw-space-x-2',
18
+ radioLabelClasses
19
+ ]"
20
+ >
21
+ <span :class="indicatorClasses"></span>
22
+ <slot />
23
+ </label>
24
+ </template>
25
+
26
+ <script lang="ts" setup>
27
+ import { radioPropTypes, radioEmitTypes } from "./radio";
28
+ import { useVModel } from "@vueuse/core";
29
+ import { useRadioButton } from "./use-radio";
30
+
31
+ const props = defineProps(radioPropTypes);
32
+ const emit = defineEmits(radioEmitTypes);
33
+
34
+ const proxyValue = useVModel(props, "modelValue", emit);
35
+ const { radioRef, radioClasses, indicatorClasses, radioLabelClasses } = useRadioButton(props);
36
+ </script>