mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: accept a calendar hex colour with or without # and show it with # after typing
This commit is contained in:
@@ -76,6 +76,7 @@
|
||||
"clear_confirm": "Alle entfernen",
|
||||
"clear_description": "Alle Termine in diesem Kalender werden gelöscht. Termine, die auch zu einem anderen Kalender gehören, werden nur aus diesem entfernt.",
|
||||
"clear_title": "Alle Termine entfernen?",
|
||||
"color_invalid": "Geben Sie eine Farbe als sechs Hex-Ziffern ein, mit oder ohne #, etwa #3b82f6.",
|
||||
"delete_description": "Der Kalender und alle seine Termine werden gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_title": "Kalender löschen?",
|
||||
"description_field": "Beschreibung",
|
||||
|
||||
+1
-1
@@ -95,7 +95,7 @@
|
||||
"clear_confirm": "Remove all",
|
||||
"clear_description": "Every event in this calendar will be deleted. Events that also belong to another calendar are only unlinked.",
|
||||
"clear_title": "Remove all events?",
|
||||
"color_invalid": "Enter a color as # and six hex digits, such as #3b82f6.",
|
||||
"color_invalid": "Enter a color as six hex digits, with or without #, such as #3b82f6.",
|
||||
"custom_color": "Custom color (hex)",
|
||||
"delete_description": "The calendar and all of its events will be deleted. This cannot be undone.",
|
||||
"delete_title": "Delete calendar?",
|
||||
|
||||
@@ -27,7 +27,7 @@ import { radius, spacing, typography, type ThemePalette } from '../../theme/toke
|
||||
import { useColors } from '../../theme/colors';
|
||||
import { useLocaleStore, type TranslateFn } from '../../stores/locale-store';
|
||||
import { CALENDAR_COLOR_PALETTE, calendarColorName, getCalendarColor } from '../../lib/calendar-utils';
|
||||
import { customColorGrid, normalizeHexColor } from '../../lib/calendar-custom-color';
|
||||
import { customColorGrid, HEX_FIELD_MAX_LENGTH, hexFieldText, normalizeHexColor } from '../../lib/calendar-custom-color';
|
||||
|
||||
const CUSTOM_COLOR_GRID = customColorGrid();
|
||||
import Button from '../Button';
|
||||
@@ -260,11 +260,14 @@ export function CalendarEditSheet({ visible, calendar, onSave, onClose }: Calend
|
||||
const hex = normalizeHexColor(v);
|
||||
if (hex) setColor(hex);
|
||||
}}
|
||||
// "3b82f6" works as well as "#3b82f6"; leaving the field
|
||||
// shows it with its "#".
|
||||
onBlur={() => setHexInput((v) => hexFieldText(v))}
|
||||
placeholder="#3b82f6"
|
||||
placeholderTextColor={c.textMuted}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
maxLength={7}
|
||||
maxLength={HEX_FIELD_MAX_LENGTH}
|
||||
accessibilityLabel={t('calendar.management.custom_color', 'Custom color (hex)')}
|
||||
style={[styles.input, styles.hexInput, hexInvalid && styles.inputInvalid]}
|
||||
/>
|
||||
@@ -283,7 +286,7 @@ export function CalendarEditSheet({ visible, calendar, onSave, onClose }: Calend
|
||||
</View>
|
||||
{hexInvalid && (
|
||||
<Text style={styles.error}>
|
||||
{t('calendar.management.color_invalid', 'Enter a color as # and six hex digits, such as #3b82f6.')}
|
||||
{t('calendar.management.color_invalid', 'Enter a color as six hex digits, with or without #, such as #3b82f6.')}
|
||||
</Text>
|
||||
)}
|
||||
{moreColors && (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { customColorGrid, normalizeHexColor } from '../calendar-custom-color';
|
||||
import { customColorGrid, HEX_FIELD_MAX_LENGTH, hexFieldText, normalizeHexColor } from '../calendar-custom-color';
|
||||
|
||||
describe('normalizeHexColor', () => {
|
||||
it('takes a typed hex colour in its usual spellings', () => {
|
||||
@@ -23,3 +23,31 @@ describe('customColorGrid', () => {
|
||||
expect(grid[1][0]).toBe('#c32222');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the hex field', () => {
|
||||
it('shows a colour typed without "#" with it once the user leaves the field', () => {
|
||||
expect(hexFieldText('3b82f6')).toBe('#3b82f6');
|
||||
expect(hexFieldText('3B82F6')).toBe('#3b82f6');
|
||||
expect(hexFieldText('#3b82f6')).toBe('#3b82f6');
|
||||
expect(hexFieldText('3b8')).toBe('#33bb88');
|
||||
// Not a colour: left as typed for the hint under the field.
|
||||
expect(hexFieldText('3b82f')).toBe('3b82f');
|
||||
});
|
||||
|
||||
it('takes a pasted colour with spaces around it', () => {
|
||||
const pasted = ' #3b82f6 ';
|
||||
expect(pasted.length).toBeLessThanOrEqual(HEX_FIELD_MAX_LENGTH);
|
||||
expect(normalizeHexColor(pasted.slice(0, HEX_FIELD_MAX_LENGTH))).toBe('#3b82f6');
|
||||
});
|
||||
});
|
||||
|
||||
describe('the calendar sheet’s hex field', () => {
|
||||
it('spells the colour out on blur and takes the longer pasted text', async () => {
|
||||
const fs = await import('node:fs');
|
||||
const path = await import('node:path');
|
||||
const sheet = fs.readFileSync(path.resolve(__dirname, '../../components/calendar/CalendarEditSheet.tsx'), 'utf8');
|
||||
const field = sheet.slice(sheet.indexOf('value={hexInput}'), sheet.indexOf('/>', sheet.indexOf('value={hexInput}')));
|
||||
expect(field).toMatch(/onBlur=\{\(\) => setHexInput\(\(v\) => hexFieldText\(v\)\)\}/);
|
||||
expect(field).toMatch(/maxLength=\{HEX_FIELD_MAX_LENGTH\}/);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,21 @@ export function normalizeHexColor(input: string): string | null {
|
||||
return `#${hex.toLowerCase()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the hex field shows once the user leaves it: a colour typed without
|
||||
* its "#", in capitals or as three digits, spelled out as "#rrggbb"; anything
|
||||
* else stays as typed, so the hint under the field still applies to it.
|
||||
*/
|
||||
export function hexFieldText(input: string): string {
|
||||
return normalizeHexColor(input) ?? input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Longest text the hex field takes: "#rrggbb" plus a space either side, as a
|
||||
* pasted value can carry. Seven cut a pasted " #3b82f6" to an invalid colour.
|
||||
*/
|
||||
export const HEX_FIELD_MAX_LENGTH = 9;
|
||||
|
||||
function hslToHex(h: number, s: number, l: number): string {
|
||||
const a = s * Math.min(l, 1 - l);
|
||||
const f = (n: number) => {
|
||||
|
||||
Reference in New Issue
Block a user