diff --git a/.stylelintrc.mjs b/.stylelintrc.mjs index d1376c798d..d7e57e064c 100644 --- a/.stylelintrc.mjs +++ b/.stylelintrc.mjs @@ -12,7 +12,10 @@ export default { rules: { // Disallow relative font units since we don't know the base font size in other apps 'unit-disallowed-list': ['rem', 'em'], - 'order/properties-alphabetical-order': true, + 'order/properties-order': [ + ['all'], + { 'unspecified': 'bottomAlphabetical' }, + ], '@kong/design-tokens/use-proper-token': true, '@stylistic/indentation': [2, { baseIndentLevel: 0 }], // Only allow @kong/design-tokens or `--kong-ui-*` CSS custom properties diff --git a/docs/components/button.md b/docs/components/button.md index c6fa9d94e0..056a42d808 100644 --- a/docs/components/button.md +++ b/docs/components/button.md @@ -13,18 +13,26 @@ and configuration options. ### appearance -The Button component can take 1 of 4 appearance values: +The Button component can take 1 of 5 appearance values: - `primary` - `secondary` - `tertiary` - `danger` +- `none` + +:::tip NOTE +Use `appearance="none"` to get an unstyled button, making it easier to customize from scratch. This removes the built-in styling but retains the button's functionality. + +When `appearance="none"` is set, the `size` prop only works if `icon` is `true`; otherwise, `size` has no effect. +:::
Primary Secondary Tertiary Danger + None
```html @@ -32,6 +40,7 @@ The Button component can take 1 of 4 appearance values: Secondary Tertiary Danger +None ``` ### size diff --git a/docs/vite.config.ts b/docs/vite.config.ts index e9f3eedd97..b0dcf72a18 100644 --- a/docs/vite.config.ts +++ b/docs/vite.config.ts @@ -26,6 +26,7 @@ export default defineConfig({ css: { preprocessorOptions: { scss: { + api: 'modern', // Inject the @kong/design-tokens SCSS variables, kongponents variables and mixins to make them available for all components. // This is not needed in host applications. additionalData: ` diff --git a/sandbox/pages/SandboxButton.vue b/sandbox/pages/SandboxButton.vue index 847b0a1c84..045e910236 100644 --- a/sandbox/pages/SandboxButton.vue +++ b/sandbox/pages/SandboxButton.vue @@ -13,7 +13,7 @@ title="appearance" >
@@ -51,6 +54,12 @@ > Danger + + None +
Disabled + + Disabled +
+ Still Primary @@ -188,6 +205,13 @@ > Danger + + None + Danger + + None + + + None + +
@@ -365,6 +403,10 @@ Danger + + None + +
@@ -392,6 +434,13 @@ Danger + + None + +
@@ -429,6 +478,13 @@ > + + +
@@ -452,6 +508,12 @@ > + + +
+ + + +
+ + + +
+ + Custom + + + Custom +
@@ -507,4 +599,23 @@ const test = () => { gap: $kui-space-50; } } + +/* Low-specificity styles should be able to override */ +.custom-button { + background-color: #42b883; + border-radius: 8px; + color: #fff; + font-size: 16px; + font-weight: 600; + padding: 8px 16px; + + &:hover:not(:disabled) { + background-color: #33a06f; + transition-duration: .2s; + } + + &:disabled { + opacity: 0.3; + } +} diff --git a/src/components/KButton/KButton.vue b/src/components/KButton/KButton.vue index a8cb98194f..7120fe3356 100644 --- a/src/components/KButton/KButton.vue +++ b/src/components/KButton/KButton.vue @@ -2,10 +2,12 @@ @@ -86,6 +88,24 @@ const buttonType = computed((): string => { return 'button' }) +const buttonAppearance = computed((): ButtonAppearance | [ButtonAppearance, string] => { + // If the appearance is invalid, output both to keep backwards compatibility + // in case some of the tests rely on the invalid appearance output + if (Object.values(ButtonAppearances).indexOf(props.appearance) === -1) { + return ['primary', props.appearance] + } + + return props.appearance +}) + +const buttonSize = computed((): ButtonSize | null => { + if (props.appearance === 'none' && !props.icon) { + return null + } + + return props.size +}) + /** * Strips falsy `disabled` attribute, so it does not fall onto native elements. * Vue 3 no longer removes attribute if the value is boolean false. Instead, it's set as attr="false". @@ -95,10 +115,14 @@ const buttonType = computed((): string => { const strippedAttrs = computed((): typeof attrs => { const modifiedAttrs = Object.assign({}, attrs) - if (props.to && typeof props.to === 'string') { - modifiedAttrs.href = props.to - } else if (props.to) { - modifiedAttrs.to = props.to + if (props.disabled) { + modifiedAttrs.href = null + } else { + if (props.to && typeof props.to === 'string') { + modifiedAttrs.href = props.to + } else if (props.to) { + modifiedAttrs.to = props.to + } } if (props.disabled !== undefined && props.disabled !== false) { @@ -110,6 +134,25 @@ const strippedAttrs = computed((): typeof attrs => { return modifiedAttrs }) +const stop = (event: Event) => { + event.preventDefault() + event.stopPropagation() +} + +const listeners = computed(() => { + if (!props.disabled || buttonType.value === 'button') { + return {} + } + + // Only prevent clicks on disabled links, emulating the same behavior as a disabled button + return { + clickCapture: stop, + dblclickCapture: stop, + mousedownCapture: stop, + mouseupCapture: stop, + } +}) + onMounted(() => { if (slots.icon) { console.warn('KButton: `icon` slot is deprecated. Please slot an icon into the `default` slot instead. See the migration guide for more details: https://kongponents.konghq.com/guide/migrating-to-version-9.html#kbutton') @@ -124,36 +167,9 @@ export default {