Skip to content
Commits on Source (28)
......@@ -6,7 +6,7 @@ module.exports = {
rules: {
'import/no-extraneous-dependencies': 'off',
'import/no-relative-packages': 'off',
'@gitlab/tailwind': 'error',
'@gitlab/tailwind-no-interpolation': 'error',
'no-restricted-imports': [
'error',
{
......@@ -95,7 +95,7 @@ module.exports = {
],
},
],
'@gitlab/tailwind': 'off',
'@gitlab/tailwind-no-interpolation': 'off',
},
},
{
......
......@@ -62,7 +62,7 @@ secret_detection:
needs: []
.playwright:
image: mcr.microsoft.com/playwright:v1.47.2-jammy
image: mcr.microsoft.com/playwright:v1.48.0-jammy
.node:
image: node:20.18.0-bookworm
......@@ -312,9 +312,7 @@ bootstrap-vue:
- yarn run test
bootstrap-vue_vue3:
extends: [bootstrap-vue]
variables:
USE_VUE3: '1'
extends: [bootstrap-vue, .use_vue3]
.unit_tests:
extends: [.node, .yarn_install]
......
# [97.0.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v96.3.0...v97.0.0) (2024-10-16)
### Features
* migrate GlProgressBar component ([c58a6c8](https://gitlab.com/gitlab-org/gitlab-ui/commit/c58a6c8197388cd97e44c7b87a6ec87e5bf23b4a))
### BREAKING CHANGES
* Remove GlProgressBar's `animated`, `precision`,
`showValue`, `showProgress`, `striped` props, and functionality to use
the default slot.
* Migrate GlProgressBar component so it does not depend
on Bootstrap Vue's BProgress and BProgressBar components anymore
* Remove Bootstrap Vue's BProgress, BProgressBar, styling, and docs
* Include documentation for GlProgressBar
* Update stories to include `height` and `max`
* Update toast docs to remove mention of progress bar styling
# [96.3.0](https://gitlab.com/gitlab-org/gitlab-ui/compare/v96.2.0...v96.3.0) (2024-10-11)
......
{
"name": "@gitlab/ui",
"version": "96.3.0",
"version": "97.0.0",
"description": "GitLab UI Components",
"license": "MIT",
"main": "dist/index.js",
......@@ -107,13 +107,13 @@
},
"devDependencies": {
"@arkweid/lefthook": "0.7.7",
"@babel/core": "^7.25.7",
"@babel/core": "^7.25.8",
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6",
"@babel/plugin-proposal-optional-chaining": "^7.21.0",
"@babel/preset-env": "^7.25.7",
"@babel/preset-env": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@cypress/grep": "^4.0.1",
"@gitlab/eslint-plugin": "20.3.0",
"@gitlab/eslint-plugin": "20.4.1",
"@gitlab/fonts": "^1.3.0",
"@gitlab/stylelint-config": "6.2.2",
"@gitlab/svgs": "3.117.0",
......@@ -157,7 +157,7 @@
"esbuild": "^0.18.0",
"eslint": "8.57.1",
"eslint-import-resolver-jest": "3.0.2",
"eslint-plugin-cypress": "3.5.0",
"eslint-plugin-cypress": "3.6.0",
"eslint-plugin-storybook": "0.9.0",
"gitlab-api-async-iterator": "^1.3.1",
"glob": "10.3.3",
......@@ -174,8 +174,8 @@
"module-alias": "^2.2.2",
"npm-run-all": "^4.1.5",
"pikaday": "^1.8.0",
"playwright": "^1.47.2",
"playwright-core": "^1.47.2",
"playwright": "^1.48.0",
"playwright-core": "^1.48.0",
"plop": "^2.5.4",
"postcss": "8.4.28",
"postcss-loader": "^7.0.2",
......
......@@ -156,7 +156,7 @@ export default {
}, []);
},
variantClass() {
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return `gl-alert-${this.variant}`;
},
},
......
......@@ -72,17 +72,17 @@ export default {
computed: {
sizeClasses() {
if (isNumber(this.size)) {
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return `gl-avatar-s${this.size}`;
}
const { default: defaultSize, ...nonDefaultSizes } = this.size;
return [
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
`gl-avatar-s${defaultSize || avatarSizeOptions[1]}`,
...Object.entries(nonDefaultSizes).map(
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
([breakpoint, size]) => `gl-${breakpoint}-avatar-s${size}`
),
];
......@@ -96,7 +96,7 @@ export default {
* Gets the remainder after dividing the 'entityId' by the number of available backgrounds.
*/
const type = (this.entityId % IDENTICON_BG_COUNT) + 1;
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return `gl-avatar-identicon-bg${type}`;
},
identiconText() {
......
......@@ -245,7 +245,7 @@ export default {
'gl-datepicker',
'd-inline-block',
'gl-w-full',
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
`gl-form-input-${this.computedWidth}`,
];
},
......
......@@ -62,7 +62,7 @@ export default {
return Boolean(this.$slots.footer);
},
variantClass() {
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return `gl-drawer-${this.variant}`;
},
},
......
......@@ -6,6 +6,8 @@ import GlLink from '../../link/link.vue';
import GlFormInput from '../form_input/form_input.vue';
import GlFormTextarea from '../form_textarea/form_textarea.vue';
import BVueReadme from '../../../../vendor/bootstrap-vue/src/components/form-group/README.md';
import BVueReadmeLayout from '../../../../vendor/bootstrap-vue/src/components/layout/README.md';
import BVueReadmeSettings from '../../../../vendor/bootstrap-vue/docs/markdown/reference/settings/README.md';
import readme from './form_group.md';
import GlFormGroup from './form_group.vue';
......@@ -183,7 +185,7 @@ export default {
component: GlFormGroup,
parameters: {
bootstrapComponent: 'b-form-group',
bootstrapDocs: BVueReadme,
bootstrapDocs: BVueReadme + BVueReadmeLayout + BVueReadmeSettings,
docs: {
description: {
component: readme,
......
......@@ -42,16 +42,16 @@ export default {
const { default: defaultWidth, ...nonDefaultWidths } = this.width;
return [
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
...(defaultWidth ? [`gl-form-input-${defaultWidth}`] : []),
...Object.entries(nonDefaultWidths).map(
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
([breakpoint, width]) => `gl-${breakpoint}-form-input-${width}`
),
];
}
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return [`gl-form-input-${this.width}`];
},
listeners() {
......
......@@ -34,16 +34,16 @@ export default {
const { default: defaultWidth, ...nonDefaultWidths } = this.width;
return [
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
...(defaultWidth ? [`gl-form-select-${defaultWidth}`] : []),
...Object.entries(nonDefaultWidths).map(
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
([breakpoint, width]) => `gl-${breakpoint}-form-select-${width}`
),
];
}
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
return [`gl-form-select-${this.width}`];
},
},
......
import { targetOptions } from '../../../utils/constants';
import BVueReadme from '../../../vendor/bootstrap-vue/src/components/link/README.md';
import BVueReadmeRouterLinks from '../../../vendor/bootstrap-vue/docs/markdown/reference/router-links/README.md';
import GlLink from './link.vue';
import readme from './link.md';
......@@ -50,7 +51,7 @@ export default {
component: GlLink,
parameters: {
bootstrapComponent: 'b-link',
bootstrapDocs: BVueReadme,
bootstrapDocs: BVueReadme + BVueReadmeRouterLinks,
docs: {
description: {
component: readme,
......
// variables
$modal-body-height: 80px;
$modal-body-line-height: 1.5;
$modal-box-shadow: 0 4px 16px $t-gray-a-24;
// mixins
@mixin gl-tmp-modal-btn-group-ml {
......@@ -38,7 +37,7 @@ body.modal-open {
.modal-content {
@apply gl-rounded-base;
@apply gl-border-0;
box-shadow: $modal-box-shadow;
@apply gl-shadow-lg;
background-color: var(--gl-background-color-overlap);
color: var(--gl-text-color-default);
......
The progress bar component can be used to show an amount of progress.
It comes in 4 variants and supports setting a custom maximum and height.
## Value
The `value` prop can be a Number or String. If not given, it will default to `0`.
## Variants
The following variants are available:
1. 'primary' (default)
2. 'success'
3. 'warning'
4. 'danger'
## Maximum
A custom maximum can be set with the `max` prop. If not given, it will default to `100`.
## Width and Height
The `GlProgressBar` will always expand to the maximum width of its parent container.
The height can be controlled with the `height` prop. The value should be a standard
CSS dimension (`px`, `rem`, `em`, etc.) and given as a String, e.g. `'20px'`.
.gl-progress-bar {
background-color: var(--gl-progress-bar-track-color);
.gl-progress {
@apply gl-flex-col;
@apply gl-justify-center;
@apply gl-w-full;
@apply gl-overflow-hidden;
@apply gl-text-center;
@apply gl-whitespace-nowrap;
transition: transform 0.6s $gl-easing-out-cubic;
transform-origin: left;
}
.bg-primary {
background-color: var(--gl-progress-bar-indicator-color-default) !important;
......
import { mount } from '@vue/test-utils';
import { progressBarVariantOptions } from '../../../utils/constants';
import ProgressBar from './progress_bar.vue';
describe('GlProgressBar', () => {
let wrapper;
const createWrapper = (propsData) => {
wrapper = mount(ProgressBar, {
propsData,
});
};
const findProgress = () => wrapper.find('[role="progressbar"]');
beforeEach(() => {
createWrapper();
});
describe('default', () => {
it('renders wrapper with expected classes and style', () => {
expect(wrapper.classes()).toMatchObject(['gl-progress-bar', 'progress']);
expect(wrapper.attributes('style')).toBeUndefined();
});
it('renders child with expected classes and attributes', () => {
const progress = findProgress();
expect(progress.classes()).toMatchObject(['gl-progress', 'bg-primary']);
expect(progress.attributes('style')).toBe('transform: scaleX(0);');
expect(progress.attributes('aria-valuemin')).toBe('0');
expect(progress.attributes('aria-valuemax')).toBe('100');
expect(progress.attributes('aria-valuenow')).toBe('0');
});
});
describe('value', () => {
it('sets style and attributes correctly when setting `value`', () => {
const value = 65.6;
createWrapper({ value });
const progress = findProgress();
const computedValue = parseFloat(value) / parseFloat(100);
expect(progress.attributes('style')).toBe(`transform: scaleX(${computedValue});`);
expect(progress.attributes('aria-valuenow')).toBe(`${parseFloat(value)}`);
});
});
describe('max', () => {
it('sets style and attributes correctly when using custom `max`', () => {
const value = 45.1;
const max = 75.5;
createWrapper({ value, max });
const progress = findProgress();
const computedValue = parseFloat(value) / parseFloat(max);
expect(progress.attributes('style')).toBe(`transform: scaleX(${computedValue});`);
expect(progress.attributes('aria-valuemax')).toBe(`${parseFloat(max)}`);
expect(progress.attributes('aria-valuenow')).toBe(`${parseFloat(value)}`);
});
});
describe('variant', () => {
it.each(Object.values(progressBarVariantOptions))(
'sets correct css class for variant %s',
(variant) => {
createWrapper({ variant });
expect(findProgress().classes()).toMatchObject(['gl-progress', `bg-${variant}`]);
}
);
});
describe('height', () => {
it('sets height correctly', () => {
createWrapper({ height: '5px' });
expect(wrapper.attributes('style')).toBe('height: 5px;');
});
});
});
import { progressBarVariantOptions } from '../../../utils/constants';
import BVueReadme from '../../../vendor/bootstrap-vue/src/components/progress/README.md';
import GlProgressBar from './progress_bar.vue';
const generateProps = ({ value = 30, variant = progressBarVariantOptions.primary } = {}) => ({
const generateProps = ({
value = 30,
variant = progressBarVariantOptions.primary,
height,
max = 100,
} = {}) => ({
value,
variant,
height,
max,
});
const Template = (args, { argTypes }) => ({
components: { GlProgressBar },
props: Object.keys(argTypes),
template: '<gl-progress-bar :value="value" :variant="variant" />',
template: '<gl-progress-bar :value="value" :variant="variant" :height="height" :max="max" />',
});
export const Default = Template.bind({});
......@@ -34,10 +40,6 @@ Variants.parameters = { controls: { disable: true } };
export default {
title: 'base/progress-bar',
component: GlProgressBar,
parameters: {
bootstrapDocs: BVueReadme,
bootstrapComponent: 'b-progress',
},
argTypes: {
variant: {
options: progressBarVariantOptions,
......
<script>
import { BProgress } from '../../../vendor/bootstrap-vue/src/components/progress/progress';
import { progressBarVariantOptions } from '../../../utils/constants';
import { toFloat } from '../../../utils/number_utils';
export default {
name: 'GlProgressBar',
components: {
BProgress,
props: {
value: {
type: [Number, String],
required: false,
default: 0,
},
variant: {
type: String,
required: false,
default: 'primary',
validator: (value) => Object.keys(progressBarVariantOptions).includes(value),
},
max: {
type: [Number, String],
required: false,
default: 100,
},
height: {
type: String,
required: false,
default: null,
},
},
computed: {
progressHeight() {
return { height: this.height };
},
computedValue() {
return toFloat(this.value, 0);
},
computedMax() {
const max = toFloat(this.max, 100);
return max > 0 ? max : 100;
},
progressBarStyles() {
return {
transform: `scaleX(${this.computedValue / this.computedMax})`,
};
},
classes() {
return ['gl-progress', `bg-${this.variant}`];
},
},
inheritAttrs: false,
};
</script>
<template>
<b-progress v-bind="$attrs" class="gl-progress-bar" />
<div class="gl-progress-bar progress" :style="progressHeight">
<div
:class="classes"
:style="progressBarStyles"
role="progressbar"
aria-valuemin="0"
:aria-valuemax="String(computedMax)"
:aria-valuenow="computedValue"
></div>
</div>
</template>
......@@ -3,6 +3,7 @@ import GlBadge from '../../badge/badge.vue';
import { badgeVariantOptions } from '../../../../utils/constants';
import GlTab from '../tab/tab.vue';
import BVueReadme from '../../../../vendor/bootstrap-vue/src/components/tabs/README.md';
import BVueReadmeSizeProps from '../../../../vendor/bootstrap-vue/docs/markdown/reference/size-props/README.md';
import GlScrollableTabs from './scrollable_tabs.vue';
import GlTabs from './tabs.vue';
import readme from './tabs.md';
......@@ -193,7 +194,7 @@ export default {
component: GlTabs,
subcomponents: { GlTab, GlScrollableTabs },
parameters: {
bootstrapDocs: BVueReadme,
bootstrapDocs: BVueReadme + BVueReadmeSizeProps,
bootstrapComponent: 'b-tabs',
docs: {
description: {
......
......@@ -44,7 +44,7 @@ function renderTitle(h, toast, options) {
}
function showToast(message, options = {}) {
// eslint-disable-next-line @gitlab/tailwind -- Not a CSS utility
// eslint-disable-next-line @gitlab/tailwind-no-interpolation -- Not a CSS utility
const id = `gl-toast-${toastsCount}`;
toastsCount += 1;
const hide = () => {
......