diff --git a/docs/content/docs/2.components/progress.md b/docs/content/docs/2.components/progress.md index f0d88221b7..6bf4d5b790 100644 --- a/docs/content/docs/2.components/progress.md +++ b/docs/content/docs/2.components/progress.md @@ -23,6 +23,46 @@ props: --- :: +### Variant + +Use the `variant` prop to change the style of the Progress. Defaults to `linear`. + +::component-code +--- +ignore: + - class +external: + - modelValue +props: + modelValue: 50 + variant: circular + class: 'justify-center' +--- +:: + +::note +The `circular` variant is rendered as an SVG, so the `track` slot is used for the background circle instead of the `base` background. +:: + +### Thickness + +Use the `thickness` prop to change the stroke width of the `circular` variant, in pixels. Defaults to `auto`, which derives the thickness from the [`size`](#size) prop. + +::component-code +--- +ignore: + - class + - variant +external: + - modelValue +props: + modelValue: 50 + variant: circular + thickness: 4 + class: 'justify-center' +--- +:: + ### Max Use the `max` prop to set the maximum value of the Progress. @@ -60,43 +100,70 @@ props: ### Status Use the `status` prop to display the current Progress value above the bar. +With the `circular` variant, the status is displayed in the center of the circle instead. ::component-code --- +ignore: + - class external: - modelValue props: modelValue: 50 + variant: linear status: true + class: 'justify-center' --- :: +Use the `#status` slot to customize the content, it receives the current `percent` value. + +```vue + +``` + ### Indeterminate -When no `v-model` is set or the value is `null`, the Progress becomes _indeterminate_. The progress bar is animated as a `carousel`, but you can change it using the [`animation`](#animation) prop. +When no `v-model` is set or the value is `null`, the Progress becomes _indeterminate_. The progress bar is animated as a `carousel`, but you can change it using the [`animation`](#animation) prop. \ +The `circular` variant is indeterminate under the same conditions, which makes it suitable as a loading spinner. ::component-code --- +ignore: + - class external: - modelValue props: modelValue: null + variant: linear + class: 'justify-center' --- :: ### Animation -Use the `animation` prop to change the animation of the Progress to an inverse carousel, a swinging bar or an elastic bar. Defaults to `carousel`. +Use the `animation` prop to change the animation of the Progress to an inverse carousel, a swinging bar or an elastic bar. Defaults to `carousel`. \ +Each animation has a `circular` equivalent. ::component-code --- +ignore: + - class props: animation: swing + variant: linear + class: 'justify-center' --- :: ::tip -The animation is automatically disabled when the user prefers reduced motion, the indeterminate bar is displayed as a full width pulse instead. +With the `linear` variant, the animation is automatically disabled when the user prefers reduced motion, the indeterminate bar is displayed as a full width pulse instead. :: ### Orientation @@ -113,37 +180,50 @@ props: --- :: +::note +The `circular` variant is always a circle, so the `orientation` prop only controls the axis along which the [steps](#max) are laid out next to it. +:: + ### Color -Use the `color` prop to change the color of the Slider. +Use the `color` prop to change the color of the Progress. ::component-code --- props: color: neutral + variant: linear --- :: ### Size -Use the `size` prop to change the size of the Slider. +Use the `size` prop to change the size of the Progress. With the `circular` variant, the `size` prop controls the diameter of the circle and the default stroke width, which you can override with the [`thickness`](#thickness) prop. ::component-code --- +ignore: + - class props: size: xl + variant: linear + class: 'justify-center' --- :: ### Inverted -Use the `inverted` prop to visually invert the Progress. +Use the `inverted` prop to visually invert the Progress. With the `circular` variant, this reverses the direction in which the circle is filled. ::component-code --- +ignore: + - class props: inverted: true + variant: linear modelValue: 25 + class: 'justify-center' --- :: diff --git a/playgrounds/nuxt/app/pages/components/progress.vue b/playgrounds/nuxt/app/pages/components/progress.vue index 50a4e52c1c..4aeaf6bae7 100644 --- a/playgrounds/nuxt/app/pages/components/progress.vue +++ b/playgrounds/nuxt/app/pages/components/progress.vue @@ -6,6 +6,7 @@ const colors = Object.keys(theme.variants.color) const sizes = Object.keys(theme.variants.size) const animations = Object.keys(theme.variants.animation) const orientations = Object.keys(theme.variants.orientation) +const variants = Object.keys(theme.variants.variant) const attrs = reactive({ color: [theme.defaultVariants.color], @@ -14,6 +15,7 @@ const attrs = reactive({ }) const orientation = ref('horizontal' as keyof typeof theme.variants.orientation) +const variant = ref('linear' as keyof typeof theme.variants.variant) const value1 = ref(0) const value2 = ref(0) @@ -46,22 +48,39 @@ onMounted(() => { + - - + + + diff --git a/src/runtime/components/Progress.vue b/src/runtime/components/Progress.vue index 5180665048..d28df47657 100644 --- a/src/runtime/components/Progress.vue +++ b/src/runtime/components/Progress.vue @@ -29,7 +29,7 @@ export interface ProgressProps extends Pick(), { inverted: false, modelValue: null, - orientation: 'horizontal' + orientation: 'horizontal', + variant: 'linear', + thickness: 'auto' }) const emits = defineEmits() const slots = defineSlots() @@ -109,24 +122,44 @@ const indicatorStyle = computed(() => { return } - if (props.orientation === 'vertical') { - return { - transform: `translateY(${props.inverted ? '' : '-'}${100 - percent.value}%)` - } - } else { - if (dir.value === 'rtl') { + if (props.variant === 'linear') { + if (props.orientation === 'vertical') { return { - transform: `translateX(${props.inverted ? '-' : ''}${100 - percent.value}%)` + transform: `translateY(${props.inverted ? '' : '-'}${100 - percent.value}%)` } } else { - return { - transform: `translateX(${props.inverted ? '' : '-'}${100 - percent.value}%)` + if (dir.value === 'rtl') { + return { + transform: `translateX(${props.inverted ? '-' : ''}${100 - percent.value}%)` + } + } else { + return { + transform: `translateX(${props.inverted ? '' : '-'}${100 - percent.value}%)` + } } } + } else { + const strokeDasharray = `${percent.value ?? 25}, 100` + + return { + 'stroke-dasharray': strokeDasharray + } + } +}) + +const thicknessStyle = computed(() => { + if (props.variant === 'linear') return + + if (props.thickness === 'auto') return + + return { + '--ui-progress-thickness': `${props.thickness}px` } }) const statusStyle = computed(() => { + if (props.variant === 'circular') return undefined + const value = `${Math.max(percent.value ?? 0, 0)}%` return props.orientation === 'vertical' ? { height: value } : { width: value } }) @@ -167,20 +200,43 @@ const ui = computed(() => tv({ extend: theme, ...(appConfig.ui?.progress || {}) size: props.size, color: props.color, orientation: props.orientation, - inverted: props.inverted + inverted: props.inverted, + variant: props.variant, + thickness: props.thickness === 'auto' ? 'auto' : undefined }))