Skip to content
Merged
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
3 changes: 3 additions & 0 deletions packages/components/progress/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const PROGRESS_NAME = 'OkuProgress'
export const DEFAULT_MAX = 100
export const INDICATOR_NAME = 'OkuProgressIndicator'
21 changes: 9 additions & 12 deletions packages/components/progress/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
export {
OkuProgress,
OkuProgressIndicator,
createProgressScope,
} from './progress'
export { OkuProgress, createProgressScope } from './progress'

export type{
ProgressProps,
ProgressIndicatorProps,
ProgressElement,
ProgressIndicatorElement,
ProgressRef,
export type { ProgressProps, ProgressElement, ProgressRef } from './progress'

export { OkuProgressIndicator } from './progressIndicator'

export type {
ProgressIndicatorRef,
} from './progress'
ProgressIndicatorElement,
ProgressIndicatorProps,
} from './progressIndicator'
205 changes: 58 additions & 147 deletions packages/components/progress/src/progress.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import type { ElementType, MergeProps, PrimitiveProps, RefElement } from '@oku-ui/primitive'
import type { ElementType, MergeProps, RefElement } from '@oku-ui/primitive'
import { Primitive } from '@oku-ui/primitive'
import type { Scope } from '@oku-ui/provide'
import { createProvideScope } from '@oku-ui/provide'
import type { ComputedRef, PropType } from 'vue'
import { computed, defineComponent, h, toRefs } from 'vue'
import { useRef } from '@oku-ui/use-composable'
import {
defaultGetValueLabel,
getInvalidMaxError,
getInvalidValueError,
getProgressState,
isNumber,
isValidMaxNumber,
isValidValueNumber,
} from './utils'
import { DEFAULT_MAX, PROGRESS_NAME } from './constants'
import type { ProgressIndicatorProps } from '.'

// ---------- Progress ---------- //

type ProgressContextValue = { value: ComputedRef<number | null> | null; max: ComputedRef<number> }
type ProgressContextValue = {
value: ComputedRef<number | null> | null
max: ComputedRef<number>
}

type ProgressElement = ElementType<'div'>
type ProgressState = 'indeterminate' | 'complete' | 'loading'

interface ProgressProps {
value?: number | null
Expand All @@ -19,11 +33,8 @@ interface ProgressProps {
scopeProgress?: Scope
}

// ---constants---
const PROGRESS_NAME = 'Progress'
const DEFAULT_MAX = 100

const [createProgressContext, createProgressScope] = createProvideScope(PROGRESS_NAME)
const [createProgressContext, createProgressScope]
= createProvideScope(PROGRESS_NAME)

const [progressProvider, useProgressContext]
= createProgressContext<ProgressContextValue>(PROGRESS_NAME)
Expand Down Expand Up @@ -51,9 +62,7 @@ const Progress = defineComponent({
},
setup(props, { attrs, slots, expose }) {
const { value, max, getValueLabel, scopeProgress } = toRefs(props)
const {
...progressProps
} = attrs as ProgressElement
const { ...progressProps } = attrs as ProgressElement

// propstype check
if (max.value && !isValidMaxNumber(max.value))
Expand All @@ -64,28 +73,41 @@ const Progress = defineComponent({

const { $el, newRef } = useRef<HTMLDivElement>()

const maxProp = computed(() => isValidMaxNumber(max.value) ? max.value : DEFAULT_MAX)
const valueProp = computed(() => isValidValueNumber(value.value, maxProp.value) ? value.value : null)
const valueLabel = computed(() => isNumber(valueProp.value) ? getValueLabel.value(valueProp.value, maxProp.value) : undefined)

const originalReturn = () => h(
Primitive.div,
{
'aria-valuemax': maxProp.value,
'aria-valuemin': 0,
'aria-valuenow': isNumber(valueProp.value) ? valueProp.value : undefined,
'aria-valuetext': valueLabel.value,
'role': 'progressbar',
'data-state': computed(() => getProgressState(maxProp.value, valueProp.value)).value,
'data-value': valueProp.value ?? undefined,
'data-max': maxProp.value,
...progressProps,
'ref': newRef,
},
{
default: () => slots.default?.(),
},
const maxProp = computed(() =>
isValidMaxNumber(max.value) ? max.value : DEFAULT_MAX,
)
const valueProp = computed(() =>
isValidValueNumber(value.value, maxProp.value) ? value.value : null,
)
const valueLabel = computed(() =>
isNumber(valueProp.value)
? getValueLabel.value(valueProp.value, maxProp.value)
: undefined,
)

const originalReturn = () =>
h(
Primitive.div,
{
'aria-valuemax': maxProp.value,
'aria-valuemin': 0,
'aria-valuenow': isNumber(valueProp.value)
? valueProp.value
: undefined,
'aria-valuetext': valueLabel.value,
'role': 'progressbar',
'data-state': computed(() =>
getProgressState(maxProp.value, valueProp.value),
).value,
'data-value': valueProp.value ?? undefined,
'data-max': maxProp.value,
...progressProps,
'ref': newRef,
},
{
default: () => slots.default?.(),
},
)

expose({
inferRef: $el,
Expand All @@ -103,125 +125,14 @@ const Progress = defineComponent({
},
})

// ---function---

function defaultGetValueLabel(value: number, max: number) {
return `${Math.round((value / max) * 100)}%`
}

function isNumber(value: any): value is number {
return typeof value === 'number'
}

function isValidMaxNumber(max: any): max is number {
return (
isNumber(max)
&& !Number.isNaN(max)
&& max > 0
)
}

function isValidValueNumber(value: any, max: number): value is number {
return (
isNumber(value)
&& !Number.isNaN(value)
&& value <= max
&& value >= 0
)
}

function getProgressState(maxValue: number, value?: number | null): ProgressState {
return value == null ? 'indeterminate' : value === maxValue ? 'complete' : 'loading'
}

function getInvalidMaxError(propValue: string) {
return `Invalid prop \`max\` of value \`${propValue}\` supplied to \`${PROGRESS_NAME}\`. Only numbers greater than 0 are valid max values. Defaulting to \`${DEFAULT_MAX}\`.`
}

function getInvalidValueError(propValue: string) {
return `Invalid prop \`value\` of value \`${propValue}\` supplied to \`${PROGRESS_NAME}\`. The \`value\` prop must be:
- a positive number
- less than the value passed to \`max\` (or ${DEFAULT_MAX} if no \`max\` prop is set)
- \`null\` if the progress is indeterminate.

Defaulting to \`null\`.`
}

// ---------- ProgressIndicator

// ---constants---

const INDICATOR_NAME = 'ProgressIndicator'

// ---component---
type ProgressIndicatorElement = ElementType<'div'>
interface ProgressIndicatorProps extends PrimitiveProps {
scopeProgress?: Scope
}

const ProgressIndicator = defineComponent({
name: INDICATOR_NAME,
inheritAttrs: true,
props: {
scopeProgress: {
type: Object as unknown as PropType<Scope>,
required: false,
},
},
setup(props, { attrs, slots, expose }) {
const { scopeProgress } = props
const {
...indicatorProps
} = attrs as ProgressIndicatorProps

const { $el, newRef } = useRef<HTMLDivElement>()

const context = useProgressContext(INDICATOR_NAME, scopeProgress)

expose({
inferRef: $el,
})

const originalReturn = () => h(
'div',
{
'data-state': getProgressState(context.value.max.value, context.value.value?.value),
'data-value': context.value.value?.value ?? undefined,
'data-max': context.value.max.value,
...indicatorProps,
'ref': newRef,
},
{
default: () => slots.default?.(),
})

return originalReturn as unknown as {
innerRef: ProgressIndicatorElement
}
},
})

// TODO: https://github.com/vuejs/core/pull/7444 after delete
type _OkuProgressProps = MergeProps<ProgressProps, ProgressIndicatorProps>
type _OkuProgressIndicatorProps = MergeProps<ProgressIndicatorProps, PrimitiveProps>

type ProgressRef = RefElement<typeof Progress>
type ProgressIndicatorRef = RefElement<typeof ProgressIndicator>

const OkuProgress = Progress as typeof Progress & (new () => { $props: _OkuProgressProps })
const OkuProgressIndicator = ProgressIndicator as typeof ProgressIndicator & (new () => { $props: _OkuProgressIndicatorProps })
const OkuProgress = Progress as typeof Progress &
(new () => { $props: _OkuProgressProps })

export {
createProgressScope,
OkuProgress,
OkuProgressIndicator,
}
export { createProgressScope, OkuProgress, useProgressContext }

export type {
ProgressProps,
ProgressIndicatorProps,
ProgressElement,
ProgressIndicatorElement,
ProgressRef,
ProgressIndicatorRef,
}
export type { ProgressProps, ProgressElement, ProgressRef }
83 changes: 83 additions & 0 deletions packages/components/progress/src/progressIndicator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import type { PropType } from 'vue'
import { defineComponent, h } from 'vue'
import type {
ElementType,
MergeProps,
PrimitiveProps,
RefElement,
} from '@oku-ui/primitive'
import type { Scope } from '@oku-ui/provide'
import { useRef } from '@oku-ui/use-composable'
import { getProgressState } from './utils'
import { useProgressContext } from './progress'
import { INDICATOR_NAME } from './constants'

// ---component---
type ProgressIndicatorElement = ElementType<'div'>

interface ProgressIndicatorProps extends PrimitiveProps {
scopeProgress?: Scope
}

const ProgressIndicator = defineComponent({
name: INDICATOR_NAME,
inheritAttrs: true,
props: {
scopeProgress: {
type: Object as unknown as PropType<Scope>,
required: false,
},
},
setup(props, { attrs, slots, expose }) {
const { scopeProgress } = props
const { ...indicatorProps } = attrs as ProgressIndicatorProps

const { $el, newRef } = useRef<HTMLDivElement>()

const context = useProgressContext(INDICATOR_NAME, scopeProgress)

expose({
inferRef: $el,
})

const originalReturn = () =>
h(
'div',
{
'data-state': getProgressState(
context.value.max.value,
context.value.value?.value,
),
'data-value': context.value.value?.value ?? undefined,
'data-max': context.value.max.value,
...indicatorProps,
'ref': newRef,
},
{
default: () => slots.default?.(),
},
)

return originalReturn as unknown as {
innerRef: ProgressIndicatorElement
}
},
})

type _OkuProgressIndicatorProps = MergeProps<
ProgressIndicatorProps,
PrimitiveProps
>

const OkuProgressIndicator = ProgressIndicator as typeof ProgressIndicator &
(new () => { $props: _OkuProgressIndicatorProps })

type ProgressIndicatorRef = RefElement<typeof ProgressIndicator>

export { OkuProgressIndicator }

export type {
ProgressIndicatorProps,
ProgressIndicatorElement,
ProgressIndicatorRef,
}
Loading