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
16 changes: 16 additions & 0 deletions docs/content/docs/2.components/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,22 @@ props:
---
::

### Variant

WIP
Use the `variant` prop to change the style of the progress. May be `linear` (default) or `circular`.

::component-code
---
external:
- modelValue
- variant
props:
modelValue: 50
variant: 'circular'
---
::

### 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.
Expand Down
27 changes: 23 additions & 4 deletions playgrounds/nuxt/app/pages/components/progress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand All @@ -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)
Expand Down Expand Up @@ -46,22 +48,39 @@ onMounted(() => {
<USelect v-model="attrs.size" :items="sizes" multiple />
<USelect v-model="attrs.animation" :items="animations" multiple />
<USelect v-model="orientation" :items="orientations" />
<USelect v-model="variant" :items="variants" />
</Navbar>

<Matrix
v-slot="props"
:attrs="attrs"
:container-props="{ 'data-orientation': orientation }"
container-class="gap-4 data-[orientation=horizontal]:w-48 data-[orientation=vertical]:h-48 data-[orientation=vertical]:flex-row"
:container-props="{ 'data-orientation': orientation, 'data-variant': variant }"
container-class="gap-4 data-[orientation=horizontal]:data-[variant=linear]:w-48 data-[orientation=vertical]:data-[variant=linear]:h-48 data-[orientation=vertical]:flex-row"
>
<UProgress :orientation="orientation" v-bind="props" />
<UProgress v-model="value2" :max="max" status :orientation="orientation" v-bind="props" />
<UProgress :orientation="orientation" :variant="variant" v-bind="props" inverted />
<UProgress
v-model="value2"
:max="max"
status
:orientation="orientation"
:variant="variant"
v-bind="props"
/>
<UProgress
v-model="value2"
:max="max"
status
inverted
:orientation="orientation"
:variant="variant"
v-bind="props"
/>
<UProgress
v-model="value2"
:max="max"
:thickness="5"
:orientation="orientation"
:variant="variant"
v-bind="props"
/>
</Matrix>
Expand Down
71 changes: 57 additions & 14 deletions src/runtime/components/Progress.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export interface ProgressProps extends Pick<ProgressRootProps, 'getValueLabel' |
* @defaultValue 'carousel'
*/
animation?: Progress['variants']['animation']
/**
* The progress bar variant
* @defaultValue 'linear'
*/
variant?: Progress['variants']['variant']
thickness?: 'auto' | number
class?: any
ui?: Progress['slots']
}
Expand Down Expand Up @@ -65,7 +71,9 @@ import { tv } from '../utils/tv'
const _props = withDefaults(defineProps<ProgressProps>(), {
inverted: false,
modelValue: null,
orientation: 'horizontal'
orientation: 'horizontal',
variant: 'linear',
thickness: 'auto'
})
const emits = defineEmits<ProgressEmits>()
const slots = defineSlots<ProgressSlots>()
Expand Down Expand Up @@ -109,24 +117,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 }
})
Expand Down Expand Up @@ -167,20 +195,35 @@ 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
}))
</script>

<template>
<Primitive :as="props.as" :data-orientation="props.orientation" data-slot="root" :class="ui.root({ class: [props.ui?.root, props.class] })">
<div v-if="!isIndeterminate && (props.status || !!slots.status)" data-slot="status" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
<div v-if="!isIndeterminate && (props.status || !!slots.status) && props.variant === 'linear'" data-slot="status" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
<slot name="status" :percent="percent">
{{ percent }}%
</slot>
</div>

<ProgressRoot v-bind="rootProps" :max="realMax" data-slot="base" :class="ui.base({ class: props.ui?.base })" style="transform: translateZ(0)">
<ProgressIndicator data-slot="indicator" :class="ui.indicator({ class: props.ui?.indicator })" :style="indicatorStyle" />
<ProgressRoot v-bind="rootProps" :max="realMax" data-slot="base" :class="ui.base({ class: props.ui?.base })" :style="['transform: translateZ(0)', thicknessStyle]">
<template v-if="props.variant === 'circular'">
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" data-slot="track" :class="ui.track({ class: props.ui?.track })" />
<ProgressIndicator as-child>
<circle cx="50" cy="50" pathLength="100" :class="ui.indicator({ class: props.ui?.indicator })" :style="indicatorStyle" />
</ProgressIndicator>
</svg>
<div v-if="!isIndeterminate && (props.status || !!slots.status)" data-slot="status" :class="ui.status({ class: props.ui?.status })" :style="statusStyle">
<slot name="status" :percent="percent">
{{ percent }}%
</slot>
</div>
</template>

<ProgressIndicator v-else-if="props.variant === 'linear'" data-slot="indicator" :class="ui.indicator({ class: props.ui?.indicator })" :style="indicatorStyle" />
</ProgressRoot>

<div v-if="hasSteps" data-slot="steps" :class="ui.steps({ class: props.ui?.steps })">
Expand Down
60 changes: 60 additions & 0 deletions src/runtime/keyframes.css
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,66 @@
}
}

@keyframes circular-rotate {
to {
transform: rotate(360deg);
}
}

@keyframes circular-rotate-ccw {
from {
transform: scaleX(-1) rotate(0deg);
}
to {
transform: scaleX(-1) rotate(360deg);
}
}

@keyframes circular-swing {
0%,
100% {
transform: rotate(360deg);
}

50% {
transform: rotate(-45deg);
}
}

@keyframes circular-elastic {
0% {
stroke-dasharray: 1, 100;
stroke-dashoffset: 0;
}

50% {
stroke-dasharray: 75, 100;
stroke-dashoffset: -12.4;
}

100% {
stroke-dasharray: 75, 100;
stroke-dashoffset: -100;
}
}

@keyframes circular-elastic-inverse {
0% {
stroke-dasharray: 1, 0;
stroke-dashoffset: 0;
}

50% {
stroke-dasharray: 75, 100;
stroke-dashoffset: 12.4;
}

100% {
stroke-dasharray: 75, 100;
stroke-dashoffset: 100;
}
}

@keyframes marquee {
from {
transform: translate3d(0, 0, 0);
Expand Down
Loading
Loading