diff --git a/fakemui/fakemui/feedback/Progress.tsx b/fakemui/fakemui/feedback/Progress.tsx index 93f178574..70c95b832 100644 --- a/fakemui/fakemui/feedback/Progress.tsx +++ b/fakemui/fakemui/feedback/Progress.tsx @@ -1,14 +1,133 @@ import React from 'react' +import styles from '../../styles/components/Progress.module.scss' export interface LinearProgressProps extends React.HTMLAttributes { + /** 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 = ({ value, indeterminate, className = '', ...props }) => ( -
-
-
-) +export const LinearProgress: React.FC = ({ + 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 ( +
+ {effectiveVariant === 'buffer' && ( + <> +
+
+ + )} +
+ {(effectiveVariant === 'indeterminate' || effectiveVariant === 'query') && ( +
+ )} +
+ ) +} + +export interface CircularProgressProps extends React.HTMLAttributes { + /** 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 = ({ + 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 ( +
+ + {/* Background circle (track) */} + + {/* Progress circle */} + + +
+ ) +} export const Progress = LinearProgress // alias diff --git a/fakemui/styles/components/Progress.module.scss b/fakemui/styles/components/Progress.module.scss new file mode 100644 index 000000000..3af042b36 --- /dev/null +++ b/fakemui/styles/components/Progress.module.scss @@ -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; + } +} diff --git a/packages/user_manager/seed/scripts/change_level.lua b/packages/user_manager/seed/scripts/change_level.lua new file mode 100644 index 000000000..e827085ba --- /dev/null +++ b/packages/user_manager/seed/scripts/change_level.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/create_user.lua b/packages/user_manager/seed/scripts/create_user.lua new file mode 100644 index 000000000..5596b6d7b --- /dev/null +++ b/packages/user_manager/seed/scripts/create_user.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/delete_user.lua b/packages/user_manager/seed/scripts/delete_user.lua new file mode 100644 index 000000000..a8b20e15a --- /dev/null +++ b/packages/user_manager/seed/scripts/delete_user.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/get_columns.lua b/packages/user_manager/seed/scripts/get_columns.lua new file mode 100644 index 000000000..e60010d81 --- /dev/null +++ b/packages/user_manager/seed/scripts/get_columns.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/render_row.lua b/packages/user_manager/seed/scripts/render_row.lua new file mode 100644 index 000000000..52d24f76b --- /dev/null +++ b/packages/user_manager/seed/scripts/render_row.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/render_users.lua b/packages/user_manager/seed/scripts/render_users.lua new file mode 100644 index 000000000..cfb8de769 --- /dev/null +++ b/packages/user_manager/seed/scripts/render_users.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/toggle_active.lua b/packages/user_manager/seed/scripts/toggle_active.lua new file mode 100644 index 000000000..2ad55d2ef --- /dev/null +++ b/packages/user_manager/seed/scripts/toggle_active.lua @@ -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 diff --git a/packages/user_manager/seed/scripts/types.lua b/packages/user_manager/seed/scripts/types.lua new file mode 100644 index 000000000..d7366c797 --- /dev/null +++ b/packages/user_manager/seed/scripts/types.lua @@ -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 {} diff --git a/packages/user_manager/seed/scripts/update_user.lua b/packages/user_manager/seed/scripts/update_user.lua new file mode 100644 index 000000000..fc64bcb1c --- /dev/null +++ b/packages/user_manager/seed/scripts/update_user.lua @@ -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