mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
feat: Implement Linear and Circular Progress components with styles
This commit is contained in:
@@ -1,14 +1,133 @@
|
||||
import React from 'react'
|
||||
import styles from '../../styles/components/Progress.module.scss'
|
||||
|
||||
export interface LinearProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/** Current progress value (0-100) */
|
||||
value?: number
|
||||
/** Buffer value for buffered progress (0-100) */
|
||||
valueBuffer?: number
|
||||
/** Variant determines the visual style */
|
||||
variant?: 'determinate' | 'indeterminate' | 'buffer' | 'query'
|
||||
/** Color theme */
|
||||
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info'
|
||||
/** @deprecated Use variant="indeterminate" instead */
|
||||
indeterminate?: boolean
|
||||
}
|
||||
|
||||
export const LinearProgress: React.FC<LinearProgressProps> = ({ value, indeterminate, className = '', ...props }) => (
|
||||
<div className={`progress ${indeterminate ? 'progress--indeterminate' : ''} ${className}`} {...props}>
|
||||
<div className="progress-bar" style={value !== undefined ? { width: `${value}%` } : undefined} />
|
||||
</div>
|
||||
)
|
||||
export const LinearProgress: React.FC<LinearProgressProps> = ({
|
||||
value = 0,
|
||||
valueBuffer,
|
||||
variant,
|
||||
color = 'primary',
|
||||
indeterminate,
|
||||
className = '',
|
||||
...props
|
||||
}) => {
|
||||
// Support legacy indeterminate prop
|
||||
const effectiveVariant = variant || (indeterminate ? 'indeterminate' : 'determinate')
|
||||
const clampedValue = Math.min(100, Math.max(0, value))
|
||||
const clampedBuffer = valueBuffer !== undefined ? Math.min(100, Math.max(0, valueBuffer)) : undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.linearProgress} ${styles[effectiveVariant]} ${styles[color]} ${className}`}
|
||||
role="progressbar"
|
||||
aria-valuenow={effectiveVariant === 'determinate' ? clampedValue : undefined}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
{...props}
|
||||
>
|
||||
{effectiveVariant === 'buffer' && (
|
||||
<>
|
||||
<div className={styles.buffer} style={{ width: `${clampedBuffer ?? 0}%` }} />
|
||||
<div className={styles.dashed} />
|
||||
</>
|
||||
)}
|
||||
<div
|
||||
className={styles.bar}
|
||||
style={effectiveVariant === 'determinate' || effectiveVariant === 'buffer'
|
||||
? { width: `${clampedValue}%` }
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{(effectiveVariant === 'indeterminate' || effectiveVariant === 'query') && (
|
||||
<div className={styles.bar2} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export interface CircularProgressProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
/** Current progress value (0-100) for determinate variant */
|
||||
value?: number
|
||||
/** Variant determines the visual style */
|
||||
variant?: 'determinate' | 'indeterminate'
|
||||
/** Size of the progress circle */
|
||||
size?: number | string
|
||||
/** Thickness of the progress circle stroke */
|
||||
thickness?: number
|
||||
/** Color theme */
|
||||
color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info' | 'inherit'
|
||||
/** Disable shrink animation on indeterminate */
|
||||
disableShrink?: boolean
|
||||
}
|
||||
|
||||
export const CircularProgress: React.FC<CircularProgressProps> = ({
|
||||
value = 0,
|
||||
variant = 'indeterminate',
|
||||
size = 40,
|
||||
thickness = 3.6,
|
||||
color = 'primary',
|
||||
disableShrink = false,
|
||||
className = '',
|
||||
style,
|
||||
...props
|
||||
}) => {
|
||||
const clampedValue = Math.min(100, Math.max(0, value))
|
||||
const circumference = 2 * Math.PI * ((44 - thickness) / 2)
|
||||
const strokeDasharray = circumference.toFixed(3)
|
||||
const strokeDashoffset = variant === 'determinate'
|
||||
? (((100 - clampedValue) / 100) * circumference).toFixed(3)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${styles.circularProgress} ${styles[variant]} ${styles[`circular-${color}`]} ${disableShrink ? styles.disableShrink : ''} ${className}`}
|
||||
role="progressbar"
|
||||
aria-valuenow={variant === 'determinate' ? clampedValue : undefined}
|
||||
aria-valuemin={0}
|
||||
aria-valuemax={100}
|
||||
style={{
|
||||
width: typeof size === 'number' ? `${size}px` : size,
|
||||
height: typeof size === 'number' ? `${size}px` : size,
|
||||
...style,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<svg viewBox="0 0 44 44" className={styles.svg}>
|
||||
{/* Background circle (track) */}
|
||||
<circle
|
||||
className={styles.track}
|
||||
cx={22}
|
||||
cy={22}
|
||||
r={(44 - thickness) / 2}
|
||||
fill="none"
|
||||
strokeWidth={thickness}
|
||||
/>
|
||||
{/* Progress circle */}
|
||||
<circle
|
||||
className={styles.circle}
|
||||
cx={22}
|
||||
cy={22}
|
||||
r={(44 - thickness) / 2}
|
||||
fill="none"
|
||||
strokeWidth={thickness}
|
||||
strokeDasharray={strokeDasharray}
|
||||
strokeDashoffset={strokeDashoffset}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Progress = LinearProgress // alias
|
||||
|
||||
272
fakemui/styles/components/Progress.module.scss
Normal file
272
fakemui/styles/components/Progress.module.scss
Normal file
@@ -0,0 +1,272 @@
|
||||
// Progress component styles
|
||||
// LinearProgress and CircularProgress
|
||||
|
||||
// Linear Progress
|
||||
.linearProgress {
|
||||
height: 4px;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: var(--progress-track, rgba(var(--color-primary-rgb), 0.2));
|
||||
border-radius: var(--radius-full);
|
||||
|
||||
.bar,
|
||||
.bar2 {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
border-radius: inherit;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.bar {
|
||||
background-color: var(--progress-bar, var(--color-primary));
|
||||
}
|
||||
|
||||
.buffer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--progress-buffer, rgba(var(--color-primary-rgb), 0.3));
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.dashed {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-image: radial-gradient(
|
||||
var(--progress-track, rgba(var(--color-primary-rgb), 0.2)) 0%,
|
||||
var(--progress-track, rgba(var(--color-primary-rgb), 0.2)) 16%,
|
||||
transparent 42%
|
||||
);
|
||||
background-size: 10px 10px;
|
||||
background-position: 0 -23px;
|
||||
animation: dashed-animation 3s infinite linear;
|
||||
}
|
||||
|
||||
// Determinate (default)
|
||||
&.determinate {
|
||||
.bar {
|
||||
transition: width 0.4s linear;
|
||||
}
|
||||
}
|
||||
|
||||
// Indeterminate animation
|
||||
&.indeterminate {
|
||||
.bar {
|
||||
width: auto;
|
||||
animation: indeterminate-bar1 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;
|
||||
}
|
||||
|
||||
.bar2 {
|
||||
background-color: var(--progress-bar, var(--color-primary));
|
||||
animation: indeterminate-bar2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// Query animation (reverse indeterminate)
|
||||
&.query {
|
||||
.bar {
|
||||
width: auto;
|
||||
animation: indeterminate-bar1 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite reverse;
|
||||
}
|
||||
|
||||
.bar2 {
|
||||
background-color: var(--progress-bar, var(--color-primary));
|
||||
animation: indeterminate-bar2 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) 1.15s infinite reverse;
|
||||
}
|
||||
}
|
||||
|
||||
// Color variants
|
||||
&.primary {
|
||||
--progress-bar: var(--color-primary);
|
||||
--progress-track: rgba(var(--color-primary-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-primary-rgb), 0.4);
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
--progress-bar: var(--color-secondary);
|
||||
--progress-track: rgba(var(--color-secondary-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-secondary-rgb), 0.4);
|
||||
}
|
||||
|
||||
&.success {
|
||||
--progress-bar: var(--color-success);
|
||||
--progress-track: rgba(var(--color-success-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-success-rgb), 0.4);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
--progress-bar: var(--color-warning);
|
||||
--progress-track: rgba(var(--color-warning-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-warning-rgb), 0.4);
|
||||
}
|
||||
|
||||
&.error {
|
||||
--progress-bar: var(--color-error);
|
||||
--progress-track: rgba(var(--color-error-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-error-rgb), 0.4);
|
||||
}
|
||||
|
||||
&.info {
|
||||
--progress-bar: var(--color-info);
|
||||
--progress-track: rgba(var(--color-info-rgb), 0.2);
|
||||
--progress-buffer: rgba(var(--color-info-rgb), 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
// Circular Progress
|
||||
.circularProgress {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
|
||||
.svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.track {
|
||||
stroke: var(--circular-track, rgba(var(--color-primary-rgb), 0.2));
|
||||
}
|
||||
|
||||
.circle {
|
||||
stroke: var(--circular-bar, var(--color-primary));
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
// Determinate
|
||||
&.determinate {
|
||||
.svg {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.circle {
|
||||
transition: stroke-dashoffset 0.3s ease;
|
||||
}
|
||||
}
|
||||
|
||||
// Indeterminate
|
||||
&.indeterminate {
|
||||
animation: circular-rotate 1.4s linear infinite;
|
||||
|
||||
.circle {
|
||||
animation: circular-dash 1.4s ease-in-out infinite;
|
||||
stroke-dasharray: 80px, 200px;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.disableShrink {
|
||||
.circle {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Color variants
|
||||
&.circular-primary {
|
||||
--circular-bar: var(--color-primary);
|
||||
--circular-track: rgba(var(--color-primary-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-secondary {
|
||||
--circular-bar: var(--color-secondary);
|
||||
--circular-track: rgba(var(--color-secondary-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-success {
|
||||
--circular-bar: var(--color-success);
|
||||
--circular-track: rgba(var(--color-success-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-warning {
|
||||
--circular-bar: var(--color-warning);
|
||||
--circular-track: rgba(var(--color-warning-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-error {
|
||||
--circular-bar: var(--color-error);
|
||||
--circular-track: rgba(var(--color-error-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-info {
|
||||
--circular-bar: var(--color-info);
|
||||
--circular-track: rgba(var(--color-info-rgb), 0.2);
|
||||
}
|
||||
|
||||
&.circular-inherit {
|
||||
--circular-bar: currentColor;
|
||||
--circular-track: currentColor;
|
||||
|
||||
.track {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animations
|
||||
@keyframes indeterminate-bar1 {
|
||||
0% {
|
||||
left: -35%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
100% {
|
||||
left: 100%;
|
||||
right: -90%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes indeterminate-bar2 {
|
||||
0% {
|
||||
left: -200%;
|
||||
right: 100%;
|
||||
}
|
||||
60% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
100% {
|
||||
left: 107%;
|
||||
right: -8%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dashed-animation {
|
||||
0% {
|
||||
background-position: 0 -23px;
|
||||
}
|
||||
100% {
|
||||
background-position: -200px -23px;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes circular-rotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes circular-dash {
|
||||
0% {
|
||||
stroke-dasharray: 1px, 200px;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 100px, 200px;
|
||||
stroke-dashoffset: -15px;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 100px, 200px;
|
||||
stroke-dashoffset: -125px;
|
||||
}
|
||||
}
|
||||
13
packages/user_manager/seed/scripts/change_level.lua
Normal file
13
packages/user_manager/seed/scripts/change_level.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
--- Change user access level action
|
||||
---@param user_id string User ID
|
||||
---@param new_level number New access level (1-6)
|
||||
---@return UserAction Action object
|
||||
local function change_level(user_id, new_level)
|
||||
return {
|
||||
action = "change_level",
|
||||
user_id = user_id,
|
||||
level = new_level
|
||||
}
|
||||
end
|
||||
|
||||
return change_level
|
||||
11
packages/user_manager/seed/scripts/create_user.lua
Normal file
11
packages/user_manager/seed/scripts/create_user.lua
Normal file
@@ -0,0 +1,11 @@
|
||||
--- Create a new user action
|
||||
---@param data table User data
|
||||
---@return UserAction Action object
|
||||
local function create_user(data)
|
||||
return {
|
||||
action = "create_user",
|
||||
data = data
|
||||
}
|
||||
end
|
||||
|
||||
return create_user
|
||||
12
packages/user_manager/seed/scripts/delete_user.lua
Normal file
12
packages/user_manager/seed/scripts/delete_user.lua
Normal file
@@ -0,0 +1,12 @@
|
||||
--- Delete a user action
|
||||
---@param user_id string User ID
|
||||
---@return UserAction Action object
|
||||
local function delete_user(user_id)
|
||||
return {
|
||||
action = "delete_user",
|
||||
user_id = user_id,
|
||||
confirm = true
|
||||
}
|
||||
end
|
||||
|
||||
return delete_user
|
||||
14
packages/user_manager/seed/scripts/get_columns.lua
Normal file
14
packages/user_manager/seed/scripts/get_columns.lua
Normal file
@@ -0,0 +1,14 @@
|
||||
--- Get user table column definitions
|
||||
---@return TableColumn[] Column definitions
|
||||
local function get_columns()
|
||||
return {
|
||||
{ id = "username", label = "Username", sortable = true },
|
||||
{ id = "email", label = "Email", sortable = true },
|
||||
{ id = "role", label = "Role", sortable = true },
|
||||
{ id = "level", label = "Level", sortable = true },
|
||||
{ id = "active", label = "Status", type = "badge" },
|
||||
{ id = "actions", label = "", type = "actions" }
|
||||
}
|
||||
end
|
||||
|
||||
return get_columns
|
||||
15
packages/user_manager/seed/scripts/render_row.lua
Normal file
15
packages/user_manager/seed/scripts/render_row.lua
Normal file
@@ -0,0 +1,15 @@
|
||||
--- Render a single user row
|
||||
---@param user User User object
|
||||
---@return TableRow Table row data
|
||||
local function render_row(user)
|
||||
return {
|
||||
username = user.username,
|
||||
email = user.email,
|
||||
role = user.role,
|
||||
level = user.level,
|
||||
active = user.active and "Active" or "Inactive",
|
||||
actions = { "edit", "delete" }
|
||||
}
|
||||
end
|
||||
|
||||
return render_row
|
||||
19
packages/user_manager/seed/scripts/render_users.lua
Normal file
19
packages/user_manager/seed/scripts/render_users.lua
Normal file
@@ -0,0 +1,19 @@
|
||||
local get_columns = require("get_columns")
|
||||
local render_row = require("render_row")
|
||||
|
||||
--- Render users list as data table config
|
||||
---@param users User[] Array of users
|
||||
---@return DataTableConfig Table configuration
|
||||
local function render_users(users)
|
||||
local rows = {}
|
||||
for _, user in ipairs(users) do
|
||||
table.insert(rows, render_row(user))
|
||||
end
|
||||
return {
|
||||
type = "data_table",
|
||||
columns = get_columns(),
|
||||
rows = rows
|
||||
}
|
||||
end
|
||||
|
||||
return render_users
|
||||
13
packages/user_manager/seed/scripts/toggle_active.lua
Normal file
13
packages/user_manager/seed/scripts/toggle_active.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
--- Toggle user active status action
|
||||
---@param user_id string User ID
|
||||
---@param active boolean New active status
|
||||
---@return UserAction Action object
|
||||
local function toggle_active(user_id, active)
|
||||
return {
|
||||
action = "toggle_active",
|
||||
user_id = user_id,
|
||||
active = active
|
||||
}
|
||||
end
|
||||
|
||||
return toggle_active
|
||||
39
packages/user_manager/seed/scripts/types.lua
Normal file
39
packages/user_manager/seed/scripts/types.lua
Normal file
@@ -0,0 +1,39 @@
|
||||
---@meta
|
||||
-- Type definitions for user_manager package
|
||||
|
||||
---@class UserAction
|
||||
---@field action string Action type
|
||||
---@field user_id? string Target user ID
|
||||
---@field data? table Action data
|
||||
---@field confirm? boolean Requires confirmation
|
||||
---@field level? number Target access level
|
||||
---@field active? boolean Active status
|
||||
|
||||
---@class User
|
||||
---@field id string User ID
|
||||
---@field username string Username
|
||||
---@field email string Email address
|
||||
---@field role string User role
|
||||
---@field level number Access level (1-6)
|
||||
---@field active boolean Whether user is active
|
||||
|
||||
---@class TableColumn
|
||||
---@field id string Column identifier
|
||||
---@field label string Column header label
|
||||
---@field sortable? boolean Whether column is sortable
|
||||
---@field type? string Column type (badge, actions, etc.)
|
||||
|
||||
---@class TableRow
|
||||
---@field username string
|
||||
---@field email string
|
||||
---@field role string
|
||||
---@field level number
|
||||
---@field active string Status badge text
|
||||
---@field actions string[] Available actions
|
||||
|
||||
---@class DataTableConfig
|
||||
---@field type string Component type
|
||||
---@field columns TableColumn[] Column definitions
|
||||
---@field rows TableRow[] Row data
|
||||
|
||||
return {}
|
||||
13
packages/user_manager/seed/scripts/update_user.lua
Normal file
13
packages/user_manager/seed/scripts/update_user.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
--- Update an existing user action
|
||||
---@param user_id string User ID
|
||||
---@param data table Update data
|
||||
---@return UserAction Action object
|
||||
local function update_user(user_id, data)
|
||||
return {
|
||||
action = "update_user",
|
||||
user_id = user_id,
|
||||
data = data
|
||||
}
|
||||
end
|
||||
|
||||
return update_user
|
||||
Reference in New Issue
Block a user