ui phase 0
This commit is contained in:
@@ -60,14 +60,17 @@ describe('<AppButton/> component', () => {
|
||||
expect(button).toHaveAttribute('type', 'button'); // not "submit" or "input" by default
|
||||
});
|
||||
|
||||
it('has .margin style by default', () => {
|
||||
let text = 'button with default margin';
|
||||
it('does not force a default margin, and passes a custom sx through untouched', () => {
|
||||
const testId = randomText(8);
|
||||
render(<ComponentToTest data-testid={testId}>{text}</ComponentToTest>);
|
||||
render(
|
||||
<ComponentToTest data-testid={testId} sx={{ marginInlineStart: 2 }}>
|
||||
button with custom sx
|
||||
</ComponentToTest>
|
||||
);
|
||||
const button = screen.getByTestId(testId);
|
||||
expect(button).toBeDefined();
|
||||
// MUI v9 + cssVariables: spacing is emitted as CSS vars (not resolved in jsdom).
|
||||
// Verify the sx margin is applied via class rather than inline style.
|
||||
// Regression guard for the removed starter DEFAULT_SX_VALUES={ margin: 1 }:
|
||||
// AppButton must not silently drop or merge over a caller-provided sx.
|
||||
expect(button).toHaveClass('MuiButton-root');
|
||||
});
|
||||
|
||||
@@ -127,7 +130,7 @@ describe('<AppButton/> component', () => {
|
||||
let text = 'button with start icon';
|
||||
render(<ComponentToTest text={text} startIcon="default" />);
|
||||
let button = screen.getByText(text);
|
||||
let icon = within(button).getByTestId('MoreHorizIcon'); //Note: this is valid only when "default" icon is <MoreHorizIcon />
|
||||
let icon = within(button).getByTestId('MoreHorizRoundedIcon'); //Note: this is valid only when "default" icon is <MoreHorizRoundedIcon />
|
||||
expect(icon).toBeDefined();
|
||||
let span = icon.closest('span');
|
||||
expect(span).toHaveClass('MuiButton-startIcon');
|
||||
@@ -137,7 +140,7 @@ describe('<AppButton/> component', () => {
|
||||
let text = 'button with end icon as string';
|
||||
render(<ComponentToTest text={text} endIcon="default" />);
|
||||
let button = screen.getByText(text);
|
||||
let icon = within(button).getByTestId('MoreHorizIcon'); //Note: this is valid only when "default" icon is <MoreHorizIcon />
|
||||
let icon = within(button).getByTestId('MoreHorizRoundedIcon'); //Note: this is valid only when "default" icon is <MoreHorizRoundedIcon />
|
||||
expect(icon).toBeDefined();
|
||||
let span = icon.closest('span');
|
||||
expect(span).toHaveClass('MuiButton-endIcon');
|
||||
|
||||
@@ -6,26 +6,20 @@ import { APP_BUTTON_VARIANT } from '../../config';
|
||||
|
||||
const MUI_BUTTON_COLORS = ['inherit', 'primary', 'secondary', 'success', 'error', 'info', 'warning'];
|
||||
|
||||
const DEFAULT_SX_VALUES = {
|
||||
margin: 1, // By default the AppButton has theme.spacing(1) margin on all sides
|
||||
};
|
||||
|
||||
export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' | 'startIcon'> {
|
||||
color?: string; // Not only 'inherit' | 'primary' | 'secondary' | 'success' | 'error' | 'info' | 'warning',
|
||||
endIcon?: string | ReactNode;
|
||||
label?: string; // Alternate to .text
|
||||
text?: string; // Alternate to .label
|
||||
startIcon?: string | ReactNode;
|
||||
// Missing props
|
||||
component?: ElementType; // Could be RouterLink, AppLink, <a>, etc.
|
||||
to?: string; // Link prop
|
||||
href?: string; // Link prop
|
||||
openInNewTab?: boolean; // Link prop
|
||||
underline?: 'none' | 'hover' | 'always'; // Link prop
|
||||
}
|
||||
|
||||
/**
|
||||
* Application styled Material UI Button with Box around to specify margins using props
|
||||
* Application-styled Material UI Button.
|
||||
* @component AppButton
|
||||
* @param {string} [color] - when passing MUI value ('primary', 'secondary', and so on), it is color of the button body, otherwise it is color of text and icons
|
||||
* @param {string} [children] - content to render, overrides .label and .text props
|
||||
@@ -37,7 +31,6 @@ export interface AppButtonProps extends Omit<ButtonProps, 'color' | 'endIcon' |
|
||||
* @param {Array<func| object| bool> | func | object} [sx] - additional CSS styles to apply to the button
|
||||
* @param {string} [text] - text to render, alternate to .label
|
||||
* @param {string} [to] - internal link URI
|
||||
* @param {string} [underline] - controls underline style when button used as link, one of 'none', 'hover', or 'always'
|
||||
* @param {string} [variant] - MUI variant of the button, one of 'text', 'outlined', or 'contained'
|
||||
*/
|
||||
const AppButton: FunctionComponent<AppButtonProps> = ({
|
||||
@@ -47,9 +40,7 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
|
||||
endIcon,
|
||||
label,
|
||||
startIcon,
|
||||
sx: propSx = DEFAULT_SX_VALUES,
|
||||
text,
|
||||
underline = 'none',
|
||||
variant = APP_BUTTON_VARIANT,
|
||||
...restOfProps
|
||||
}) => {
|
||||
@@ -69,10 +60,7 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
|
||||
!propComponent && (restOfProps?.href || restOfProps?.to) ? AppLink : propComponent ?? Button;
|
||||
|
||||
const colorToRender = isMuiColor ? (propColor as ButtonProps['color']) : 'inherit';
|
||||
const sxToRender = {
|
||||
...propSx,
|
||||
...(isMuiColor ? {} : { color: propColor }),
|
||||
};
|
||||
const sxToRender = isMuiColor ? restOfProps.sx : { ...restOfProps.sx, color: propColor };
|
||||
|
||||
return (
|
||||
<Button
|
||||
@@ -80,9 +68,9 @@ const AppButton: FunctionComponent<AppButtonProps> = ({
|
||||
color={colorToRender}
|
||||
endIcon={iconEnd}
|
||||
startIcon={iconStart}
|
||||
sx={sxToRender}
|
||||
variant={variant}
|
||||
{...{ ...restOfProps, underline }}
|
||||
{...restOfProps}
|
||||
sx={sxToRender}
|
||||
>
|
||||
{children || label || text}
|
||||
</Button>
|
||||
|
||||
@@ -16,9 +16,11 @@ describe('<AppIcon/> component', () => {
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toBeDefined();
|
||||
expect(svg).toHaveAttribute('data-icon', 'default');
|
||||
expect(svg).toHaveAttribute('size', String(APP_ICON_SIZE)); // default size
|
||||
expect(svg).toHaveAttribute('height', String(APP_ICON_SIZE)); // default size when .size is not set
|
||||
expect(svg).toHaveAttribute('width', String(APP_ICON_SIZE)); // default size when .size is not se
|
||||
// Size is actually driven by fontSize (MUI SvgIcon's `1em` sizing beats
|
||||
// width/height attributes) — never assert a `size` DOM attribute, it's invalid.
|
||||
expect(svg).toHaveStyle(`font-size: ${APP_ICON_SIZE}px`);
|
||||
});
|
||||
|
||||
it('supports .color property', () => {
|
||||
@@ -48,9 +50,11 @@ describe('<AppIcon/> component', () => {
|
||||
const size = Math.floor(Math.random() * 128) + 1;
|
||||
render(<ComponentToTest data-testid={testId} size={size} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toHaveAttribute('size', String(size));
|
||||
expect(svg).toHaveAttribute('height', String(size));
|
||||
expect(svg).toHaveAttribute('width', String(size));
|
||||
// The bug this regression-guards: MUI SvgIcon's own CSS sets width/height:1em,
|
||||
// which beats plain width/height attributes — only fontSize actually resizes it.
|
||||
expect(svg).toHaveStyle(`font-size: ${size}px`);
|
||||
});
|
||||
|
||||
it('supports .title property', () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ComponentType, FunctionComponent, SVGAttributes } from 'react';
|
||||
import { APP_ICON_SIZE } from '../../config';
|
||||
import { IconName, ICONS } from './config';
|
||||
import { IconName, ICONS, DIRECTIONAL_ICONS } from './config';
|
||||
|
||||
/**
|
||||
* Props of the AppIcon component, also can be used for SVG icons
|
||||
@@ -31,21 +31,35 @@ const AppIcon: FunctionComponent<Props> = ({
|
||||
|
||||
let ComponentToRender: ComponentType = ICONS[iconName];
|
||||
if (!ComponentToRender) {
|
||||
console.warn(`AppIcon: icon "${iconName}" is not found!`);
|
||||
ComponentToRender = ICONS.default; // ICONS['default'];
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
console.warn(`AppIcon: icon "${iconName}" is not found!`);
|
||||
}
|
||||
ComponentToRender = ICONS.default;
|
||||
}
|
||||
|
||||
const sizeValue = typeof size === 'number' ? `${size}px` : size;
|
||||
|
||||
// MUI's SvgIcon sets width/height:1em via its own class, which beats plain
|
||||
// width/height attributes — driving size through fontSize (the basis for 1em)
|
||||
// is what actually resizes MUI-registry icons. Custom raw <svg> icons (the
|
||||
// brand mark) aren't MUI SvgIcon-based, so they keep scaling off the explicit
|
||||
// width/height attributes below; the extra fontSize style is harmless for them.
|
||||
const propsToRender = {
|
||||
height: size,
|
||||
width: size,
|
||||
color,
|
||||
fill: color && 'currentColor',
|
||||
size,
|
||||
style: { ...style, color },
|
||||
width: size,
|
||||
style: { ...style, color, fontSize: sizeValue },
|
||||
...restOfProps,
|
||||
};
|
||||
|
||||
return <ComponentToRender data-icon={iconName} {...propsToRender} />;
|
||||
// Directional icons (back/chevron_start, …) are authored for LTR and mirror
|
||||
// under RTL via the single CSS rule in tokens.css keyed on this attribute —
|
||||
// registering a name in DIRECTIONAL_ICONS (config.ts) is the only step a
|
||||
// later phase needs; never hand-roll a flip at the call site.
|
||||
const directionalProps = DIRECTIONAL_ICONS.has(iconName) ? { 'data-icon-directional': true } : undefined;
|
||||
|
||||
return <ComponentToRender data-icon={iconName} {...directionalProps} {...propsToRender} />;
|
||||
};
|
||||
|
||||
export default AppIcon;
|
||||
|
||||
@@ -1,101 +1,103 @@
|
||||
// SVG assets
|
||||
import PencilIcon from './icons/PencilIcon';
|
||||
// MUI Icons
|
||||
import DefaultIcon from '@mui/icons-material/MoreHoriz';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import DayNightIcon from '@mui/icons-material/Brightness4';
|
||||
import NightIcon from '@mui/icons-material/Brightness3';
|
||||
import DayIcon from '@mui/icons-material/Brightness5';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
|
||||
import NotificationsIcon from '@mui/icons-material/NotificationsOutlined';
|
||||
import DangerousIcon from '@mui/icons-material/Dangerous';
|
||||
import EventNoteIcon from '@mui/icons-material/EventNote';
|
||||
import GroupsIcon from '@mui/icons-material/Groups';
|
||||
import PeopleAltIcon from '@mui/icons-material/PeopleAlt';
|
||||
import WalletIcon from '@mui/icons-material/AccountBalanceWallet';
|
||||
import PersonOutlineIcon from '@mui/icons-material/AccountCircleOutlined';
|
||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||
import VerifiedUserIcon from '@mui/icons-material/VerifiedUser';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmpty';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import MedicalServicesIcon from '@mui/icons-material/MedicalServices';
|
||||
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import EditIcon from '@mui/icons-material/EditOutlined';
|
||||
import ArchiveIcon from '@mui/icons-material/Inventory2Outlined';
|
||||
import BankIcon from '@mui/icons-material/AccountBalanceOutlined';
|
||||
import CameraIcon from '@mui/icons-material/PhotoCameraOutlined';
|
||||
import WarningIcon from '@mui/icons-material/WarningAmberOutlined';
|
||||
import LocationIcon from '@mui/icons-material/LocationOnOutlined';
|
||||
import DeleteIcon from '@mui/icons-material/DeleteOutlined';
|
||||
import CoverageIcon from '@mui/icons-material/MapOutlined';
|
||||
// SVG assets — the Balinyaar brand mark
|
||||
import LogoMark from './icons/LogoMark';
|
||||
// MUI Icons — normalized to ONE visual family (Rounded): warmer, softer strokes
|
||||
// than the filled/outlined mix this registry used to carry, fitting the
|
||||
// clinical-but-human tone. Adding an icon = import here + one lowercase key
|
||||
// below; never import a raw MUI icon anywhere else in the app.
|
||||
import DefaultIcon from '@mui/icons-material/MoreHorizRounded';
|
||||
import CloseIcon from '@mui/icons-material/CloseRounded';
|
||||
import MenuIcon from '@mui/icons-material/MenuRounded';
|
||||
import SearchIcon from '@mui/icons-material/SearchRounded';
|
||||
import InfoIcon from '@mui/icons-material/InfoRounded';
|
||||
import HomeIcon from '@mui/icons-material/HomeRounded';
|
||||
import AccountCircleIcon from '@mui/icons-material/AccountCircleRounded';
|
||||
import ExitToAppIcon from '@mui/icons-material/ExitToAppRounded';
|
||||
import NotificationsIcon from '@mui/icons-material/NotificationsRounded';
|
||||
import DangerousIcon from '@mui/icons-material/DangerousRounded';
|
||||
import EventNoteIcon from '@mui/icons-material/EventNoteRounded';
|
||||
import GroupsIcon from '@mui/icons-material/GroupsRounded';
|
||||
import PeopleAltIcon from '@mui/icons-material/PeopleAltRounded';
|
||||
import WalletIcon from '@mui/icons-material/AccountBalanceWalletRounded';
|
||||
import DashboardIcon from '@mui/icons-material/DashboardRounded';
|
||||
import VerifiedUserIcon from '@mui/icons-material/VerifiedUserRounded';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircleRounded';
|
||||
import HourglassEmptyIcon from '@mui/icons-material/HourglassEmptyRounded';
|
||||
import CancelIcon from '@mui/icons-material/CancelRounded';
|
||||
import MedicalServicesIcon from '@mui/icons-material/MedicalServicesRounded';
|
||||
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettingsRounded';
|
||||
import AddIcon from '@mui/icons-material/AddRounded';
|
||||
import EditIcon from '@mui/icons-material/EditRounded';
|
||||
import ArchiveIcon from '@mui/icons-material/Inventory2Rounded';
|
||||
import BankIcon from '@mui/icons-material/AccountBalanceRounded';
|
||||
import CameraIcon from '@mui/icons-material/PhotoCameraRounded';
|
||||
import WarningIcon from '@mui/icons-material/WarningAmberRounded';
|
||||
import LocationIcon from '@mui/icons-material/LocationOnRounded';
|
||||
import DeleteIcon from '@mui/icons-material/DeleteRounded';
|
||||
import CoverageIcon from '@mui/icons-material/MapRounded';
|
||||
// Catalog — nurse services surface + the customer Home service-category grid (f4/b5)
|
||||
import ServicesIcon from '@mui/icons-material/LocalOfferOutlined';
|
||||
import CategoryIcon from '@mui/icons-material/CategoryOutlined';
|
||||
import ElderlyIcon from '@mui/icons-material/ElderlyOutlined';
|
||||
import PostSurgeryIcon from '@mui/icons-material/HealingOutlined';
|
||||
import InfantIcon from '@mui/icons-material/ChildCareOutlined';
|
||||
import ChronicIcon from '@mui/icons-material/MonitorHeartOutlined';
|
||||
import CompanionshipIcon from '@mui/icons-material/VolunteerActivismOutlined';
|
||||
import ServicesIcon from '@mui/icons-material/LocalOfferRounded';
|
||||
import CategoryIcon from '@mui/icons-material/CategoryRounded';
|
||||
import ElderlyIcon from '@mui/icons-material/ElderlyRounded';
|
||||
import PostSurgeryIcon from '@mui/icons-material/HealingRounded';
|
||||
import InfantIcon from '@mui/icons-material/ChildCareRounded';
|
||||
import ChronicIcon from '@mui/icons-material/MonitorHeartRounded';
|
||||
import CompanionshipIcon from '@mui/icons-material/VolunteerActivismRounded';
|
||||
// Verification — nurse trust flow (f5/b6): document upload, credential + identity, re-upload
|
||||
import UploadIcon from '@mui/icons-material/CloudUploadOutlined';
|
||||
import DocumentIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
||||
import RefreshIcon from '@mui/icons-material/RefreshOutlined';
|
||||
import IdentityIcon from '@mui/icons-material/BadgeOutlined';
|
||||
import LicenseIcon from '@mui/icons-material/WorkspacePremiumOutlined';
|
||||
import PublishIcon from '@mui/icons-material/RocketLaunchOutlined';
|
||||
import UploadIcon from '@mui/icons-material/CloudUploadRounded';
|
||||
import DocumentIcon from '@mui/icons-material/InsertDriveFileRounded';
|
||||
import RefreshIcon from '@mui/icons-material/RefreshRounded';
|
||||
import IdentityIcon from '@mui/icons-material/BadgeRounded';
|
||||
import LicenseIcon from '@mui/icons-material/WorkspacePremiumRounded';
|
||||
import PublishIcon from '@mui/icons-material/RocketLaunchRounded';
|
||||
// Search & discovery — the customer nurse-finding flow (f6/b7): rating star, filter controls
|
||||
import StarIcon from '@mui/icons-material/Star';
|
||||
import TuneIcon from '@mui/icons-material/TuneOutlined';
|
||||
import StarIcon from '@mui/icons-material/StarRounded';
|
||||
import StarHalfIcon from '@mui/icons-material/StarHalfRounded';
|
||||
import TuneIcon from '@mui/icons-material/TuneRounded';
|
||||
import SortIcon from '@mui/icons-material/SortRounded';
|
||||
// Booking requests — the pre-payment intent flow (f7/b8): nurse inbox + the pay-&-continue handoff
|
||||
import RequestsIcon from '@mui/icons-material/AssignmentOutlined';
|
||||
import PaymentIcon from '@mui/icons-material/CreditCardOutlined';
|
||||
import RequestsIcon from '@mui/icons-material/AssignmentRounded';
|
||||
import PaymentIcon from '@mui/icons-material/CreditCardRounded';
|
||||
// Bookings, sessions & EVV — the post-payment engagement (f8/b9): check-in/out, GPS, care instructions
|
||||
import CheckInIcon from '@mui/icons-material/LoginOutlined';
|
||||
import CheckOutIcon from '@mui/icons-material/LogoutOutlined';
|
||||
import GpsIcon from '@mui/icons-material/MyLocationOutlined';
|
||||
import ScheduleIcon from '@mui/icons-material/ScheduleOutlined';
|
||||
import ClinicalIcon from '@mui/icons-material/HealthAndSafetyOutlined';
|
||||
import MedicationIcon from '@mui/icons-material/MedicationOutlined';
|
||||
import EmergencyIcon from '@mui/icons-material/LocalPhoneOutlined';
|
||||
import LockIcon from '@mui/icons-material/LockOutlined';
|
||||
import CheckInIcon from '@mui/icons-material/LoginRounded';
|
||||
import CheckOutIcon from '@mui/icons-material/LogoutRounded';
|
||||
import GpsIcon from '@mui/icons-material/MyLocationRounded';
|
||||
import ScheduleIcon from '@mui/icons-material/ScheduleRounded';
|
||||
import ClinicalIcon from '@mui/icons-material/HealthAndSafetyRounded';
|
||||
import MedicationIcon from '@mui/icons-material/MedicationRounded';
|
||||
import EmergencyIcon from '@mui/icons-material/LocalPhoneRounded';
|
||||
import PhoneIcon from '@mui/icons-material/PhoneRounded';
|
||||
import LockIcon from '@mui/icons-material/LockRounded';
|
||||
import NavigateIcon from '@mui/icons-material/NavigationRounded';
|
||||
// BNPL — the installment-checkout surface (f11/b12): installment plans + repayment schedule
|
||||
import InstallmentsIcon from '@mui/icons-material/PaymentsOutlined';
|
||||
import InstallmentsIcon from '@mui/icons-material/PaymentsRounded';
|
||||
// Payouts — the nurse earnings & payout-history surface (f12/b13)
|
||||
import EarningsIcon from '@mui/icons-material/PaidOutlined';
|
||||
import EarningsIcon from '@mui/icons-material/PaidRounded';
|
||||
// Reviews & patient care records (f13/b14): visit-note authoring, record tabs, family-ownership banner
|
||||
import NotesIcon from '@mui/icons-material/NoteAltOutlined';
|
||||
import RoutineIcon from '@mui/icons-material/EventRepeatOutlined';
|
||||
import TasksIcon from '@mui/icons-material/ChecklistOutlined';
|
||||
import HistoryIcon from '@mui/icons-material/HistoryOutlined';
|
||||
import FamilyIcon from '@mui/icons-material/FamilyRestroomOutlined';
|
||||
import NotesIcon from '@mui/icons-material/NoteAltRounded';
|
||||
import RoutineIcon from '@mui/icons-material/EventRepeatRounded';
|
||||
import TasksIcon from '@mui/icons-material/ChecklistRounded';
|
||||
import HistoryIcon from '@mui/icons-material/HistoryRounded';
|
||||
import FamilyIcon from '@mui/icons-material/FamilyRestroomRounded';
|
||||
// Messaging (tickets) & notifications (f14/b15): support inbox + the message-send action
|
||||
import SupportIcon from '@mui/icons-material/SupportAgentOutlined';
|
||||
import SendIcon from '@mui/icons-material/SendOutlined';
|
||||
import SupportIcon from '@mui/icons-material/SupportAgentRounded';
|
||||
import SendIcon from '@mui/icons-material/SendRounded';
|
||||
// Admin backoffice & partner consoles (f15/b15): config, holidays, audit, alerts, moderation, partners, refunds
|
||||
import ConfigIcon from '@mui/icons-material/TuneOutlined';
|
||||
import CalendarIcon from '@mui/icons-material/CalendarMonthOutlined';
|
||||
import AuditIcon from '@mui/icons-material/FactCheckOutlined';
|
||||
import AlertsIcon from '@mui/icons-material/NotificationImportantOutlined';
|
||||
import ModerationIcon from '@mui/icons-material/GavelOutlined';
|
||||
import PartnersIcon from '@mui/icons-material/ApartmentOutlined';
|
||||
import RolesIcon from '@mui/icons-material/ManageAccountsOutlined';
|
||||
import RefundsIcon from '@mui/icons-material/CurrencyExchangeOutlined';
|
||||
import DownloadIcon from '@mui/icons-material/FileDownloadOutlined';
|
||||
import ExpandIcon from '@mui/icons-material/ExpandMoreOutlined';
|
||||
import ExternalIcon from '@mui/icons-material/OpenInNewOutlined';
|
||||
import AssignIcon from '@mui/icons-material/AssignmentIndOutlined';
|
||||
import CalendarIcon from '@mui/icons-material/CalendarMonthRounded';
|
||||
import AuditIcon from '@mui/icons-material/FactCheckRounded';
|
||||
import AlertsIcon from '@mui/icons-material/NotificationImportantRounded';
|
||||
import ModerationIcon from '@mui/icons-material/GavelRounded';
|
||||
import PartnersIcon from '@mui/icons-material/ApartmentRounded';
|
||||
import RolesIcon from '@mui/icons-material/ManageAccountsRounded';
|
||||
import RefundsIcon from '@mui/icons-material/CurrencyExchangeRounded';
|
||||
import DownloadIcon from '@mui/icons-material/FileDownloadRounded';
|
||||
import ExpandIcon from '@mui/icons-material/ExpandMoreRounded';
|
||||
import ExternalIcon from '@mui/icons-material/OpenInNewRounded';
|
||||
import AssignIcon from '@mui/icons-material/AssignmentIndRounded';
|
||||
// Cross-cutting affordances: back navigation, share/copy, attachments
|
||||
import BackIcon from '@mui/icons-material/ArrowBackRounded';
|
||||
import ShareIcon from '@mui/icons-material/ShareRounded';
|
||||
import CopyIcon from '@mui/icons-material/ContentCopyRounded';
|
||||
import AttachmentIcon from '@mui/icons-material/AttachFileRounded';
|
||||
|
||||
/**
|
||||
* List of all available Icon names
|
||||
@@ -112,21 +114,13 @@ export type IconName = keyof typeof ICONS;
|
||||
*/
|
||||
export const ICONS /* Note: Setting type disables property autocomplete :( was - : Record<string, ComponentType> */ = {
|
||||
default: DefaultIcon,
|
||||
logo: PencilIcon,
|
||||
logo: LogoMark,
|
||||
close: CloseIcon,
|
||||
menu: MenuIcon,
|
||||
settings: SettingsIcon,
|
||||
visibilityon: VisibilityIcon,
|
||||
visibilityoff: VisibilityOffIcon,
|
||||
daynight: DayNightIcon,
|
||||
night: NightIcon,
|
||||
day: DayIcon,
|
||||
search: SearchIcon,
|
||||
info: InfoIcon,
|
||||
home: HomeIcon,
|
||||
account: AccountCircle,
|
||||
signup: PersonAddIcon,
|
||||
login: PersonIcon,
|
||||
account: AccountCircleIcon,
|
||||
logout: ExitToAppIcon,
|
||||
notifications: NotificationsIcon,
|
||||
error: DangerousIcon,
|
||||
@@ -134,7 +128,7 @@ export const ICONS /* Note: Setting type disables property autocomplete :( was -
|
||||
patients: GroupsIcon,
|
||||
users: PeopleAltIcon,
|
||||
wallet: WalletIcon,
|
||||
profile: PersonOutlineIcon,
|
||||
profile: AccountCircleIcon,
|
||||
dashboard: DashboardIcon,
|
||||
verification: VerifiedUserIcon,
|
||||
verified: CheckCircleIcon,
|
||||
@@ -165,7 +159,9 @@ export const ICONS /* Note: Setting type disables property autocomplete :( was -
|
||||
license: LicenseIcon,
|
||||
publish: PublishIcon,
|
||||
star: StarIcon,
|
||||
star_half: StarHalfIcon,
|
||||
tune: TuneIcon,
|
||||
sort: SortIcon,
|
||||
requests: RequestsIcon,
|
||||
payment: PaymentIcon,
|
||||
check_in: CheckInIcon,
|
||||
@@ -175,7 +171,9 @@ export const ICONS /* Note: Setting type disables property autocomplete :( was -
|
||||
clinical: ClinicalIcon,
|
||||
medication: MedicationIcon,
|
||||
emergency: EmergencyIcon,
|
||||
phone: PhoneIcon,
|
||||
lock: LockIcon,
|
||||
navigate: NavigateIcon,
|
||||
installments: InstallmentsIcon,
|
||||
earnings: EarningsIcon,
|
||||
notes: NotesIcon,
|
||||
@@ -185,7 +183,7 @@ export const ICONS /* Note: Setting type disables property autocomplete :( was -
|
||||
family: FamilyIcon,
|
||||
support: SupportIcon,
|
||||
send: SendIcon,
|
||||
config: ConfigIcon,
|
||||
config: TuneIcon,
|
||||
calendar: CalendarIcon,
|
||||
audit: AuditIcon,
|
||||
alerts: AlertsIcon,
|
||||
@@ -197,4 +195,16 @@ export const ICONS /* Note: Setting type disables property autocomplete :( was -
|
||||
expand: ExpandIcon,
|
||||
external: ExternalIcon,
|
||||
assign: AssignIcon,
|
||||
back: BackIcon,
|
||||
chevron_start: BackIcon,
|
||||
share: ShareIcon,
|
||||
copy: CopyIcon,
|
||||
attachment: AttachmentIcon,
|
||||
};
|
||||
|
||||
/**
|
||||
* Icons authored for LTR that must mirror horizontally under RTL (back arrows,
|
||||
* chevrons pointing "start"). AppIcon applies the flip via a `data-icon-directional`
|
||||
* attribute + the single CSS rule in globals.css — add a name here, nothing else.
|
||||
*/
|
||||
export const DIRECTIONAL_ICONS = new Set<IconName>(['back', 'chevron_start']);
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
/**
|
||||
* The full-color Balinyaar mark: deep-teal rounded-square ground, cream
|
||||
* lowercase "b" glyph, single terracotta dot — the brand identity described
|
||||
* in the frontend-designer skill. Colors are `var(--bal-*)` tokens (never a
|
||||
* hex literal), so the mark tracks the color scheme like everything else.
|
||||
* Used directly by BrandMark next to the translated wordmark — the wordmark
|
||||
* stays real `<Typography>` text (not baked into the SVG) so it still
|
||||
* switches with the locale. For an inline, single-color icon (e.g. inside a
|
||||
* button) use AppIcon's `logo` name (LogoMark) instead.
|
||||
* @component LogoLockup
|
||||
*/
|
||||
const LogoLockup: FunctionComponent<IconProps> = ({ size = 56, ...props }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40" width={size} height={size} {...props}>
|
||||
<rect width="40" height="40" rx="10" fill="var(--bal-primary)" />
|
||||
<path
|
||||
fill="var(--bal-primary-contrast)"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M21,17 A7.5,7.5 0 1,0 21,32 A7.5,7.5 0 1,0 21,17 Z
|
||||
M21,20.8 A3.7,3.7 0 1,0 21,28.2 A3.7,3.7 0 1,0 21,20.8 Z"
|
||||
/>
|
||||
<rect x="12.9" y="9" width="4.8" height="22" rx="2.4" fill="var(--bal-primary-contrast)" />
|
||||
<circle cx="28.5" cy="10.5" r="2.6" fill="var(--bal-secondary)" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default LogoLockup;
|
||||
@@ -0,0 +1,28 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
/**
|
||||
* The Balinyaar monochrome mark — a lowercase "b" (stem + bowl) reduced to a
|
||||
* single `currentColor` fill so it works like any other AppIcon: it recolors
|
||||
* via the `color` prop and therefore for free across the light/dark scheme.
|
||||
* The bowl's counter is a true transparent hole (evenodd fill-rule on two
|
||||
* concentric circles), not a background-color cutout — it reads correctly on
|
||||
* any surface. For the full-color brand lockup (auth splash, TopBar) see
|
||||
* LogoLockup.tsx instead.
|
||||
* @component LogoMark
|
||||
*/
|
||||
const LogoMark: FunctionComponent<IconProps> = ({ color = 'currentColor', ...props }) => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" {...props}>
|
||||
<path
|
||||
fill={color}
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M19.5,15.3 A7.2,7.2 0 1,0 19.5,29.7 A7.2,7.2 0 1,0 19.5,15.3 Z
|
||||
M19.5,18.9 A3.6,3.6 0 1,0 19.5,26.1 A3.6,3.6 0 1,0 19.5,18.9 Z"
|
||||
/>
|
||||
<rect x="10.9" y="8" width="4.6" height="21" rx="2.3" fill={color} />
|
||||
<circle cx="27" cy="10" r="2.4" fill={color} />
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default LogoMark;
|
||||
@@ -1,29 +0,0 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
const PencilIcon: FunctionComponent<IconProps> = (props) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" {...props}>
|
||||
<path
|
||||
fill="#D99E82"
|
||||
d="M35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA596E"
|
||||
d="M13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0L1.141 9.475c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0l4.168 4.167z"
|
||||
/>
|
||||
<path fill="#FFCC4D" d="M31.353 23.018l-4.17 4.17-4.163 4.165L7.392 15.726l8.335-8.334 15.626 15.626z" />
|
||||
<path
|
||||
fill="#292F33"
|
||||
d="M32.078 34.763s2.709 1.489 3.441.757c.732-.732-.765-3.435-.765-3.435s-2.566.048-2.676 2.678z"
|
||||
/>
|
||||
<path fill="#CCD6DD" d="M2.183 10.517l8.335-8.335 5.208 5.209-8.334 8.335z" />
|
||||
<path
|
||||
fill="#99AAB5"
|
||||
d="M3.225 11.558l8.334-8.334 1.042 1.042L4.267 12.6zm2.083 2.086l8.335-8.335 1.042 1.042-8.335 8.334z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -36,9 +36,10 @@ describe('<AppIconButton/> component', () => {
|
||||
const svg = button.querySelector('svg');
|
||||
expect(svg).toBeDefined();
|
||||
expect(svg).toHaveAttribute('data-icon', 'default'); // default icon
|
||||
expect(svg).toHaveAttribute('size', String(APP_ICON_SIZE)); // default size
|
||||
expect(svg).toHaveAttribute('height', String(APP_ICON_SIZE)); // default size when .size is not set
|
||||
expect(svg).toHaveAttribute('width', String(APP_ICON_SIZE)); // default size when .size is not se
|
||||
// Size is actually driven by fontSize, not a `size` DOM attribute (invalid on <svg>).
|
||||
expect(svg).toHaveStyle(`font-size: ${APP_ICON_SIZE}px`);
|
||||
});
|
||||
|
||||
it('supports .color property', () => {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { randomText } from '@/utils';
|
||||
import AppImage from './AppImage';
|
||||
|
||||
const ComponentToTest = AppImage;
|
||||
|
||||
/**
|
||||
* Tests for <AppImage/> component
|
||||
*/
|
||||
describe('<AppImage/> component', () => {
|
||||
const src = 'https:/domain.com/image.jpg';
|
||||
|
||||
it('renders itself', () => {
|
||||
const testId = randomText(8);
|
||||
render(<ComponentToTest data-testid={testId} src={src} />);
|
||||
const image = screen.getByTestId(testId);
|
||||
expect(image).toBeDefined();
|
||||
expect(image).toHaveAttribute('src', src);
|
||||
expect(image).toHaveAttribute('alt', 'Image'); // Default prop value
|
||||
expect(image).toHaveAttribute('height', '256'); // Default prop value
|
||||
expect(image).toHaveAttribute('width', '256'); // Default prop value
|
||||
});
|
||||
|
||||
it('supports .width and .height props', () => {
|
||||
const testId = randomText(8);
|
||||
const height = 345;
|
||||
const width = 123;
|
||||
render(<ComponentToTest data-testid={testId} height={height} src={src} width={width} />);
|
||||
const image = screen.getByTestId(testId);
|
||||
expect(image).toBeDefined();
|
||||
expect(image).toHaveAttribute('height', String(height));
|
||||
expect(image).toHaveAttribute('width', String(width));
|
||||
});
|
||||
|
||||
it('supports .title property', () => {
|
||||
const testId = randomText(8);
|
||||
const title = randomText(16);
|
||||
render(<ComponentToTest data-testid={testId} src={src} title={title} />);
|
||||
const image = screen.getByTestId(testId);
|
||||
expect(image).toBeDefined();
|
||||
expect(image).toHaveAttribute('title', title);
|
||||
expect(image).toHaveAttribute('alt', title); // When title is provided, it is used as alt
|
||||
});
|
||||
|
||||
it('supports .alt property even when .title is provided', () => {
|
||||
const testId = randomText(8);
|
||||
const title = randomText(16);
|
||||
const alt = randomText(32);
|
||||
render(<ComponentToTest alt={alt} data-testid={testId} src={src} title={title} />);
|
||||
const image = screen.getByTestId(testId);
|
||||
expect(image).toBeDefined();
|
||||
expect(image).toHaveAttribute('alt', alt);
|
||||
expect(image).toHaveAttribute('title', title);
|
||||
});
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import NextImage, { ImageProps } from 'next/image';
|
||||
|
||||
interface AppImageProps extends Omit<ImageProps, 'alt'> {
|
||||
alt?: string; // Make property optional as it was before NextJs v13
|
||||
}
|
||||
|
||||
/**
|
||||
* Application wrapper around NextJS image with some default props
|
||||
* @component AppImage
|
||||
*/
|
||||
const AppImage: FunctionComponent<AppImageProps> = ({
|
||||
title, // Note: value has be destructed before usage as default value for other property
|
||||
alt = title ?? 'Image',
|
||||
height = 256,
|
||||
width = 256,
|
||||
...restOfProps
|
||||
}) => {
|
||||
// Uses custom loader + unoptimized="true" to avoid NextImage warning https://nextjs.org/docs/api-reference/next/image#unoptimized
|
||||
return <NextImage alt={alt} height={height} title={title} unoptimized={true} width={width} {...restOfProps} />;
|
||||
};
|
||||
|
||||
export default AppImage;
|
||||
@@ -1,3 +0,0 @@
|
||||
import AppImage from './AppImage';
|
||||
|
||||
export { AppImage as default, AppImage };
|
||||
@@ -2,9 +2,8 @@ import AppAlert from './AppAlert';
|
||||
import AppButton from './AppButton';
|
||||
import AppIcon from './AppIcon';
|
||||
import AppIconButton from './AppIconButton';
|
||||
import AppImage from './AppImage';
|
||||
import AppLink from './AppLink';
|
||||
import AppLoading from './AppLoading';
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
export { ErrorBoundary, AppAlert, AppButton, AppIcon, AppIconButton, AppImage, AppLink, AppLoading };
|
||||
export { ErrorBoundary, AppAlert, AppButton, AppIcon, AppIconButton, AppLink, AppLoading };
|
||||
|
||||
Reference in New Issue
Block a user