A design system in a box. hip-ui.tngl.io/docs/introduction
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

progress circle

+565
+196
apps/docs/src/components/progress-circle/index.tsx
··· 1 + import type { ProgressBarProps as AriaProgressBarProps } from "react-aria-components"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { Check } from "lucide-react"; 5 + import { use } from "react"; 6 + import { ProgressBar as ProgressBar } from "react-aria-components"; 7 + 8 + import type { Size, StyleXComponentProps } from "../theme/types"; 9 + 10 + import { SizeContext } from "../context"; 11 + import { Label } from "../label"; 12 + import { animationDuration } from "../theme/animations.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 15 + import { spacing } from "../theme/spacing.stylex"; 16 + 17 + const IndeterminateFillAnimation = stylex.keyframes({ 18 + from: { 19 + transform: "rotate(0deg)", 20 + }, 21 + to: { 22 + transform: "rotate(360deg)", 23 + }, 24 + }); 25 + 26 + const styles = stylex.create({ 27 + wrapper: { 28 + alignItems: "center", 29 + boxSizing: "border-box", 30 + display: "flex", 31 + flexDirection: "column", 32 + gap: spacing["2"], 33 + }, 34 + circleWrapper: { 35 + justifyContent: "center", 36 + position: "relative", 37 + 38 + "--progress-border-width": spacing["1"], 39 + "--progress-size": { 40 + ":is([data-size=sm] *)": spacing["4"], 41 + ":is([data-size=md] *)": spacing["8"], 42 + ":is([data-size=lg] *)": spacing["10"], 43 + }, 44 + }, 45 + track: { 46 + borderColor: uiColor.component1, 47 + borderRadius: radius.full, 48 + borderStyle: "solid", 49 + borderWidth: "var(--progress-border-width)", 50 + boxSizing: "border-box", 51 + height: "var(--progress-size)", 52 + width: "var(--progress-size)", 53 + }, 54 + fills: { 55 + height: "100%", 56 + left: 0, 57 + position: "absolute", 58 + top: 0, 59 + width: "100%", 60 + }, 61 + fillMask: { 62 + boxSizing: "border-box", 63 + height: "100%", 64 + overflow: "hidden", 65 + position: "absolute", 66 + transformOrigin: "100%", 67 + width: "50%", 68 + }, 69 + fillMask1: { 70 + transform: "rotate(0deg)", 71 + }, 72 + fillMask2: { 73 + transform: "rotate(180deg)", 74 + }, 75 + fillSubmask: { 76 + boxSizing: "border-box", 77 + height: "100%", 78 + overflow: "hidden", 79 + transformOrigin: "100%", 80 + transitionDuration: animationDuration.default, 81 + transitionProperty: "transform", 82 + transitionTimingFunction: "linear", 83 + width: "100%", 84 + }, 85 + fill: { 86 + borderColor: primaryColor.solid1, 87 + borderRadius: radius.full, 88 + borderStyle: "solid", 89 + borderWidth: "var(--progress-border-width)", 90 + boxSizing: "border-box", 91 + height: "var(--progress-size)", 92 + width: "var(--progress-size)", 93 + }, 94 + check: { 95 + left: "50%", 96 + position: "absolute", 97 + top: "50%", 98 + transform: "translate(-50%, -50%)", 99 + }, 100 + completed: { 101 + backgroundColor: primaryColor.solid1, 102 + }, 103 + indeterminateFillAnimation: { 104 + animationDuration: "1s", 105 + animationIterationCount: "infinite", 106 + animationName: IndeterminateFillAnimation, 107 + animationTimingFunction: "linear", 108 + }, 109 + }); 110 + 111 + export interface ProgressCircleProps 112 + extends StyleXComponentProps<Omit<AriaProgressBarProps, "children">> { 113 + label?: string; 114 + size?: Size; 115 + } 116 + 117 + export function ProgressCircle({ 118 + label, 119 + style, 120 + size: sizeProp, 121 + ...props 122 + }: ProgressCircleProps) { 123 + const size = sizeProp || use(SizeContext); 124 + 125 + return ( 126 + <ProgressBar 127 + {...props} 128 + data-size={size} 129 + {...stylex.props(styles.wrapper, style)} 130 + > 131 + {({ percentage = 0, isIndeterminate }) => { 132 + console.log({ percentage }, Math.min(0.5, percentage / 100)); 133 + const rotateFirstHalf = Math.min(0.5, percentage / 100) * 360 - 180; 134 + const rotateSecondHalf = (1 - Math.max(0.5, percentage / 100)) * -360; 135 + 136 + return ( 137 + <> 138 + <div {...stylex.props(styles.circleWrapper)}> 139 + <div 140 + {...stylex.props( 141 + styles.track, 142 + percentage === 100 && styles.completed, 143 + )} 144 + /> 145 + 146 + {isIndeterminate ? ( 147 + <div {...stylex.props(styles.fills)}> 148 + <div 149 + {...stylex.props( 150 + styles.fillMask, 151 + styles.fillMask1, 152 + styles.indeterminateFillAnimation, 153 + )} 154 + > 155 + <div 156 + {...stylex.props(styles.fillSubmask)} 157 + style={{ transform: "rotate(90deg)" }} 158 + > 159 + <div {...stylex.props(styles.fill)} /> 160 + </div> 161 + </div> 162 + </div> 163 + ) : ( 164 + <div {...stylex.props(styles.fills)}> 165 + <div {...stylex.props(styles.fillMask, styles.fillMask1)}> 166 + <div 167 + {...stylex.props(styles.fillSubmask)} 168 + style={{ 169 + transform: `rotate(${rotateSecondHalf.toString()}deg)`, 170 + }} 171 + > 172 + <div {...stylex.props(styles.fill)} /> 173 + </div> 174 + </div> 175 + <div {...stylex.props(styles.fillMask, styles.fillMask2)}> 176 + <div 177 + {...stylex.props(styles.fillSubmask)} 178 + style={{ 179 + transform: `rotate(${rotateFirstHalf.toString()}deg)`, 180 + }} 181 + > 182 + <div {...stylex.props(styles.fill)} /> 183 + </div> 184 + </div> 185 + </div> 186 + )} 187 + 188 + {percentage === 100 && <Check {...stylex.props(styles.check)} />} 189 + </div> 190 + {label && <Label>{label}</Label>} 191 + </> 192 + ); 193 + }} 194 + </ProgressBar> 195 + ); 196 + }
+55
apps/docs/src/docs/components/progress-circle.mdx
··· 1 + --- 2 + title: Progress Circle 3 + description: A circular progress indicator component for showing completion status. 4 + --- 5 + 6 + import { PropDocs } from '../../lib/PropDocs' 7 + import { Example } from '../../lib/Example' 8 + import { Basic } from '../../examples/progress-circle/basic' 9 + import { WithLabel } from '../../examples/progress-circle/with-label' 10 + import { Indeterminate } from '../../examples/progress-circle/indeterminate' 11 + import { Sizes } from '../../examples/progress-circle/sizes' 12 + 13 + <Example src={Basic} /> 14 + 15 + ## Installation 16 + 17 + Run the following command to add the progress circle component to your project. 18 + 19 + ```bash 20 + pnpm hip install progress-circle 21 + ``` 22 + 23 + ## Props 24 + 25 + This component is built using the [React Aria ProgressBar](https://react-spectrum.adobe.com/react-aria/ProgressBar.html). 26 + 27 + <PropDocs components={["ProgressCircle"]} /> 28 + 29 + ## Features 30 + 31 + ### With Label 32 + 33 + The label is optional and can be omitted for a minimal progress indicator. 34 + 35 + <Example src={WithLabel} /> 36 + 37 + ### Indeterminate 38 + 39 + If your progress is not known, you can omit the `value` prop and provide the `isIndeterminate` prop to true. 40 + This is commonly known as a `Spinner`. 41 + 42 + <Example src={Indeterminate} /> 43 + 44 + ### Sizes 45 + 46 + The progress circle supports three different sizes. 47 + 48 + <Example src={Sizes} /> 49 + 50 + ## Related Components 51 + 52 + - [Progress Bar](/docs/components/progress-bar) - For linear progress indicators 53 + - [Meter](/docs/components/meter) - For displaying values within a range 54 + - [Label](/docs/components/label) - For form labels 55 +
+36
apps/docs/src/examples/progress-circle/basic.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + import { useEffect, useState } from "react"; 3 + 4 + import { ProgressCircle } from "../../components/progress-circle"; 5 + 6 + const styles = stylex.create({ 7 + wrapper: { 8 + display: "flex", 9 + flexDirection: "column", 10 + gap: "8px", 11 + }, 12 + }); 13 + 14 + export function Basic() { 15 + const [value, setValue] = useState(0); 16 + 17 + useEffect(() => { 18 + const interval = setInterval(() => { 19 + setValue((prev) => { 20 + const newVal = Math.min(100, prev + 10 * Math.random()); 21 + 22 + if (newVal === 100) { 23 + clearInterval(interval); 24 + } 25 + 26 + return newVal; 27 + }); 28 + }, 500); 29 + }, []); 30 + 31 + return ( 32 + <div {...stylex.props(styles.wrapper)}> 33 + <ProgressCircle value={value} /> 34 + </div> 35 + ); 36 + }
+20
apps/docs/src/examples/progress-circle/indeterminate.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + 3 + import { ProgressCircle } from "../../components/progress-circle"; 4 + 5 + const styles = stylex.create({ 6 + wrapper: { 7 + display: "flex", 8 + flexDirection: "column", 9 + gap: "16px", 10 + }, 11 + }); 12 + 13 + export function Indeterminate() { 14 + return ( 15 + <div {...stylex.props(styles.wrapper)}> 16 + <ProgressCircle isIndeterminate label="Loading..." /> 17 + </div> 18 + ); 19 + } 20 +
+22
apps/docs/src/examples/progress-circle/sizes.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + 3 + import { ProgressCircle } from "../../components/progress-circle"; 4 + 5 + const styles = stylex.create({ 6 + wrapper: { 7 + display: "flex", 8 + flexDirection: "column", 9 + gap: "16px", 10 + }, 11 + }); 12 + 13 + export function Sizes() { 14 + return ( 15 + <div {...stylex.props(styles.wrapper)}> 16 + <ProgressCircle value={75} label="Small" size="sm" /> 17 + <ProgressCircle value={75} label="Medium" size="md" /> 18 + <ProgressCircle value={75} label="Large" size="lg" /> 19 + </div> 20 + ); 21 + } 22 +
+21
apps/docs/src/examples/progress-circle/with-label.tsx
··· 1 + import * as stylex from "@stylexjs/stylex"; 2 + 3 + import { ProgressCircle } from "../../components/progress-circle"; 4 + 5 + const styles = stylex.create({ 6 + wrapper: { 7 + display: "flex", 8 + flexDirection: "column", 9 + gap: "16px", 10 + }, 11 + }); 12 + 13 + export function WithLabel() { 14 + return ( 15 + <div {...stylex.props(styles.wrapper)}> 16 + <ProgressCircle value={50} /> 17 + <ProgressCircle value={75} /> 18 + <ProgressCircle value={100} /> 19 + </div> 20 + ); 21 + }
+2
packages/hip-ui/src/cli/install.tsx
··· 47 47 import { paginationConfig } from "../components/pagination/pagination-config.js"; 48 48 import { popoverConfig } from "../components/popover/popover-config.js"; 49 49 import { progressBarConfig } from "../components/progress-bar/progress-bar-config.js"; 50 + import { progressCircleConfig } from "../components/progress-circle/progress-circle-config.js"; 50 51 import { radioConfig } from "../components/radio/radio-config.js"; 51 52 import { rangeCalendarConfig } from "../components/range-calendar/range-calendar-config.js"; 52 53 import { searchFieldConfig } from "../components/search-field/search-field-config.js"; ··· 117 118 sliderConfig, 118 119 tagGroupConfig, 119 120 progressBarConfig, 121 + progressCircleConfig, 120 122 meterConfig, 121 123 hoverCardConfig, 122 124 segmentedControlConfig,
+196
packages/hip-ui/src/components/progress-circle/index.tsx
··· 1 + import type { ProgressBarProps as AriaProgressBarProps } from "react-aria-components"; 2 + 3 + import * as stylex from "@stylexjs/stylex"; 4 + import { Check } from "lucide-react"; 5 + import { use } from "react"; 6 + import { ProgressBar as ProgressBar } from "react-aria-components"; 7 + 8 + import type { Size, StyleXComponentProps } from "../theme/types"; 9 + 10 + import { SizeContext } from "../context"; 11 + import { Label } from "../label"; 12 + import { animationDuration } from "../theme/animations.stylex"; 13 + import { radius } from "../theme/radius.stylex"; 14 + import { primaryColor, uiColor } from "../theme/semantic-color.stylex"; 15 + import { spacing } from "../theme/spacing.stylex"; 16 + 17 + const IndeterminateFillAnimation = stylex.keyframes({ 18 + from: { 19 + transform: "rotate(0deg)", 20 + }, 21 + to: { 22 + transform: "rotate(360deg)", 23 + }, 24 + }); 25 + 26 + const styles = stylex.create({ 27 + wrapper: { 28 + alignItems: "center", 29 + boxSizing: "border-box", 30 + display: "flex", 31 + flexDirection: "column", 32 + gap: spacing["2"], 33 + }, 34 + circleWrapper: { 35 + justifyContent: "center", 36 + position: "relative", 37 + 38 + "--progress-border-width": spacing["1"], 39 + "--progress-size": { 40 + ":is([data-size=sm] *)": spacing["4"], 41 + ":is([data-size=md] *)": spacing["8"], 42 + ":is([data-size=lg] *)": spacing["10"], 43 + }, 44 + }, 45 + track: { 46 + borderColor: uiColor.component1, 47 + borderRadius: radius.full, 48 + borderStyle: "solid", 49 + borderWidth: "var(--progress-border-width)", 50 + boxSizing: "border-box", 51 + height: "var(--progress-size)", 52 + width: "var(--progress-size)", 53 + }, 54 + fills: { 55 + height: "100%", 56 + left: 0, 57 + position: "absolute", 58 + top: 0, 59 + width: "100%", 60 + }, 61 + fillMask: { 62 + boxSizing: "border-box", 63 + height: "100%", 64 + overflow: "hidden", 65 + position: "absolute", 66 + transformOrigin: "100%", 67 + width: "50%", 68 + }, 69 + fillMask1: { 70 + transform: "rotate(0deg)", 71 + }, 72 + fillMask2: { 73 + transform: "rotate(180deg)", 74 + }, 75 + fillSubmask: { 76 + boxSizing: "border-box", 77 + height: "100%", 78 + overflow: "hidden", 79 + transformOrigin: "100%", 80 + transitionDuration: animationDuration.default, 81 + transitionProperty: "transform", 82 + transitionTimingFunction: "linear", 83 + width: "100%", 84 + }, 85 + fill: { 86 + borderColor: primaryColor.solid1, 87 + borderRadius: radius.full, 88 + borderStyle: "solid", 89 + borderWidth: "var(--progress-border-width)", 90 + boxSizing: "border-box", 91 + height: "var(--progress-size)", 92 + width: "var(--progress-size)", 93 + }, 94 + check: { 95 + left: "50%", 96 + position: "absolute", 97 + top: "50%", 98 + transform: "translate(-50%, -50%)", 99 + }, 100 + completed: { 101 + backgroundColor: primaryColor.solid1, 102 + }, 103 + indeterminateFillAnimation: { 104 + animationDuration: "1s", 105 + animationIterationCount: "infinite", 106 + animationName: IndeterminateFillAnimation, 107 + animationTimingFunction: "linear", 108 + }, 109 + }); 110 + 111 + export interface ProgressCircleProps 112 + extends StyleXComponentProps<Omit<AriaProgressBarProps, "children">> { 113 + label?: string; 114 + size?: Size; 115 + } 116 + 117 + export function ProgressCircle({ 118 + label, 119 + style, 120 + size: sizeProp, 121 + ...props 122 + }: ProgressCircleProps) { 123 + const size = sizeProp || use(SizeContext); 124 + 125 + return ( 126 + <ProgressBar 127 + {...props} 128 + data-size={size} 129 + {...stylex.props(styles.wrapper, style)} 130 + > 131 + {({ percentage = 0, isIndeterminate }) => { 132 + console.log({ percentage }, Math.min(0.5, percentage / 100)); 133 + const rotateFirstHalf = Math.min(0.5, percentage / 100) * 360 - 180; 134 + const rotateSecondHalf = (1 - Math.max(0.5, percentage / 100)) * -360; 135 + 136 + return ( 137 + <> 138 + <div {...stylex.props(styles.circleWrapper)}> 139 + <div 140 + {...stylex.props( 141 + styles.track, 142 + percentage === 100 && styles.completed, 143 + )} 144 + /> 145 + 146 + {isIndeterminate ? ( 147 + <div {...stylex.props(styles.fills)}> 148 + <div 149 + {...stylex.props( 150 + styles.fillMask, 151 + styles.fillMask1, 152 + styles.indeterminateFillAnimation, 153 + )} 154 + > 155 + <div 156 + {...stylex.props(styles.fillSubmask)} 157 + style={{ transform: "rotate(90deg)" }} 158 + > 159 + <div {...stylex.props(styles.fill)} /> 160 + </div> 161 + </div> 162 + </div> 163 + ) : ( 164 + <div {...stylex.props(styles.fills)}> 165 + <div {...stylex.props(styles.fillMask, styles.fillMask1)}> 166 + <div 167 + {...stylex.props(styles.fillSubmask)} 168 + style={{ 169 + transform: `rotate(${rotateSecondHalf.toString()}deg)`, 170 + }} 171 + > 172 + <div {...stylex.props(styles.fill)} /> 173 + </div> 174 + </div> 175 + <div {...stylex.props(styles.fillMask, styles.fillMask2)}> 176 + <div 177 + {...stylex.props(styles.fillSubmask)} 178 + style={{ 179 + transform: `rotate(${rotateFirstHalf.toString()}deg)`, 180 + }} 181 + > 182 + <div {...stylex.props(styles.fill)} /> 183 + </div> 184 + </div> 185 + </div> 186 + )} 187 + 188 + {percentage === 100 && <Check {...stylex.props(styles.check)} />} 189 + </div> 190 + {label && <Label>{label}</Label>} 191 + </> 192 + ); 193 + }} 194 + </ProgressBar> 195 + ); 196 + }
+17
packages/hip-ui/src/components/progress-circle/progress-circle-config.ts
··· 1 + import { ComponentConfig } from "../../types"; 2 + 3 + export const progressCircleConfig: ComponentConfig = { 4 + name: "progress-circle", 5 + filepath: "./index.tsx", 6 + hipDependencies: [ 7 + "../context.ts", 8 + "../label/index.tsx", 9 + "../theme/radius.stylex.tsx", 10 + "../theme/spacing.stylex.tsx", 11 + "../theme/types.ts", 12 + ], 13 + dependencies: { 14 + "react-aria-components": "^1.13.0", 15 + }, 16 + }; 17 +