Skip to content

Commit a77ea5e

Browse files
authored
Merge pull request #19946 from ElectronicBlueberry/g-button-and-color-system
First steps of bootstrap replacement
2 parents f0fa975 + 282ec87 commit a77ea5e

15 files changed

Lines changed: 933 additions & 184 deletions

File tree

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,342 @@
1+
<script setup lang="ts">
2+
import type { Placement } from "@popperjs/core";
3+
import { computed, ref } from "vue";
4+
import { RouterLink } from "vue-router";
5+
6+
import { useAccessibleHover } from "@/composables/accessibleHover";
7+
import { useResolveElement } from "@/composables/resolveElement";
8+
import { useUid } from "@/composables/utils/uid";
9+
10+
import { type ComponentColor, type ComponentSize, type ComponentVariantClassList, prefix } from "./componentVariants";
11+
12+
import GTooltip from "./GTooltip.vue";
13+
14+
const props = defineProps<{
15+
href?: string;
16+
to?: string;
17+
color?: ComponentColor;
18+
outline?: boolean;
19+
disabled?: boolean;
20+
title?: string;
21+
disabledTitle?: string;
22+
size?: ComponentSize;
23+
tooltip?: boolean;
24+
tooltipPlacement?: Placement;
25+
inline?: boolean;
26+
iconOnly?: boolean;
27+
transparent?: boolean;
28+
pill?: boolean;
29+
pressed?: boolean;
30+
}>();
31+
32+
const emit = defineEmits<{
33+
(e: "click", event: PointerEvent): void;
34+
(e: "update:pressed", pressed: boolean): void;
35+
}>();
36+
37+
function onClick(event: PointerEvent) {
38+
if (!props.disabled) {
39+
emit("click", event);
40+
emit("update:pressed", !props.pressed);
41+
}
42+
}
43+
44+
const variantClasses = computed(() => {
45+
const classObject = {} as ComponentVariantClassList;
46+
classObject[prefix(props.color ?? "grey")] = true;
47+
classObject[prefix(props.size ?? "medium")] = true;
48+
return classObject;
49+
});
50+
51+
const styleClasses = computed(() => {
52+
return {
53+
"g-outline": props.outline,
54+
"g-disabled": props.disabled,
55+
"g-icon-only": props.iconOnly,
56+
"g-inline": props.inline,
57+
"g-pill": props.pill,
58+
"g-transparent": props.transparent,
59+
"g-pressed": props.pressed,
60+
};
61+
});
62+
63+
const baseComponent = computed(() => {
64+
if (props.to) {
65+
return RouterLink;
66+
} else if (props.href) {
67+
return "a" as const;
68+
} else {
69+
return "button" as const;
70+
}
71+
});
72+
73+
const currentTooltip = computed(() => {
74+
if (props.disabled) {
75+
return props.disabledTitle ?? props.title;
76+
} else {
77+
return props.title;
78+
}
79+
});
80+
81+
const currentTitle = computed(() => {
82+
if (props.tooltip) {
83+
return false;
84+
} else {
85+
return currentTooltip.value;
86+
}
87+
});
88+
89+
const tooltipId = useUid("g-tooltip");
90+
91+
const describedBy = computed(() => {
92+
if (props.tooltip) {
93+
return tooltipId.value;
94+
} else {
95+
return false;
96+
}
97+
});
98+
99+
const buttonRef = ref<HTMLElement | InstanceType<typeof RouterLink> | null>(null);
100+
const tooltipRef = ref<InstanceType<typeof GTooltip>>();
101+
102+
const buttonElementRef = useResolveElement(buttonRef);
103+
104+
useAccessibleHover(
105+
buttonElementRef,
106+
() => {
107+
tooltipRef.value?.show();
108+
},
109+
() => {
110+
tooltipRef.value?.hide();
111+
}
112+
);
113+
</script>
114+
115+
<template>
116+
<component
117+
:is="baseComponent"
118+
ref="buttonRef"
119+
class="g-button"
120+
:data-title="currentTooltip"
121+
:class="{ ...variantClasses, ...styleClasses }"
122+
:to="props.to"
123+
:href="props.to ?? props.href"
124+
:title="currentTitle"
125+
:aria-describedby="describedBy"
126+
v-bind="$attrs"
127+
@click="onClick">
128+
<slot></slot>
129+
130+
<!-- TODO: make tooltip a sibling in Vue 3 -->
131+
<GTooltip
132+
v-if="props.tooltip"
133+
:id="tooltipId"
134+
ref="tooltipRef"
135+
:reference="buttonElementRef"
136+
:text="currentTooltip"
137+
:placement="props.tooltipPlacement" />
138+
</component>
139+
</template>
140+
141+
<style scoped lang="scss">
142+
.g-button {
143+
display: inline-block;
144+
margin: 0;
145+
border: 1px solid;
146+
border-radius: var(--spacing-1);
147+
text-decoration: none;
148+
vertical-align: middle;
149+
cursor: pointer;
150+
151+
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out,
152+
box-shadow 0.15s ease-in-out;
153+
154+
@media (prefers-reduced-motion) {
155+
transition: none;
156+
}
157+
158+
&:focus {
159+
outline: none;
160+
box-shadow: 0 0 0 0.2rem rgb(from var(--color-blue-400) r g b / 0.33);
161+
z-index: 999;
162+
}
163+
164+
&:focus-visible {
165+
outline: none;
166+
box-shadow: 0 0 0 0.2rem var(--color-blue-400);
167+
z-index: 999;
168+
}
169+
170+
// sizes
171+
&.g-small {
172+
font-size: var(--font-size-small);
173+
padding: var(--spacing-1) var(--spacing-2);
174+
}
175+
176+
&.g-medium {
177+
font-size: var(--font-size-medium);
178+
padding: var(--spacing-1) var(--spacing-2);
179+
}
180+
181+
&.g-large {
182+
font-size: var(--font-size-large);
183+
padding: var(--spacing-2) var(--spacing-3);
184+
}
185+
186+
// colors
187+
&.g-grey {
188+
background-color: var(--color-grey-200);
189+
border-color: var(--color-grey-300);
190+
color: var(--color-grey-800);
191+
192+
&:hover,
193+
&:focus-visible {
194+
background-color: var(--color-grey-300);
195+
border-color: var(--color-grey-400);
196+
197+
&:active {
198+
background-color: var(--color-grey-400);
199+
border-color: var(--color-grey-600);
200+
}
201+
}
202+
203+
&:focus-visible {
204+
border-color: var(--color-grey-600);
205+
}
206+
}
207+
208+
@each $color in "blue", "green", "red", "yellow", "orange" {
209+
&.g-#{$color} {
210+
background-color: var(--color-#{$color}-600);
211+
border-color: var(--color-#{$color}-600);
212+
color: var(--color-#{$color}-100);
213+
214+
&:hover,
215+
&:focus-visible {
216+
background-color: var(--color-#{$color}-700);
217+
border-color: var(--color-#{$color}-700);
218+
219+
&:active {
220+
background-color: var(--color-#{$color}-600);
221+
color: var(--color-#{$color}-100);
222+
}
223+
}
224+
225+
&:focus-visible {
226+
border-color: var(--color-#{$color}-900);
227+
}
228+
}
229+
230+
&.g-outline:not(.g-pressed).g-#{$color} {
231+
border-color: var(--color-#{$color}-600);
232+
color: var(--color-#{$color}-600);
233+
234+
&:hover {
235+
background-color: var(--color-#{$color}-600);
236+
border-color: var(--color-#{$color}-600);
237+
color: var(--color-#{$color}-100);
238+
}
239+
240+
&:focus-visible {
241+
border-color: var(--color-#{$color}-900);
242+
background-color: var(--color-#{$color}-200);
243+
color: var(--color-#{$color}-600);
244+
}
245+
}
246+
}
247+
248+
&.g-outline:not(.g-pressed) {
249+
background-color: var(--background-color);
250+
}
251+
252+
&.g-disabled {
253+
background-color: var(--color-grey-100);
254+
border-color: var(--color-grey-200);
255+
color: var(--color-grey-500);
256+
257+
&:hover,
258+
&:focus-visible {
259+
background-color: var(--color-grey-100);
260+
border-color: var(--color-grey-200);
261+
262+
&:active {
263+
background-color: var(--color-grey-100);
264+
border-color: var(--color-grey-200);
265+
color: var(--color-grey-500);
266+
}
267+
}
268+
269+
&:focus-visible {
270+
border-color: var(--color-grey-500);
271+
}
272+
273+
&.g-outline {
274+
background-color: var(--background-color);
275+
border-color: var(--color-grey-400);
276+
color: var(--color-grey-400);
277+
278+
&:hover,
279+
&:focus,
280+
&:focus-visible {
281+
background-color: var(--background-color);
282+
border-color: var(--color-grey-400);
283+
color: var(--color-grey-400);
284+
}
285+
286+
&:focus-visible {
287+
border-color: var(--color-grey-800);
288+
background-color: var(--background-color);
289+
color: var(--color-grey-500);
290+
}
291+
}
292+
}
293+
294+
// variants
295+
&.g-inline {
296+
display: inline;
297+
padding-top: 0;
298+
padding-bottom: 0;
299+
}
300+
301+
&.g-pill {
302+
border-radius: 100rem;
303+
}
304+
305+
&.g-icon-only {
306+
aspect-ratio: 1;
307+
display: inline-flex;
308+
justify-content: center;
309+
310+
&.g-small,
311+
&.g-medium {
312+
padding: var(--spacing-1);
313+
}
314+
315+
&.g-large {
316+
padding: var(--spacing-2);
317+
}
318+
319+
&.g-inline {
320+
padding: 2px;
321+
}
322+
}
323+
324+
&.g-transparent {
325+
border: none;
326+
background-color: rgb(100% 100% 100% / 0);
327+
328+
@each $color in "blue", "green", "red", "yellow", "orange" {
329+
&.g-#{$color} {
330+
color: var(--color-#{$color}-600);
331+
332+
&:hover,
333+
&:focus,
334+
&:focus-visible {
335+
background-color: var(--color-#{$color}-600);
336+
color: var(--color-#{$color}-100);
337+
}
338+
}
339+
}
340+
}
341+
}
342+
</style>

0 commit comments

Comments
 (0)