Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions app/assets/scss/_resources.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
// Include only SASS variables / mixins / functions.

// functions
@forward "./functions/px-to-rem";
@forward "./functions/px-to-em";
@forward "./functions/ease";
@forward "./functions/flex-grid";
@forward "assets/scss/functions/px-to-rem";
@forward "assets/scss/functions/px-to-em";
@forward "assets/scss/functions/ease";
@forward "assets/scss/functions/flex-grid";

// mixins
@forward './mixins/include-media' show media;
@forward './mixins/typography';
@forward './mixins/form';
@forward 'assets/scss/mixins/include-media' show media;
@forward 'assets/scss/mixins/typography';
@forward 'assets/scss/mixins/form';
@forward 'assets/scss/mixins/theme';

// components
@forward './components/v-button';
@forward 'assets/scss/components/v-button';
90 changes: 90 additions & 0 deletions app/assets/scss/components/_v-button-outlined.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
@use 'assets/scss/mixins/css-vars-map';
@use 'assets/scss/mixins/sizes' as *;
@use 'assets/scss/functions/ease' as *;

$vars: (
(vars: (xs: (padding: 8px 12px,
gap: 6px,
),
sm: (padding: 10px 16px,
gap: 8px,
),
md: (padding: 16px 20px,
gap: 12px,
),
lg: (padding: 18px 28px,
gap: 12px,
),
),
prefix: '',
),
(vars: (xs: (font-size: 14px,
),
sm: (font-size: 16px,
),
md: (font-size: 18px,
),
lg: (font-size: 20px,
),
),
prefix: 'label',
),
(vars: (xs: (font-size: 16px,
),
sm: (font-size: 20px,
),
md: (font-size: 24px,
),
lg: (font-size: 32px,
),
),
prefix: 'icon',
),
);
$component: 'v-button';

// Outputs CSS property declarations using the CSS variables set by `size()`, with sm values as fallbacks.
// Usage: @include v-button.apply('') or @include v-button.apply('label')
@mixin apply($prefix: '') {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why moving the css props to the mixin instead of keeping it into the VButton component style?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like that, we can fully style a custom element with the same VButton variant style without using the VButton component

@if $prefix == '' {
border: initial;
background: transparent;
outline: 1px solid var(--v-button-outline-color, var(--button-label-on-light-outline-border, #010101));
transition: color 0.3s ease(out-quad), background-color 0.3s ease(out-quad);

&:where([inert], #{&}--disabled, [disabled]) {
color: var(--button-label-on-light-outline-content-disabled, #bdbdbd);
outline-color: var(--button-label-on-light-outline-border-disabled, rgba(1, 1, 1, 0.3));
}

@media (hover: hover) {
&:hover {
background-color: var(--button-label-on-light-outline-bg, #010101);
color: var(--button-label-on-light-outline-content-hover, #FFF);
}
}
}

@include css-vars-map.apply($vars, $component, $prefix, $fallback-size: 'sm');
}

// Sets the CSS variables for a given size (xs, sm, md, lg) from $vars.
// Call this on the component element to drive properties() declarations elsewhere.
// eg: @include v-button-solid.size('md') generates:
// --v-button-solid-min-height: 56px;
// --v-button-solid-padding-inline: 20px;
// --v-button-solid-label-font-size: 18px;
// --v-button-solid-icon-font-size: 24px; etc.
@mixin size($size) {
@include css-vars-map.deep-get($vars, $size, $component);
}

// Generates a BEM modifier class for each size in $vars.
// Call this on the component root to enable size variants via CSS class.
// eg: @include v-button-solid.size-classes() generates:
// &--size-xs { --v-button-solid-min-height: 32px; … }
// &--size-sm { --v-button-solid-min-height: 40px; … }
// &--size-md { --v-button-solid-min-height: 56px; … } etc.
@mixin size-classes() {
@include sizes($vars, $component);
}
70 changes: 59 additions & 11 deletions app/assets/scss/components/_v-button.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@use '../mixins/css-vars-map';
@use 'assets/scss/mixins/css-vars-map';
@use 'assets/scss/mixins/sizes' as *;

// $example-sizes-map: (
// (
Expand All @@ -25,22 +26,69 @@
// ),
// );
$vars: (
(
vars: (
sm: (
font-size: 14px,
(vars: (xs: (padding: 8px 12px,
gap: 6px,
),
md: (
font-size: 16px,
sm: (padding: 10px 16px,
gap: 8px,
),
lg: (
font-size: 18px,
md: (padding: 16px 20px,
gap: 12px,
),
lg: (padding: 18px 28px,
gap: 12px,
),
),
prefix: 'label'
prefix: '',
),
(vars: (xs: (font-size: 14px,
),
sm: (font-size: 16px,
),
md: (font-size: 18px,
),
lg: (font-size: 20px,
),
),
prefix: 'label',
),
(vars: (xs: (font-size: 16px,
),
sm: (font-size: 20px,
),
md: (font-size: 24px,
),
lg: (font-size: 32px,
),
),
prefix: 'icon',
),
);
$component: 'v-button';

// Outputs CSS property declarations using the CSS variables set by `size()`, with sm values as fallbacks.
// Usage: @include v-button.apply('') or @include v-button.apply('label')
@mixin apply($prefix: '') {
@include css-vars-map.apply($vars, $component, $prefix, $fallback-size: 'sm');
}

// Sets the CSS variables for a given size (xs, sm, md, lg) from $vars.
// Call this on the component element to drive properties() declarations elsewhere.
// eg: @include v-button-solid.size('md') generates:
// --v-button-solid-min-height: 56px;
// --v-button-solid-padding-inline: 20px;
// --v-button-solid-label-font-size: 18px;
// --v-button-solid-icon-font-size: 24px; etc.
@mixin size($size) {
@include css-vars-map.deep-get($vars, $size, 'v-button');
@include css-vars-map.deep-get($vars, $size, $component);
}

// Generates a BEM modifier class for each size in $vars.
// Call this on the component root to enable size variants via CSS class.
// eg: @include v-button-solid.size-classes() generates:
// &--size-xs { --v-button-solid-min-height: 32px; … }
// &--size-sm { --v-button-solid-min-height: 40px; … }
// &--size-md { --v-button-solid-min-height: 56px; … } etc.
@mixin size-classes() {
@include sizes($vars, $component);
}
4 changes: 2 additions & 2 deletions app/assets/scss/functions/_list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
$piece: list.nth($pieces, $i);

@if meta.type-of($piece) == list {
$result: string.unquote("#{$result}#{$glue}#{implode($piece, $glue)}");
} @else {
$result: string.unquote("#{$result}#{$glue}#{list-implode($piece, $glue)}");
} @else if $piece != null and $piece != '' {
$result: string.unquote("#{$result}#{$glue}#{$piece}");
}
}
Expand Down
39 changes: 36 additions & 3 deletions app/assets/scss/mixins/_css-vars-map.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@use 'sass:map';
@use '../functions/list' as *;
@use 'assets/scss/functions/list';

// Get CSS variables from a map
// Parameters:
Expand All @@ -26,7 +26,7 @@
// }
@mixin get($map, $name) {
@each $key, $value in $map {
--#{list-implode(($name, $key), '-')}: #{$value};
--#{list.list-implode(($name, $key), '-')}: #{$value};
}
}

Expand Down Expand Up @@ -68,11 +68,44 @@
// --example-margin: 8px;
// --example-label-padding: 40px;
// }
// Outputs CSS property declarations using the CSS variables set by `deep-get()`, with fallback values.
// Parameters:
// - $map: The map containing nested variables (same structure as deep-get)
// - $name: The base component name used for CSS variable names
// - $prefix: The entry prefix to target ('' for root, 'label' for label entries, etc.)
// - $fallback-size: The size key to use as fallback values
//
// Example usage:
// .example-component {
// @include css-vars-map.apply($vars, 'example-component', 'label', 'sm');
// }
//
// This will generate:
// .example-component {
// font-size: var(--example-component-label-font-size, 14px);
// padding: var(--example-component-label-padding, 4px 8px);
// }
@mixin apply($map, $name, $prefix: '', $fallback-size: 'sm') {
$var-prefix: list.list-implode(($name, $prefix), '-');

@each $entry in $map {
@if map.get($entry, 'prefix') == $prefix {
$entry-vars: map.get($entry, 'vars');

@if map.has-key($entry-vars, $fallback-size) {
@each $property, $default-value in map.get($entry-vars, $fallback-size) {
#{$property}: var(--#{$var-prefix}-#{$property}, #{$default-value});
}
}
}
}
}

@mixin deep-get($map, $key, $name) {
@each $value in $map {
@if map.has-key($value, 'vars') {
$map: map.get($value, 'vars');
$prefix: list-implode(($name, map.get($value, 'prefix')), '-');
$prefix: list.list-implode(($name, map.get($value, 'prefix')), '-');

@if map.has-key($map, $key) {
$key-vars: map.get($map, $key);
Expand Down
44 changes: 31 additions & 13 deletions app/components/VButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import type { RouteLocationRaw } from 'vue-router'
import { NuxtLink } from '#components'
import type { Theme } from '~~/types/theme'

export const vButtonVariant = ['outlined'] as const
export type VButtonVariant = (typeof vButtonVariant)[number]

export const vButtonSizes = ['xs', 'sm', 'md', 'lg'] as const
export type VButtonSize = (typeof vButtonSizes)[number]

Expand All @@ -19,6 +22,7 @@ export const vButtonProps = {
disabled: Boolean,
size: [String, Boolean] as PropType<VButtonSize | false>,
theme: [String, Boolean] as PropType<Theme | false>,
variant: [String, Boolean] as PropType<VButtonVariant | false>,
}

export default defineComponent({
Expand Down Expand Up @@ -51,6 +55,7 @@ export default defineComponent({
hasIcon.value && $style['root--has-icon'],
props.disabled && $style['root--disabled'],
typeof props.size === 'string' && $style[`root--size-${props.size}`],
typeof props.variant === 'string' && $style[`root--variant-${props.variant}`],
themeClass.value,
]
})
Expand Down Expand Up @@ -78,6 +83,7 @@ export default defineComponent({
>
<slot
name="icon"
:item-class="[$style.icon, iconClass]"
>
<VIcon
v-if="iconName"
Expand All @@ -96,24 +102,20 @@ export default defineComponent({

<style lang="scss" module>
@use 'assets/scss/components/v-button';
@use 'assets/scss/mixins/theme' as *;
@use 'assets/scss/mixins/sizes' as *;
@use 'assets/scss/components/v-button-outlined';

.root {
display: var(--v-button-display, inline-flex);
align-items: var(--v-button-align-items, center);
justify-content: var(--v-button-justify-content, center);
padding: var(--v-button-padding, initial);
border: var(--v-button-border, initial);
background-color: var(--v-button-background-color, initial);
font-family: var(--v-button-font-family, inherit);
text-decoration: var(--v-button-text-decoration, none);
user-select: none;

@include theme-variants('button');
@include sizes(v-button.$vars, 'v-button');
@include theme-variants('button-');

// Default styles
@include v-button.apply;
@include v-button.size-classes;

// PROPS STYLE
&--icon-last {
flex-direction: row-reverse;
}
Expand All @@ -139,21 +141,37 @@ export default defineComponent({
// remove the user agent style, but without specificity (i.e. :where()) for overriding it easily
:where(button#{&}){
text-align: inherit;
user-select: none;
}
}

&--variant-outlined {
@include v-button-outlined.apply;
}

@include v-button-outlined.size-classes;
}

// be aware than all nested svg are styled
.root svg,
.icon {
flex-shrink: var(--v-button-icon-flex-shrink, 0);
color: var(--v-button-icon-color, currentColor);

@include v-button.apply('icon');

.root--variant-outlined & {
@include v-button-outlined.apply('icon')
}
}

.label {
overflow: var(--v-button-label-overflow, hidden);
font-size: var(--v-button-label-font-size);
text-overflow: var(--v-button-label-text-overflow, ellipsis);
white-space: var(--v-button-label-white-space, nowrap);

@include v-button.apply('label');

.root--variant-outlined & {
@include v-button-outlined.apply('label')
}
}
</style>
Loading
Loading