首页 / 资讯中心 / 文章详情

Radix Vue 组件详解:使用 YearPicker 构建可访问的年选择器

Radix Vue 组件详解:使用 YearPicker 构建可访问的年选择器 ★ FEATURED ARTICLE
Radix Vue 组件详解使用 YearPicker 构建可访问的年选择器【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vueYearPicker 是一个专为选择年份而设计的日历视图组件本文以 radix-vuereka-ui源码仓库为依托深入讲解其组件结构、安装方式、全部 API 属性、数据属性、键盘交互与底层实现原理并给出可直接运行的完整示例。导读YearPicker年选择器是 radix-vue 中一类特殊的日历组件它不再像 Calendar 那样以月为单位翻页而是把一页展示为连续的一批年份默认每页 12 年让用户快速定位并选择年份。本文将从组件定位、安装前置条件出发完整剖析其 10 个可组合部件的 Anatomy 结构逐个讲解 Root、Header、Prev/Next、Heading、Grid 及 Cell/CellTrigger 的 Props、Slots 与渲染出的data-*属性并结合 YearPickerRoot.vue、useYearPicker.ts 等源码解释每页多少年翻页如何对齐禁用与不可用如何判定等底层细节最后给出完整的无障碍键盘交互表与一个可复制的 Tailwind 实战示例。读完后你将掌握如何安装并拼装 YearPicker 的全部部件、如何通过yearsPerPage与prevPage/nextPage定制分页行为、如何用isYearDisabled/isYearUnavailable/minValue/maxValue控制可选年份范围以及其完整键盘导航的底层实现方式。组件定位与特性YearPicker 在文档中被标记为 Alpha 阶段组件其定位是呈现一个专门用于选择年份的日历视图Presents a calendar view tailored for selecting years相当于把日历的粒度从月提升到年。官方文档列出的核心特性见 year-picker.mdFull keyboard navigation完整的键盘导航能力方向键、PageUp/PageDown 均可翻页与移动焦点Can be controlled or uncontrolled受控与非受控双模式既可用v-model绑定也可仅用defaultValueFocus is fully managed焦点完全由组件管理无需手动维护 tabindexLocalization support通过locale支持多语言与多日历系统的格式化Highly composable高度可组合所有部件均可独立使用并可用asChild替换渲染元素Configurable years per page每页展示的年份数量可配置yearsPerPage默认 12。前置依赖internationalized/date官方文档明确说明YearPicker 以及所有日期相关组件都依赖 internationalized/date 包该包解决了 JavaScript 中处理日期与时间的大量问题。这一点在源码中可以直接验证YearPickerRoot.vue 顶部即从internationalized/date导入了DateValue类型并使用了CalendarDate、CalendarDateTime、toZoned等值类型useYearPicker.ts 也直接使用了该包的startOfYear、endOfYear等工具函数。官方建议先通读该包的文档以理解DateValue、CalendarDate等概念——因为 YearPicker 的所有 PropsdefaultValue、placeholder、minValue、maxValue、modelValue和 CellTrigger 的year属性都以DateValue为类型。安装先在项目中安装日期包pnpm add internationalized/date再安装组件库本身pnpm add reka-ui说明当前仓库中的组件包对应reka-uiradix-vue 的演进版本命名安装完成后即可从reka-ui中按需导入 YearPicker 相关部件。Anatomy部件结构与最小模板YearPicker 由 10 个部件组成官方文档给出的骨架如下摘自 year-picker.md 的 Anatomy 章节script setup import { YearPickerCell, YearPickerCellTrigger, YearPickerGrid, YearPickerGridBody, YearPickerGridRow, YearPickerHeader, YearPickerHeading, YearPickerNext, YearPickerPrev, YearPickerRoot, } from reka-ui /script template YearPickerRoot YearPickerHeader YearPickerPrev / YearPickerHeading / YearPickerNext / /YearPickerHeader YearPickerGrid YearPickerGridBody YearPickerGridRow YearPickerCell YearPickerCellTrigger / /YearPickerCell /YearPickerGridRow /YearPickerGridBody /YearPickerGrid /YearPickerRoot /template各部件职责一览部件默认渲染元素职责YearPickerRootdiv年选择器容器提供上下文、状态与翻页逻辑YearPickerHeaderdiv容纳导航按钮与标题区段YearPickerPrevbutton向前翻一页默认 12 年YearPickerHeadingdiv显示当前年份范围如 2020 - 2031YearPickerNextbutton向后翻一页默认 12 年YearPickerGridtable年份网格容器YearPickerGridBodytbody网格主体容器YearPickerGridRowtr网格行容器YearPickerCelltd单个年份单元格容器YearPickerCellTriggerdiv可交互的年份触发区点击即选中该年源码目录 packages/core/src/YearPicker 中可逐一找到对应实现YearPickerRoot.vue、YearPickerGrid.vue、YearPickerCell.vue、YearPickerCellTrigger.vue、YearPickerHeading.vue等其中Grid系列分别以table/tbody/tr/td为默认元素见 YearPickerGrid.md 与 YearPickerGridBody.md从而天然获得表格语义而CellTrigger默认渲染div并显式声明rolebutton同时设置aria-label与aria-disabled兼顾语义与可访问性见 YearPickerCellTrigger.vue。注意Anatomy 中的Cell虽然官方文档结构示例未直接传入date但实际使用时需要通过 Root 的grid插槽遍历出行与列并为Cell/CellTrigger传入具体的DateValue完整写法见下文实战示例。API ReferenceRootYearPickerRoot包含年选择器的所有部件并负责状态管理、翻页逻辑与上下文注入通过createContext提供见 YearPickerRoot.vue。其 Props完整定义见 YearPickerRoot.md 与源码 YearPickerRoot.vue名称说明类型默认值as渲染为的元素或组件可被asChild覆盖AsTag \| ComponentdivasChild将默认渲染元素替换为传入的子元素并合并 props 与行为boolean-calendarLabel年选择器的无障碍标签string-defaultPlaceholder默认占位日期DateValue-defaultValue年选择器的默认值非受控模式DateValue-dir日历阅读方向省略时继承全局ConfigProvider或假定 LTRltr \| rtl-disabled是否禁用booleanfalseinitialFocus为 true 时挂载后聚焦到选中年、今天或范围内第一年booleanfalseisYearDisabled返回某年是否被禁用的函数Matcher-isYearUnavailable返回某年是否不可用的函数Matcher-locale日期格式化所用 localestring-maxValue可选择的最大日期DateValue-minValue可选择的最小日期DateValue-modelValue受控选中年份值可绑定v-modelDateValue \| DateValue[] \| null-multiple是否允许多选年份booleanfalsenextPage返回下一页的函数接收当前 placeholder 作为参数(placeholder: DateValue) DateValue-placeholder占位日期用于在无选中值时决定显示哪一年份范围DateValue-preventDeselect是否阻止用户在没有先选择其他日期的情况下取消选择booleanfalseprevPage返回上一页的函数接收当前 placeholder 作为参数(placeholder: DateValue) DateValue-readonly是否只读booleanfalseyearsPerPage每页展示的年份数量number12事件名称说明载荷update:modelValuemodelValue 变化时触发[date: DateValue \| DateValue[]]update:placeholderplaceholder 变化时触发[date: DateValue]插槽名称说明类型date当前 placeholder 日期DateValuegrid年份网格GridDateValuelocale年选择器 localestringmodelValue当前选中值DateValue \| DateValue[] \| undefinedRoot 渲染的数据属性由 YearPickerRoot.vue 模板直接输出属性值[data-readonly]只读时存在[data-disabled]禁用时存在[data-invalid]选中值被判定为禁用/不可用isInvalid时存在此外 Root 内部还渲染一个屏幕阅读器可见的隐藏 headingroleheadingaria-level2内容为fullCalendarLabel即calendarLabel ?? Year Picker加上当前年份范围见 useYearPicker.ts。关于禁用与不可用的底层区别isYearDisabled与isYearUnavailable都是Matcher接收日期返回布尔值的函数语义上不可用代表该年份存在但当前不可选择例如已被预约禁用则代表该年份完全不可交互。在 YearPickerCellTrigger.vue 中二者分别驱动data-unavailable与data-disabled属性且changeYear会同时拦截这两种状态在useYearPickerStateuseYearPicker.ts中任一匹配都会让isInvalid变为 true进而让 Root 带上data-invalid。Header包含导航按钮与标题区段仅接受通用as/asChild两个 Props见 YearPickerHeader.md无事件、无特殊插槽。Prev ButtonYearPickerPrev日历导航按钮向前翻一页默认 12 年。除了as/asChild外还接受一个可选 Props名称说明类型prevPage用于上一页的函数覆盖Root 上设置的prevPage(placeholder: DateValue) DateValue插槽disabled当前禁用状态boolean。渲染的数据属性属性值[data-disabled]禁用时存在禁用判定逻辑见 useYearPicker.ts当未设置minValue时按钮恒可用设置后若自定义prevPage或 RootprevPage计算出的上一页结尾早于minValue或默认算法中当前页首年减一年的年末早于minValue则禁用。Next ButtonYearPickerNext日历导航按钮向后翻一页默认 12 年。Props 与 Prev 对称名称说明类型nextPage用于下一页的函数覆盖Root 上设置的nextPage(placeholder: DateValue) DateValue插槽disabled。数据属性同样为[data-disabled]存在即禁用判定逻辑见 useYearPicker.ts未设置maxValue时恒可用设置后若下一页起始年晚于maxValue则禁用。HeadingYearPickerHeading用于显示当前年份范围如 2020 - 2031。仅as/asChild渲染时带roleheading与aria-level2见 YearPickerHeading.vue。插槽名称说明类型headingValue当前年份范围标题string数据属性[data-disabled]禁用时存在。标题字符串的计算在 useYearPicker.ts取网格首年与末年通过formatter.fullYear格式化后拼接为${firstYear} - ${lastYear}当年份采用格列高利历且 era 为 BC公元前时会附加短 era 标记。GridYearPickerGrid年份网格容器默认渲染table。仅as/asChild。数据属性属性值[data-readonly]只读时存在[data-disabled]禁用时存在Grid BodyYearPickerGridBody网格主体容器默认渲染tbody仅as/asChild无额外数据属性。Grid RowYearPickerGridRow网格行容器默认渲染tr仅as/asChild。CellYearPickerCell年份单元格容器默认渲染td仅as/asChild。数据属性属性值[data-disabled]禁用时存在Cell TriggerYearPickerCellTrigger可交互的年份触发区点击它即选中该年份默认渲染div并声明rolebutton。Props名称说明类型必填默认值as渲染为的元素或组件AsTag \| Component否divasChild替换渲染元素boolean否-year提供给 cell trigger 的日期值DateValue是-插槽见 YearPickerCellTrigger.vue名称说明类型yearValue当前年份值stringdisabled当前禁用状态booleanselected当前选中状态booleantoday当前年是否为今年booleanunavailable当前不可用状态boolean渲染的数据属性属性值[data-selected]选中时存在[data-value]日期的 ISO 字符串值[data-disabled]禁用时存在[data-unavailable]不可用时存在[data-today]该年为当前年份时存在[data-focused]聚焦时存在同时设置aria-label年份的可读文本、aria-disabled禁用或不可用时为 true并依据是否聚焦管理tabindex聚焦年为 0禁用年不设 tabindex其余为 -1见 YearPickerCellTrigger.vue。核心机制源码解析每页年份网格的生成与翻页网格由createYearGrid({ dateObj, yearsPerPage })生成useYearPicker.ts。默认yearsPerPage 12首屏会按十年对齐的方式生成网格如 1980 年对应 1980 - 1991 页翻页时使用decadeAligned: false直接以当前页首年加减yearsPerPage得到新页例如 1980 - 1991 页向后翻即为 1992 - 2003这一点被测试用例 YearPicker.test.ts 明确验证。nextPage/prevPage的实现同时支持三种来源传入的局部函数参数 Root 的nextPage/prevPageProps 默认的首年加减 yearsPerPage算法useYearPicker.ts。这意味着你可以在 Root 层面或按钮层面传入自定义函数实现任意步长的翻页策略。placeholder 与选中联动placeholder决定当前展示哪一页当modelValue变化时Root 会 watch 到新值并把 placeholder 同步到最新选中年份若年份不同保证选中后视图始终跟随YearPickerRoot.vue。而 placeholder 本身越出当前网格范围时useYearPicker中的 watch 也会重新生成网格useYearPicker.ts。多选与反选保护multiple开启后modelValue为数组onYearChange处理了单值/多值、防止重复、以及在preventDeselect为 true 时不允许取消最后选中项等分支逻辑YearPickerRoot.vue。键盘焦点移动方向键导航发生在 CellTrigger 的handleArrowKey中YearPickerCellTrigger.vue左右键按 ±1 年、上下键按 ±4 年配合 4 列布局移动焦点目标年份不在当前页时自动调用nextPage/prevPage翻页并在nextTick后继续递归寻找可聚焦的年带 48 层深度保护RTL 方向下左右键的符号会反转dir rtl ? -1 : 1。Accessibility键盘交互官方文档给出的完整键盘交互表如下与源码实现一一对应按键行为Tab焦点移入年选择器时聚焦第一个导航按钮Space焦点在YearPickerNext或YearPickerPrev上时翻页否则选中当前年份Enter焦点在YearPickerNext或YearPickerPrev上时翻页否则选中当前年份ArrowLeft/ArrowRight/ArrowUp/ArrowDown焦点在YearPickerCellTrigger上时按方向移动年份必要时自动翻页PageUp焦点在YearPickerCellTrigger上时跳到上一页年份PageDown焦点在YearPickerCellTrigger上时跳到下一页年份补充细节来自源码Space/Enter在带ctrl/meta/alt修饰键时会放行冒泡不吞掉组合键PageUp/PageDown的步长等于yearsPerPage默认 12方向键与翻页都会先经过minValue/maxValue边界校验越界即停止。组件还内置initialFocus能力挂载时通过handleCalendarInitialFocus将焦点定位到选中年/今天/范围内第一年YearPickerRoot.vue。测试文件 YearPicker.test.ts 中还有基于 vitest-axe 的无障碍断言expect(await axe(picker)).toHaveNoViolations()可作为可访问性保障的实证。实战示例基于 Tailwind 的年选择器仓库自带的演示位于 docs/components/demo/YearPickercss/与tailwind/两套样式其中 Tailwind 版本完整展示了 Root 插槽遍历网格、传值给 Cell/CellTrigger、利用data-*属性做样式适配的写法见 tailwind/index.vue核心如下script setup langts import { Icon } from iconify/vue import { CalendarDate } from internationalized/date import { YearPickerCell, YearPickerCellTrigger, YearPickerGrid, YearPickerGridBody, YearPickerGridRow, YearPickerHeader, YearPickerHeading, YearPickerNext, YearPickerPrev, YearPickerRoot, } from reka-ui const date new CalendarDate(2024, 1, 1) /script template YearPickerRoot v-slot{ grid } :default-valuedate classmt-6 rounded-xl bg-white p-4 shadow-sm border YearPickerHeader classflex items-center justify-between YearPickerPrev classinline-flex items-center cursor-pointer text-black justify-center rounded-md bg-transparent size-8 hover:bg-stone-50 focus:shadow-[0_0_0_2px] Icon iconradix-icons:chevron-left classsize-4 / /YearPickerPrev YearPickerHeading classtext-sm text-black font-medium / YearPickerNext classinline-flex items-center cursor-pointer justify-center text-black rounded-md bg-transparent size-8 hover:bg-stone-50 focus:shadow-[0_0_0_2px] Icon iconradix-icons:chevron-right classsize-4 / /YearPickerNext /YearPickerHeader div classpt-4 YearPickerGrid classw-full border-collapse select-none YearPickerGridBody classgrid gap-y-1 YearPickerGridRow v-for(years, index) in grid.rows :keyyear-${index} classgrid grid-cols-4 gap-x-1 YearPickerCell v-foryear in years :keyyear.toString() :dateyear classrelative text-center text-sm YearPickerCellTrigger :yearyear classrelative flex items-center justify-center whitespace-nowrap text-sm font-normal text-black size-12 rounded-lg outline-none >!-- 受控 禁用范围 禁用特定年份 -- YearPickerRoot v-modelselectedYear :min-valuenew CalendarDate(2020, 1, 1) :max-valuenew CalendarDate(2035, 12, 31) :is-year-disabled(year) year.year 2030 localezh-CN !-- Header / Grid 结构同上 -- /YearPickerRoot !-- 多选模式 -- YearPickerRoot v-modelselectedYears multiple prevent-deselect !-- Header / Grid 结构同上 -- /YearPickerRoot通过minValue/maxValue圈定可选区间源码中isYearDisabled会额外校验年初不晚于 maxValue、年末不早于 minValue见 useYearPicker.ts通过isYearDisabled/isYearUnavailable精细控制个别年份multiple开启多选配合preventDeselect保证至少保留一个选中项locale控制年份的格式化语言环境需要完全接管翻页行为时可在 Root 或 Prev/Next 上传入自定义prevPage/nextPage函数。小结YearPicker 是 radix-vue 日期组件体系中粒度最粗的日历视图它把 internationalized/date 的DateValue模型与 Radix 式的可组合部件设计相结合用 10 个部件、约 20 个 Props 覆盖了从单页 12 年到任意步长翻页、从单选到多选、从完全禁用逐项定制到完整键盘导航的全部能力。无论你是在做筛选面板、报表统计页还是历史数据归档工具都可以基于本文的 Anatomy、API 表与示例模板快速组合出一个无障碍、可本地化、行为可控的年选择器。如果你想深入理解实现细节建议直接阅读 YearPickerRoot.vue、useYearPicker.ts、YearPickerCellTrigger.vue 三个核心文件并结合 YearPicker.test.ts 中的交互测试用例如 respects a default value if provided、navigates to next decade using next button验证行为完整的 API 元数据可从 docs/content/meta 下各YearPicker*.md文件查阅。【免费下载链接】radix-vueAn open-source UI component library for building high-quality, accessible design systems and web apps for Vue. Previously Radix Vue项目地址: https://gitcode.com/GitHub_Trending/ra/radix-vue创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站