feat: 排序个性化新增排序形式切换
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
|
import { Switch } from '@/components/ui/switch';
|
||||||
import ConfirmModal from './ConfirmModal';
|
import ConfirmModal from './ConfirmModal';
|
||||||
import { ResetIcon, SettingsIcon } from './Icons';
|
import { ResetIcon, SettingsIcon } from './Icons';
|
||||||
|
|
||||||
@@ -19,10 +20,13 @@ export default function SettingsModal({
|
|||||||
containerWidth = 1200,
|
containerWidth = 1200,
|
||||||
setContainerWidth,
|
setContainerWidth,
|
||||||
onResetContainerWidth,
|
onResetContainerWidth,
|
||||||
|
showMarketIndex = true,
|
||||||
|
setShowMarketIndex,
|
||||||
}) {
|
}) {
|
||||||
const [sliderDragging, setSliderDragging] = useState(false);
|
const [sliderDragging, setSliderDragging] = useState(false);
|
||||||
const [resetWidthConfirmOpen, setResetWidthConfirmOpen] = useState(false);
|
const [resetWidthConfirmOpen, setResetWidthConfirmOpen] = useState(false);
|
||||||
const [localSeconds, setLocalSeconds] = useState(tempSeconds);
|
const [localSeconds, setLocalSeconds] = useState(tempSeconds);
|
||||||
|
const [localShowMarketIndex, setLocalShowMarketIndex] = useState(showMarketIndex);
|
||||||
const pageWidthTrackRef = useRef(null);
|
const pageWidthTrackRef = useRef(null);
|
||||||
|
|
||||||
const clampedWidth = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
|
const clampedWidth = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
|
||||||
@@ -55,6 +59,10 @@ export default function SettingsModal({
|
|||||||
setLocalSeconds(tempSeconds);
|
setLocalSeconds(tempSeconds);
|
||||||
}, [tempSeconds]);
|
}, [tempSeconds]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setLocalShowMarketIndex(showMarketIndex);
|
||||||
|
}, [showMarketIndex]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog
|
||||||
open
|
open
|
||||||
@@ -162,6 +170,22 @@ export default function SettingsModal({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<div hidden className="form-group" style={{ marginBottom: 16 }}>
|
||||||
|
<div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>显示大盘指数</div>
|
||||||
|
<div className="row" style={{ justifyContent: 'flex-start', alignItems: 'center' }}>
|
||||||
|
<Switch
|
||||||
|
checked={localShowMarketIndex}
|
||||||
|
className="ml-2 scale-125"
|
||||||
|
onCheckedChange={(checked) => {
|
||||||
|
const nextValue = Boolean(checked);
|
||||||
|
setLocalShowMarketIndex(nextValue);
|
||||||
|
setShowMarketIndex?.(nextValue);
|
||||||
|
}}
|
||||||
|
aria-label="显示大盘指数"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="form-group" style={{ marginBottom: 16 }}>
|
<div className="form-group" style={{ marginBottom: 16 }}>
|
||||||
<div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>数据导出</div>
|
<div className="muted" style={{ marginBottom: 8, fontSize: '0.8rem' }}>数据导出</div>
|
||||||
<div className="row" style={{ gap: 8 }}>
|
<div className="row" style={{ gap: 8 }}>
|
||||||
@@ -188,7 +212,7 @@ export default function SettingsModal({
|
|||||||
<div className="row" style={{ justifyContent: 'flex-end', marginTop: 24 }}>
|
<div className="row" style={{ justifyContent: 'flex-end', marginTop: 24 }}>
|
||||||
<button
|
<button
|
||||||
className="button"
|
className="button"
|
||||||
onClick={(e) => saveSettings(e, localSeconds)}
|
onClick={(e) => saveSettings(e, localSeconds, localShowMarketIndex)}
|
||||||
disabled={localSeconds < 30}
|
disabled={localSeconds < 30}
|
||||||
>
|
>
|
||||||
保存并关闭
|
保存并关闭
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerClose,
|
DrawerClose,
|
||||||
} from "@/components/ui/drawer";
|
} from "@/components/ui/drawer";
|
||||||
|
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
||||||
import { CloseIcon, DragIcon, ResetIcon, SettingsIcon } from "./Icons";
|
import { CloseIcon, DragIcon, ResetIcon, SettingsIcon } from "./Icons";
|
||||||
import ConfirmModal from "./ConfirmModal";
|
import ConfirmModal from "./ConfirmModal";
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ export default function SortSettingModal({
|
|||||||
rules = [],
|
rules = [],
|
||||||
onChangeRules,
|
onChangeRules,
|
||||||
onResetRules,
|
onResetRules,
|
||||||
|
sortDisplayMode = "buttons",
|
||||||
|
onChangeSortDisplayMode,
|
||||||
}) {
|
}) {
|
||||||
const [localRules, setLocalRules] = useState(rules);
|
const [localRules, setLocalRules] = useState(rules);
|
||||||
const [editingId, setEditingId] = useState(null);
|
const [editingId, setEditingId] = useState(null);
|
||||||
@@ -120,6 +123,59 @@ export default function SortSettingModal({
|
|||||||
: "pc-table-setting-body"
|
: "pc-table-setting-body"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
gap: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
className="pc-table-setting-subtitle"
|
||||||
|
style={{ margin: 0, fontSize: 14 }}
|
||||||
|
>
|
||||||
|
排序形式
|
||||||
|
</h3>
|
||||||
|
<div style={{ display: "flex", justifyContent: "flex-end", marginLeft: "auto" }}>
|
||||||
|
<RadioGroup
|
||||||
|
value={sortDisplayMode}
|
||||||
|
onValueChange={(value) => onChangeSortDisplayMode?.(value)}
|
||||||
|
className="flex flex-row items-center gap-4"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
htmlFor="sort-display-mode-buttons"
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
fontSize: 13,
|
||||||
|
color: "var(--text)",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RadioGroupItem id="sort-display-mode-buttons" value="buttons" />
|
||||||
|
<span>按钮</span>
|
||||||
|
</label>
|
||||||
|
<label
|
||||||
|
htmlFor="sort-display-mode-dropdown"
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 6,
|
||||||
|
fontSize: 13,
|
||||||
|
color: "var(--text)",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<RadioGroupItem id="sort-display-mode-dropdown" value="dropdown" />
|
||||||
|
<span>下拉单选</span>
|
||||||
|
</label>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
|||||||
80
app/page.jsx
80
app/page.jsx
@@ -71,6 +71,13 @@ import packageJson from '../package.json';
|
|||||||
import PcFundTable from './components/PcFundTable';
|
import PcFundTable from './components/PcFundTable';
|
||||||
import MobileFundTable from './components/MobileFundTable';
|
import MobileFundTable from './components/MobileFundTable';
|
||||||
import { useFundFuzzyMatcher } from './hooks/useFundFuzzyMatcher';
|
import { useFundFuzzyMatcher } from './hooks/useFundFuzzyMatcher';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
@@ -129,6 +136,7 @@ export default function HomePage() {
|
|||||||
const [settingsOpen, setSettingsOpen] = useState(false);
|
const [settingsOpen, setSettingsOpen] = useState(false);
|
||||||
const [tempSeconds, setTempSeconds] = useState(60);
|
const [tempSeconds, setTempSeconds] = useState(60);
|
||||||
const [containerWidth, setContainerWidth] = useState(1200);
|
const [containerWidth, setContainerWidth] = useState(1200);
|
||||||
|
const [showMarketIndex, setShowMarketIndex] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
@@ -141,6 +149,9 @@ export default function HomePage() {
|
|||||||
if (Number.isFinite(num)) {
|
if (Number.isFinite(num)) {
|
||||||
setContainerWidth(Math.min(2000, Math.max(600, num)));
|
setContainerWidth(Math.min(2000, Math.max(600, num)));
|
||||||
}
|
}
|
||||||
|
if (typeof parsed?.showMarketIndex === 'boolean') {
|
||||||
|
setShowMarketIndex(parsed.showMarketIndex);
|
||||||
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -174,10 +185,12 @@ export default function HomePage() {
|
|||||||
{ id: 'holding', label: '持有收益', enabled: true },
|
{ id: 'holding', label: '持有收益', enabled: true },
|
||||||
{ id: 'name', label: '基金名称', alias: '名称', enabled: true },
|
{ id: 'name', label: '基金名称', alias: '名称', enabled: true },
|
||||||
];
|
];
|
||||||
|
const SORT_DISPLAY_MODES = new Set(['buttons', 'dropdown']);
|
||||||
|
|
||||||
// 排序状态
|
// 排序状态
|
||||||
const [sortBy, setSortBy] = useState('default'); // default, name, yield, yesterdayIncrease, holding, holdingAmount
|
const [sortBy, setSortBy] = useState('default'); // default, name, yield, yesterdayIncrease, holding, holdingAmount
|
||||||
const [sortOrder, setSortOrder] = useState('desc'); // asc | desc
|
const [sortOrder, setSortOrder] = useState('desc'); // asc | desc
|
||||||
|
const [sortDisplayMode, setSortDisplayMode] = useState('buttons'); // buttons | dropdown
|
||||||
const [isSortLoaded, setIsSortLoaded] = useState(false);
|
const [isSortLoaded, setIsSortLoaded] = useState(false);
|
||||||
const [sortRules, setSortRules] = useState(DEFAULT_SORT_RULES);
|
const [sortRules, setSortRules] = useState(DEFAULT_SORT_RULES);
|
||||||
const [sortSettingOpen, setSortSettingOpen] = useState(false);
|
const [sortSettingOpen, setSortSettingOpen] = useState(false);
|
||||||
@@ -199,6 +212,13 @@ export default function HomePage() {
|
|||||||
if (parsed && Array.isArray(parsed.localSortRules)) {
|
if (parsed && Array.isArray(parsed.localSortRules)) {
|
||||||
rulesFromSettings = parsed.localSortRules;
|
rulesFromSettings = parsed.localSortRules;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
parsed &&
|
||||||
|
typeof parsed.localSortDisplayMode === 'string' &&
|
||||||
|
SORT_DISPLAY_MODES.has(parsed.localSortDisplayMode)
|
||||||
|
) {
|
||||||
|
setSortDisplayMode(parsed.localSortDisplayMode);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// ignore
|
||||||
@@ -267,6 +287,7 @@ export default function HomePage() {
|
|||||||
const next = {
|
const next = {
|
||||||
...(parsed && typeof parsed === 'object' ? parsed : {}),
|
...(parsed && typeof parsed === 'object' ? parsed : {}),
|
||||||
localSortRules: sortRules,
|
localSortRules: sortRules,
|
||||||
|
localSortDisplayMode: sortDisplayMode,
|
||||||
};
|
};
|
||||||
window.localStorage.setItem('customSettings', JSON.stringify(next));
|
window.localStorage.setItem('customSettings', JSON.stringify(next));
|
||||||
// 更新后标记 customSettings 脏并触发云端同步
|
// 更新后标记 customSettings 脏并触发云端同步
|
||||||
@@ -275,7 +296,7 @@ export default function HomePage() {
|
|||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [sortBy, sortOrder, sortRules, isSortLoaded]);
|
}, [sortBy, sortOrder, sortRules, sortDisplayMode, isSortLoaded]);
|
||||||
|
|
||||||
// 当用户关闭某个排序规则时,如果当前 sortBy 不再可用,则自动切换到第一个启用的规则
|
// 当用户关闭某个排序规则时,如果当前 sortBy 不再可用,则自动切换到第一个启用的规则
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -2785,19 +2806,25 @@ export default function HomePage() {
|
|||||||
await refreshAll(codes);
|
await refreshAll(codes);
|
||||||
};
|
};
|
||||||
|
|
||||||
const saveSettings = (e, secondsOverride) => {
|
const saveSettings = (e, secondsOverride, showMarketIndexOverride) => {
|
||||||
e?.preventDefault?.();
|
e?.preventDefault?.();
|
||||||
const seconds = secondsOverride ?? tempSeconds;
|
const seconds = secondsOverride ?? tempSeconds;
|
||||||
|
const shouldShowMarketIndex = typeof showMarketIndexOverride === 'boolean' ? showMarketIndexOverride : showMarketIndex;
|
||||||
const ms = Math.max(30, Number(seconds)) * 1000;
|
const ms = Math.max(30, Number(seconds)) * 1000;
|
||||||
setTempSeconds(Math.round(ms / 1000));
|
setTempSeconds(Math.round(ms / 1000));
|
||||||
setRefreshMs(ms);
|
setRefreshMs(ms);
|
||||||
|
setShowMarketIndex(shouldShowMarketIndex);
|
||||||
storageHelper.setItem('refreshMs', String(ms));
|
storageHelper.setItem('refreshMs', String(ms));
|
||||||
const w = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
|
const w = Math.min(2000, Math.max(600, Number(containerWidth) || 1200));
|
||||||
setContainerWidth(w);
|
setContainerWidth(w);
|
||||||
try {
|
try {
|
||||||
const raw = window.localStorage.getItem('customSettings');
|
const raw = window.localStorage.getItem('customSettings');
|
||||||
const parsed = raw ? JSON.parse(raw) : {};
|
const parsed = raw ? JSON.parse(raw) : {};
|
||||||
window.localStorage.setItem('customSettings', JSON.stringify({ ...parsed, pcContainerWidth: w }));
|
window.localStorage.setItem('customSettings', JSON.stringify({
|
||||||
|
...parsed,
|
||||||
|
pcContainerWidth: w,
|
||||||
|
showMarketIndex: shouldShowMarketIndex,
|
||||||
|
}));
|
||||||
triggerCustomSettingsSync();
|
triggerCustomSettingsSync();
|
||||||
} catch { }
|
} catch { }
|
||||||
setSettingsOpen(false);
|
setSettingsOpen(false);
|
||||||
@@ -3955,6 +3982,7 @@ export default function HomePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{showMarketIndex && (
|
||||||
<MarketIndexAccordion
|
<MarketIndexAccordion
|
||||||
navbarHeight={navbarHeight}
|
navbarHeight={navbarHeight}
|
||||||
onHeightChange={setMarketIndexAccordionHeight}
|
onHeightChange={setMarketIndexAccordionHeight}
|
||||||
@@ -3962,6 +3990,7 @@ export default function HomePage() {
|
|||||||
onCustomSettingsChange={triggerCustomSettingsSync}
|
onCustomSettingsChange={triggerCustomSettingsSync}
|
||||||
refreshing={refreshing}
|
refreshing={refreshing}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<div className="grid">
|
<div className="grid">
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
<div ref={filterBarRef} className="filter-bar" style={{ top: navbarHeight + marketIndexAccordionHeight, marginTop: 0, marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
<div ref={filterBarRef} className="filter-bar" style={{ top: navbarHeight + marketIndexAccordionHeight, marginTop: 0, marginBottom: 8, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 12 }}>
|
||||||
@@ -4085,6 +4114,46 @@ export default function HomePage() {
|
|||||||
<span className="muted">排序</span>
|
<span className="muted">排序</span>
|
||||||
<SettingsIcon width="14" height="14" />
|
<SettingsIcon width="14" height="14" />
|
||||||
</button>
|
</button>
|
||||||
|
{sortDisplayMode === 'dropdown' ? (
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
|
||||||
|
<Select
|
||||||
|
value={sortBy}
|
||||||
|
onValueChange={(nextSortBy) => {
|
||||||
|
setSortBy(nextSortBy);
|
||||||
|
if (nextSortBy !== sortBy) setSortOrder('desc');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="h-4 min-w-[110px] py-0 text-xs shadow-none"
|
||||||
|
style={{ background: 'var(--card-bg)', height: 36 }}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="选择排序规则" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{sortRules.filter((s) => s.enabled).map((s) => (
|
||||||
|
<SelectItem key={s.id} value={s.id}>
|
||||||
|
{s.alias || s.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Select
|
||||||
|
value={sortOrder}
|
||||||
|
onValueChange={(value) => setSortOrder(value)}
|
||||||
|
>
|
||||||
|
<SelectTrigger
|
||||||
|
className="h-4 min-w-[84px] py-0 text-xs shadow-none"
|
||||||
|
style={{ background: 'var(--card-bg)', height: 36 }}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="排序方向" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="desc">降序</SelectItem>
|
||||||
|
<SelectItem value="asc">升序</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="chips">
|
<div className="chips">
|
||||||
{sortRules.filter((s) => s.enabled).map((s) => (
|
{sortRules.filter((s) => s.enabled).map((s) => (
|
||||||
<button
|
<button
|
||||||
@@ -4119,6 +4188,7 @@ export default function HomePage() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -4740,6 +4810,8 @@ export default function HomePage() {
|
|||||||
containerWidth={containerWidth}
|
containerWidth={containerWidth}
|
||||||
setContainerWidth={setContainerWidth}
|
setContainerWidth={setContainerWidth}
|
||||||
onResetContainerWidth={handleResetContainerWidth}
|
onResetContainerWidth={handleResetContainerWidth}
|
||||||
|
showMarketIndex={showMarketIndex}
|
||||||
|
setShowMarketIndex={setShowMarketIndex}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -4792,6 +4864,8 @@ export default function HomePage() {
|
|||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
rules={sortRules}
|
rules={sortRules}
|
||||||
onChangeRules={setSortRules}
|
onChangeRules={setSortRules}
|
||||||
|
sortDisplayMode={sortDisplayMode}
|
||||||
|
onChangeSortDisplayMode={setSortDisplayMode}
|
||||||
onResetRules={() => setSortRules(DEFAULT_SORT_RULES)}
|
onResetRules={() => setSortRules(DEFAULT_SORT_RULES)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
46
components/ui/radio-group.jsx
Normal file
46
components/ui/radio-group.jsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { CircleIcon } from "lucide-react"
|
||||||
|
import { RadioGroup as RadioGroupPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const RadioGroup = React.forwardRef(({ className, ...props }, ref) => (
|
||||||
|
<RadioGroupPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
data-slot="radio-group"
|
||||||
|
className={cn("grid gap-3", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const RadioGroupItem = React.forwardRef(({ className, ...props }, ref) => (
|
||||||
|
<RadioGroupPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
data-slot="radio-group-item"
|
||||||
|
className={cn(
|
||||||
|
"group/radio aspect-square size-4 shrink-0 rounded-full border shadow-xs outline-none",
|
||||||
|
"border-[var(--border)] bg-[var(--input)] text-[var(--primary)]",
|
||||||
|
"transition-[color,box-shadow,border-color,background-color] duration-200 ease-out",
|
||||||
|
"hover:border-[var(--muted-foreground)]",
|
||||||
|
"data-[state=checked]:border-[var(--primary)] data-[state=checked]:bg-[var(--background)]",
|
||||||
|
"focus-visible:border-[var(--ring)] focus-visible:ring-[3px] focus-visible:ring-[var(--ring)] focus-visible:ring-opacity-50",
|
||||||
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
"aria-invalid:border-[var(--destructive)] aria-invalid:ring-[3px] aria-invalid:ring-[var(--destructive)] aria-invalid:ring-opacity-20",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<RadioGroupPrimitive.Indicator
|
||||||
|
data-slot="radio-group-indicator"
|
||||||
|
className="relative flex items-center justify-center"
|
||||||
|
>
|
||||||
|
<CircleIcon className="size-2 fill-current text-[var(--primary)]" />
|
||||||
|
</RadioGroupPrimitive.Indicator>
|
||||||
|
</RadioGroupPrimitive.Item>
|
||||||
|
))
|
||||||
|
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
|
||||||
|
|
||||||
|
export { RadioGroup, RadioGroupItem }
|
||||||
Reference in New Issue
Block a user