Picker: fix the dark theme styles

This commit is contained in:
2025-10-12 21:45:45 +03:00
parent 218e5d143b
commit 74c15aff03
5 changed files with 313 additions and 49 deletions

View File

@@ -47,13 +47,17 @@ export default function DatePickerBottomSheet({
label: (i + 1).toString(),
}));
const years = Array.from({ length: 21 }, (_, i) => {
const year = new Date().getFullYear() - 10 + i;
return {
value: year.toString(),
label: year.toString(),
};
});
const currentYear = new Date().getFullYear();
const years = Array.from(
{ length: currentYear - 10 - 1900 + 1 },
(_, i) => {
const year = 1900 + i;
return {
value: year.toString(),
label: year.toString(),
};
},
);
return {
month: months,
@@ -145,10 +149,12 @@ export default function DatePickerBottomSheet({
style={{
position: "relative",
display: "flex",
backgroundColor: "#ffffff",
backgroundColor: "var(--secondary-background)",
overflow: "hidden",
width: "100%",
height: 250,
border: "1px solid var(--border)",
borderRadius: "8px",
}}
aria-selected={false}
>
@@ -156,8 +162,8 @@ export default function DatePickerBottomSheet({
name="month"
style={{
flex: 1,
borderRight: "1px solid #e5e7eb",
backgroundColor: "#ffffff",
borderRight: "1px solid var(--border)",
backgroundColor: "var(--secondary-background)",
}}
>
{pickerOptions.month.map((option) => (
@@ -166,16 +172,22 @@ export default function DatePickerBottomSheet({
<div
style={{
fontWeight: selected ? "600" : "400",
color: selected ? "#171717" : "#9ca3af",
color: selected
? "var(--foreground)"
: "var(--text-color-gray)",
fontSize: "16px",
padding: "9px 0",
textAlign: "center",
opacity: selected ? 1 : 0.6,
opacity: selected ? 1 : 0.7,
transition: "all 0.2s ease",
lineHeight: "1.2",
height: 36,
width: "100%",
backgroundColor: selected ? "#f3f4f6" : "transparent",
backgroundColor: selected
? "var(--background)"
: "transparent",
borderRadius: selected ? "4px" : "0",
margin: selected ? "2px 4px" : "0",
}}
>
{option.label}
@@ -188,8 +200,8 @@ export default function DatePickerBottomSheet({
name="day"
style={{
flex: 1,
borderRight: "1px solid #e5e7eb",
backgroundColor: "#ffffff",
borderRight: "1px solid var(--border)",
backgroundColor: "var(--secondary-background)",
}}
>
{pickerOptions.day.map((option) => (
@@ -198,16 +210,22 @@ export default function DatePickerBottomSheet({
<div
style={{
fontWeight: selected ? "600" : "400",
color: selected ? "#171717" : "#9ca3af",
color: selected
? "var(--foreground)"
: "var(--text-color-gray)",
fontSize: "16px",
padding: "9px 0",
textAlign: "center",
opacity: selected ? 1 : 0.6,
opacity: selected ? 1 : 0.7,
transition: "all 0.2s ease",
lineHeight: "1.2",
height: 36,
width: "100%",
backgroundColor: selected ? "#f3f4f6" : "transparent",
backgroundColor: selected
? "var(--background)"
: "transparent",
borderRadius: selected ? "4px" : "0",
margin: selected ? "2px 4px" : "0",
}}
>
{option.label}
@@ -220,7 +238,7 @@ export default function DatePickerBottomSheet({
name="year"
style={{
flex: 1,
backgroundColor: "#ffffff",
backgroundColor: "var(--secondary-background)",
}}
>
{pickerOptions.year.map((option) => (
@@ -229,16 +247,22 @@ export default function DatePickerBottomSheet({
<div
style={{
fontWeight: selected ? "600" : "400",
color: selected ? "#171717" : "#9ca3af",
color: selected
? "var(--foreground)"
: "var(--text-color-gray)",
fontSize: "16px",
padding: "9px 0",
textAlign: "center",
opacity: selected ? 1 : 0.6,
opacity: selected ? 1 : 0.7,
transition: "all 0.2s ease",
lineHeight: "1.2",
height: 36,
width: "100%",
backgroundColor: selected ? "#f3f4f6" : "transparent",
backgroundColor: selected
? "var(--background)"
: "transparent",
borderRadius: selected ? "4px" : "0",
margin: selected ? "2px 4px" : "0",
}}
>
{option.label}

View File

@@ -0,0 +1,224 @@
/*
* Picker Component Theme-Aware Styling
*
* This CSS module provides enhanced dark theme support for the Picker component.
* Features include:
* - Automatic theme detection using CSS variables
* - Enhanced visual feedback for selected items
* - Smooth animations and transitions
* - Accessibility improvements (focus states, high contrast)
* - Responsive design for mobile devices
* - Reduced motion support for accessibility
*
* The styling automatically adapts to both light and dark themes using CSS variables
* defined in the application's theme system.
*/
.pickerContainer {
position: relative;
display: flex;
justify-content: center;
overflow: hidden;
background-color: var(--secondary-background);
border: 1px solid var(--border);
border-radius: 8px;
transition: all 0.2s ease;
}
.pickerContainer:hover {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary);
}
.pickerColumn {
flex: 1;
border-right: 1px solid var(--border);
background-color: var(--secondary-background);
transition: all 0.2s ease;
}
.pickerColumn:last-child {
border-right: none;
}
.pickerItem {
height: 36px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
transition: all 0.2s ease;
border-radius: 4px;
margin: 2px 4px;
}
.pickerItem:hover {
background-color: var(--background);
transform: scale(1.02);
}
.pickerItem.selected {
font-weight: 600;
color: var(--foreground);
opacity: 1;
background-color: var(--background);
border-radius: 4px;
margin: 2px 4px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.pickerItem.unselected {
font-weight: 400;
color: var(--text-color-gray);
opacity: 0.7;
background-color: transparent;
border-radius: 0;
margin: 0;
}
.pickerItemText {
font-size: 16px;
padding: 9px 0;
text-align: center;
line-height: 1.2;
width: 100%;
transition: all 0.2s ease;
}
/* Dark theme specific enhancements */
[data-theme="dark"] .pickerContainer {
background-color: var(--secondary-background);
border-color: var(--border);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .pickerContainer:hover {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary), 0 4px 12px rgba(0, 0, 0, 0.4);
}
[data-theme="dark"] .pickerColumn {
background-color: var(--secondary-background);
border-color: var(--border);
}
[data-theme="dark"] .pickerItem.selected {
background-color: var(--background);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
color: var(--foreground);
}
[data-theme="dark"] .pickerItem:hover {
background-color: var(--background);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}
[data-theme="dark"] .pickerItem.unselected {
color: var(--text-color-gray);
}
/* Light theme specific enhancements */
[data-theme="light"] .pickerContainer {
background-color: var(--secondary-background);
border-color: var(--border);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
[data-theme="light"] .pickerContainer:hover {
border-color: var(--primary);
box-shadow: 0 0 0 1px var(--primary), 0 4px 8px rgba(0, 0, 0, 0.15);
}
[data-theme="light"] .pickerItem.selected {
background-color: var(--background);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: var(--foreground);
}
[data-theme="light"] .pickerItem:hover {
background-color: var(--background);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
[data-theme="light"] .pickerItem.unselected {
color: var(--text-color-gray);
}
/* Accessibility enhancements */
.pickerItem:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
.pickerItem:focus-visible.selected {
outline-color: var(--primary);
}
/* Animation for smooth transitions */
@keyframes pickerItemSelect {
0% {
transform: scale(1);
}
50% {
transform: scale(1.05);
}
100% {
transform: scale(1);
}
}
.pickerItem.selected {
animation: pickerItemSelect 0.2s ease-out;
}
/* Responsive design */
@media (max-width: 480px) {
.pickerItemText {
font-size: 14px;
padding: 8px 0;
}
.pickerItem {
height: 32px;
margin: 1px 2px;
}
.pickerItem.selected {
margin: 1px 2px;
}
}
/* High contrast mode support */
@media (prefers-contrast: high) {
.pickerContainer {
border-width: 2px;
}
.pickerItem.selected {
border: 2px solid var(--primary);
background-color: var(--primary);
color: var(--secondary-background);
}
.pickerItem.unselected {
color: var(--foreground);
opacity: 0.8;
}
}
/* Reduced motion support */
@media (prefers-reduced-motion: reduce) {
.pickerItem,
.pickerContainer,
.pickerItemText {
transition: none;
}
.pickerItem.selected {
animation: none;
}
.pickerItem:hover {
transform: none;
}
}

View File

@@ -1,13 +1,14 @@
import {
CSSProperties,
HTMLProps,
MutableRefObject,
createContext,
useCallback,
useContext,
useMemo,
useReducer,
CSSProperties,
HTMLProps,
MutableRefObject,
createContext,
useCallback,
useContext,
useMemo,
useReducer,
} from "react";
import styles from "../Picker.module.css";
const DEFAULT_HEIGHT = 216;
const DEFAULT_ITEM_HEIGHT = 36;
@@ -160,9 +161,9 @@ function PickerRoot<TType extends PickerValue>(props: PickerRootProps<TType>) {
justifyContent: "center",
overflow: "hidden",
maskImage:
"linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)",
"linear-gradient(to top, transparent, transparent 5%, var(--foreground) 20%, var(--foreground) 80%, transparent 95%, transparent)",
WebkitMaskImage:
"linear-gradient(to top, transparent, transparent 5%, white 20%, white 80%, transparent 95%, transparent)",
"linear-gradient(to top, transparent, transparent 5%, var(--foreground) 20%, var(--foreground) 80%, transparent 95%, transparent)",
}),
[height]
);
@@ -194,6 +195,7 @@ function PickerRoot<TType extends PickerValue>(props: PickerRootProps<TType>) {
return (
<div
className={styles.pickerContainer}
style={{
...containerStyle,
...style,

View File

@@ -1,14 +1,15 @@
import {
CSSProperties,
HTMLProps,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
CSSProperties,
HTMLProps,
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
import styles from "../Picker.module.css";
import { usePickerActions, usePickerData } from "./Picker";
interface PickerColumnProps extends HTMLProps<HTMLDivElement> {
@@ -255,6 +256,7 @@ function PickerColumn({
return (
<div
className={styles.pickerColumn}
style={{
...columnStyle,
...style,

View File

@@ -1,11 +1,12 @@
import {
HTMLProps,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
HTMLProps,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
} from "react";
import styles from "../Picker.module.css";
import { usePickerActions, usePickerData } from "./Picker";
import { useColumnData } from "./PickerColumn";
@@ -45,12 +46,19 @@ function PickerItem({ style, children, value, ...restProps }: PickerItemProps) {
[itemHeight]
);
const getItemClassName = useCallback((selected: boolean) => {
return `${styles.pickerItem} ${selected ? styles.selected : styles.unselected}`;
}, []);
const handleClick = useCallback(() => {
pickerActions.change(key, value);
}, [pickerActions, key, value]);
const isSelected = pickerValue[key] === value;
return (
<div
className={getItemClassName(isSelected)}
style={{
...itemStyle,
...style,
@@ -60,8 +68,12 @@ function PickerItem({ style, children, value, ...restProps }: PickerItemProps) {
{...restProps}
>
{isFunction(children)
? children({ selected: pickerValue[key] === value })
: children}
? children({ selected: isSelected })
: (
<div className={styles.pickerItemText}>
{children}
</div>
)}
</div>
);
}