-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add colour support for tag configs #16109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,8 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; | |||||||||||||||||||||||||||||||||||||||||||||||||
| import RoleOrgSelector from "@/components/Common/RoleOrgSelector"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import FacilityOrganizationSelector from "@/pages/Facility/settings/organizations/components/FacilityOrganizationSelector"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Badge } from "@/components/ui/badge"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from "@/components/ui/button"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Form, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,6 +31,19 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||
| } from "@/components/ui/select"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { Textarea } from "@/components/ui/textarea"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| const TAG_COLORS = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#B91C1C", // Red | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#C2410C", // Orange | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#A16207", // Amber | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#15803D", // Green | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#0F766E", // Teal | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#1D4ED8", // Blue | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#7E22CE", // Purple | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#BE185D", // Pink | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#0E7490", // Cyan | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "#4D7C0F", // Lime | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ] as const; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+45
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Verify the contrast ratios using a simple Python script
pip install wcag-contrast-ratio 2>/dev/null
python3 << 'EOF'
# Calculate contrast ratios for TAG_COLORS against their tinted backgrounds
# The background is the color with 40% opacity (25% actual due to hex)
# approximated by blending with white
def hex_to_rgb(hex_color):
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
def blend_with_white(rgb, alpha):
"""Blend color with white background at given alpha"""
return tuple(int(c * alpha + 255 * (1 - alpha)) for c in rgb)
def relative_luminance(rgb):
"""Calculate relative luminance per WCAG"""
def adjust(c):
c = c / 255
return c / 12.92 if c <= 0.03928 else ((c + 0.055) / 1.055) ** 2.4
r, g, b = [adjust(c) for c in rgb]
return 0.2126 * r + 0.7152 * g + 0.0722 * b
def contrast_ratio(l1, l2):
lighter = max(l1, l2)
darker = min(l1, l2)
return (lighter + 0.05) / (darker + 0.05)
TAG_COLORS = [
("#B91C1C", "Red"),
("#C2410C", "Orange"),
("#A16207", "Amber"),
("#15803D", "Green"),
("#0F766E", "Teal"),
("#1D4ED8", "Blue"),
("#7E22CE", "Purple"),
("#BE185D", "Pink"),
("#0E7490", "Cyan"),
("#4D7C0F", "Lime"),
]
print("Color Contrast Analysis (text color vs 40% opacity background on white):")
print("=" * 70)
for color, name in TAG_COLORS:
fg_rgb = hex_to_rgb(color)
# 0x40 = 64 in decimal, 64/255 ≈ 0.25 alpha
bg_rgb = blend_with_white(fg_rgb, 0.25)
fg_lum = relative_luminance(fg_rgb)
bg_lum = relative_luminance(bg_rgb)
ratio = contrast_ratio(fg_lum, bg_lum)
status = "✓ PASS" if ratio >= 4.5 else "✗ FAIL"
print(f"{name:8} {color}: {ratio:.2f}:1 {status} (AA requires 4.5:1)")
EOFRepository: ohcnetwork/care_fe Length of output: 1618 🏁 Script executed: fd "TagBadge" --type fRepository: ohcnetwork/care_fe Length of output: 94 🏁 Script executed: cat -n src/components/Tags/TagBadge.tsxRepository: ohcnetwork/care_fe Length of output: 1548 Most colors fail WCAG AA contrast requirements and must be adjusted. Verification confirms that 9 out of 10 colors fail to meet WCAG AA 4.5:1 contrast ratio when used as text on their 40% opacity backgrounds:
This violates accessibility requirements for clinical environments. Consider using lighter or darker colors to achieve sufficient contrast, or implement an alternative approach such as using white/black text with these colored backgrounds instead. 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+34
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Move constant definition after all imports. The ♻️ Suggested fixMove lines 34-46 to after line 58 (after all imports), before the interface definition: import { isIOSDevice } from "@/Utils/utils";
+const TAG_COLORS = [
+ "#B91C1C", // Red
+ "#C2410C", // Orange
+ "#A16207", // Amber
+ "#15803D", // Green
+ "#0F766E", // Teal
+ "#1D4ED8", // Blue
+ "#7E22CE", // Purple
+ "#BE185D", // Pink
+ "#0E7490", // Cyan
+ "#4D7C0F", // Lime
+] as const;
+
interface TagConfigFormProps {And remove lines 34-46 from their current location. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| import useBreakpoints from "@/hooks/useBreakpoints"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
| import { cn } from "@/lib/utils"; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+45
to
48
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -76,6 +91,7 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| facility_organization: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| organization: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| color: z.string().optional(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
92
to
95
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| type TagConfigFormValues = z.infer<typeof tagConfigSchema>; | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -101,6 +117,7 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| resource: parentTag?.resource || TagResource.PATIENT, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| facility_organization: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| organization: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| color: "", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -126,6 +143,10 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| resource: existingConfig.resource, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| facility_organization: existingConfig.facility_organization?.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| organization: existingConfig.organization?.id, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| color: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| typeof existingConfig.meta?.color === "string" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ? existingConfig.meta.color | ||||||||||||||||||||||||||||||||||||||||||||||||||
| : "", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [existingConfig, isEditing, form]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -142,6 +163,7 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| resource: parentTag.resource, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| facility_organization: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| organization: undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| color: "", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, [parentTag, isCreatingChild, form]); | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -182,6 +204,7 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| priority: data.priority, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| status: data.status, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| resource: data.resource, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| meta: data.color ? { color: data.color } : {}, | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check what fields TagConfigMeta interface contains
ast-grep --pattern 'interface TagConfigMeta {
$$$
}'
# Also check for type definition
rg -n "TagConfigMeta" --type=ts -A 5Repository: ohcnetwork/care_fe Length of output: 1320 🏁 Script executed: sed -n '190,230p' src/pages/Admin/TagConfig/TagConfigForm.tsxRepository: ohcnetwork/care_fe Length of output: 1284 🏁 Script executed: sed -n '1,210p' src/pages/Admin/TagConfig/TagConfigForm.tsx | head -100Repository: ohcnetwork/care_fe Length of output: 2925 🏁 Script executed: sed -n '100,160p' src/pages/Admin/TagConfig/TagConfigForm.tsxRepository: ohcnetwork/care_fe Length of output: 2095 🏁 Script executed: rg "meta\s*:\s*\{" --type=ts -B 2 -A 2 src/pages/Admin/TagConfig src/types/emr/tagConfigRepository: ohcnetwork/care_fe Length of output: 373 🏁 Script executed: rg "\.meta\?" --type=ts src/pages/Admin/TagConfig src/types/emr/tagConfig -B 1 -A 1Repository: ohcnetwork/care_fe Length of output: 305 🏁 Script executed: rg "meta:" --type=ts -A 3 src/types/emr/tagConfig/ | head -40Repository: ohcnetwork/care_fe Length of output: 510 🏁 Script executed: rg "color" --type=ts src/pages/Admin/TagConfig -B 2 -A 2Repository: ohcnetwork/care_fe Length of output: 3606 Preserve existing While currently only the Although this isn't causing data loss today, consider preserving existing meta fields for robustness: Proposed fix to preserve existing meta- meta: data.color ? { color: data.color } : {},
+ meta: {
+ ...(isEditing && existingConfig?.meta ? existingConfig.meta : {}),
+ ...(data.color ? { color: data.color } : {}),
+ },🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(parentId && { parent: parentId }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
204
to
208
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(facilityId && { facility: facilityId }), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ...(data.facility_organization && { | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -358,6 +381,61 @@ export default function TagConfigForm({ | |||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| name="color" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| render={({ field }) => ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormLabel>{t("color")}</FormLabel> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <input type="hidden" {...field} value={field.value || ""} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormControl> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| variant="outline" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| size="sm" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| const others = TAG_COLORS.filter((c) => c !== field.value); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| field.onChange( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| others[Math.floor(Math.random() * others.length)], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={isLoading} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <CareIcon icon="l-shuffle" className="size-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {field.value ? t("shuffle_color") : t("assign_color")} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {field.value && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <Badge | ||||||||||||||||||||||||||||||||||||||||||||||||||
| style={{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| color: field.value, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor: field.value + "18", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| borderColor: field.value + "40", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+412
to
+416
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent opacity values with The badge preview uses different opacity suffixes ( Based on the relevant code snippet from 🔧 Proposed fix for consistency <Badge
style={{
color: field.value,
- backgroundColor: field.value + "18",
- borderColor: field.value + "40",
+ backgroundColor: field.value + "40",
+ borderColor: field.value + "60",
}}
className="capitalize"
>📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| className="capitalize" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||
| {form.watch("display") || t("preview")} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </Badge> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| variant="ghost" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| size="icon" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| className="size-7 text-muted-foreground" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={() => field.onChange("")} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={isLoading} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <CareIcon icon="l-times" className="size-4" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+421
to
+430
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add accessible label to icon-only clear button. The clear button uses only an icon without visible text or an accessible label, making it inaccessible to screen reader users. As per coding guidelines, proper ARIA labels should be implemented for accessibility. ♿ Proposed fix <Button
type="button"
variant="ghost"
size="icon"
className="size-7 text-muted-foreground"
onClick={() => field.onChange("")}
disabled={isLoading}
+ aria-label={t("clear_color")}
>
<CareIcon icon="l-times" className="size-4" />
</Button>Note: You may need to add the 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormMessage /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| </FormItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| {facilityId ? ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| <FormField | ||||||||||||||||||||||||||||||||||||||||||||||||||
| control={form.control} | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move newly added i18n keys to the end of
en.json.Both keys were inserted inline; this repository requires new keys in
public/locale/en.jsonto be appended at the end of the file.🔧 Proposed fix
As per coding guidelines: "Append missing i18n keys directly to the end of the
public/locale/en.jsonfile without reading i18n files".Also applies to: 5509-5509
🤖 Prompt for AI Agents