diff --git a/packages/components/progress/src/constants.ts b/packages/components/progress/src/constants.ts new file mode 100644 index 000000000..9a7509c68 --- /dev/null +++ b/packages/components/progress/src/constants.ts @@ -0,0 +1,3 @@ +export const PROGRESS_NAME = 'OkuProgress' +export const DEFAULT_MAX = 100 +export const INDICATOR_NAME = 'OkuProgressIndicator' diff --git a/packages/components/progress/src/index.ts b/packages/components/progress/src/index.ts index a20429a94..ddfd9380d 100644 --- a/packages/components/progress/src/index.ts +++ b/packages/components/progress/src/index.ts @@ -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' diff --git a/packages/components/progress/src/progress.ts b/packages/components/progress/src/progress.ts index 59cbf468d..c250d7509 100644 --- a/packages/components/progress/src/progress.ts +++ b/packages/components/progress/src/progress.ts @@ -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 | null; max: ComputedRef } +type ProgressContextValue = { + value: ComputedRef | null + max: ComputedRef +} + type ProgressElement = ElementType<'div'> -type ProgressState = 'indeterminate' | 'complete' | 'loading' interface ProgressProps { value?: number | null @@ -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(PROGRESS_NAME) @@ -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)) @@ -64,28 +73,41 @@ const Progress = defineComponent({ const { $el, newRef } = useRef() - 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, @@ -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, - required: false, - }, - }, - setup(props, { attrs, slots, expose }) { - const { scopeProgress } = props - const { - ...indicatorProps - } = attrs as ProgressIndicatorProps - - const { $el, newRef } = useRef() - - 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 -type _OkuProgressIndicatorProps = MergeProps type ProgressRef = RefElement -type ProgressIndicatorRef = RefElement -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 } diff --git a/packages/components/progress/src/progressIndicator.ts b/packages/components/progress/src/progressIndicator.ts new file mode 100644 index 000000000..8b2c1117c --- /dev/null +++ b/packages/components/progress/src/progressIndicator.ts @@ -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, + required: false, + }, + }, + setup(props, { attrs, slots, expose }) { + const { scopeProgress } = props + const { ...indicatorProps } = attrs as ProgressIndicatorProps + + const { $el, newRef } = useRef() + + 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 + +export { OkuProgressIndicator } + +export type { + ProgressIndicatorProps, + ProgressIndicatorElement, + ProgressIndicatorRef, +} diff --git a/packages/components/progress/src/utils.ts b/packages/components/progress/src/utils.ts new file mode 100644 index 000000000..e6317adbd --- /dev/null +++ b/packages/components/progress/src/utils.ts @@ -0,0 +1,51 @@ +import { DEFAULT_MAX, PROGRESS_NAME } from './constants' + +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 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\`.` +} + +function getProgressState( + maxValue: number, + value?: number | null, +): 'indeterminate' | 'complete' | 'loading' { + return value == null + ? 'indeterminate' + : value === maxValue + ? 'complete' + : 'loading' +} + +export { + getProgressState, + getInvalidValueError, + getInvalidMaxError, + isValidMaxNumber, + isValidValueNumber, + defaultGetValueLabel, + isNumber, +}