Zag 和 PandaCSS 都是出自 chakra 团队之手,Zag 聚焦于处理组件的逻辑,而 PandaCSS 聚焦于通过 ts 来维护样式,将两者进行搭配会有怎么样的使用体验呢?这篇文章将继续以 vuesax 中 checkbox 组件的样式作为参考,结合 Zag 和 PandaCSS 进行 vue3 版本的重构,实现一个超丝滑的勾选框组件。
Zag 中将 Checkbox 分为三个组成部分:
我们首先在 Zag Checkbox 文档中复制 JSX 的实例代码:
import * as checkbox from "@zag-js/checkbox"
import { normalizeProps, useMachine } from "@zag-js/vue"
import { defineComponent, h, Fragment, computed } from "vue"
export default defineComponent({
name: "Checkbox",
setup() {
const [state, send] = useMachine(checkbox.machine({ id: "checkbox" }))
const apiRef = computed(() =>
checkbox.connect(state.value, send, normalizeProps),
)
return () => {
const api = apiRef.value
return (
<label {...api.rootProps}>
<span {...api.labelProps}>
Input is {api.isChecked ? "checked" : "unchecked"}
</span>
<div {...api.controlProps} />
<input {...api.hiddenInputProps} />
</label>
)
}
},
})
这段代码使用了 useMachine 函数创建了一个状态机,并且写了一个无样式的基础 checkbox 结构:
接下来我们为 checkbox 组件补充样式.
想要实现丝滑的勾选框效果,最直观的是打勾的动画。这个效果可以通过 SVG 或者纯 css 实现,这里我使用的是 css 来实现的。:
首先我们要想想如何实现一个勾勾的效果,✔️由一长一短两个斜边组成,那么我们只需要放置一个矩形,设置一定的旋转角度,并设置其中的两个边框,就能实现一个✔️的形状:
代码实现如下:
import { defineComponent } from "vue";
import { css, cx } from "@/styled-system/css";
const IconCheck = defineComponent({
props: {
color: {
type: String,
default: css({ colorPalette: "gray" }),
},
size: {
type: String,
default: css({ colorPalette: "gray" }),
},
customCSS: {
type: String,
},
},
setup(props) {
return () => (
<i
class={cx(
css({
display: "flex",
alignItems: "center",
justifyContent: "center",
}),
props.customCSS,
)}
>
<div
class={css({
position: "relative",
width: "80%",
height: "40%",
transform: "rotate(-45deg)",
})}
>
<div
class={css({
position: "absolute",
left: "0",
width: "full",
height: "full",
borderLeft: "2px solid white",
animation: "checkShort 0.15s",
})}
/>
<div
class={css({
position: "absolute",
left: "0",
width: "full",
height: "full",
borderBottom: "2px solid white",
animation: "checkLong 0.5s",
})}
/>
</div>
</i>
);
},
});
export default IconCheck;
上面这段代码中,定义了一个矩形,宽高分别为最外层容器的 80% 和 40%,transform: "rotate(-45deg)", 则设置了矩形的旋转角度,内部放置两个与矩形宽高一致的容器,分别设置 borderLeft 和 borderBottom ,这样就组成了一长一短两条线。
这里之所以需要在内部放两个容器单独设置边框,而不是直接在矩形设置边框,是为了更好的实现动画效果,长短边分别设置了两个持续时间不同的动画 checkShort 0.15s 和 checkLong 0.5s:
checkShort: {
"0%": {
height:0,
},
"100%":{
height: "full",
}
},
checkLong: {
"0%": {
width: 0,
},
"30%":{
width: 0,
},
"100%": {
width: "full",
}
}
短边从最开始就执行动画,持续时间为长边动画的 30%,长边则在 0-30% 时不执行,30% 之后开始执行,这样就能实现短边动画执行完成后,长边动画再执行的效果。
之所以不使用 animation-delay 去延迟执行长边动画,是因为这种方式会导致动画延迟执行前,长边会先展示出来,效果就不对了。如果要使用这种方式还得单独做一些动画延迟前的隐藏处理,会比较麻烦:
为了让用户更容易感知勾选框是可以交互的,我们为勾选框增加未勾选和关状态的 hover 效果。
未勾选状态的 hover 效果,默认只有灰色边框,鼠标悬浮后变为灰色背景:
这里有个注意点,我们鼠标悬浮在勾选框的最外层,也可以触发内层的 hover 样式,如果直接使用 hover 效果是没法做到的,这样只能鼠标悬浮在边框内才能触发。
如果我们没有使用任何样式库,实现这个效果可以通过维护一个 鼠标是否 hover 的状态,并通过 onMouseEnter 和 onMouseLeave 来更新这个状态,再在内层根据这个状态动态渲染样式。
但这里我们可以使用 pandaCSS 的 group 选择器来实现。
首先在勾选框最外层元素增加 group 类名:
<label
{...api.rootProps}
class={[
css({
display: "flex",
alignItems: "center",
cursor: "pointer",
}),
+ "group",
]}
>
然后在内层的 control 元素增加基础样式:
<div
{...api.controlProps}
class={css({
width: "24px",
height: "24px",
borderRadius: "8px",
border: api.isChecked
? "none"
: "token(colors.gray.200) solid 2px",
transition: "all 0.3s",
marginRight: "4px",
flexShrink: "0",
_groupHover: {
background: "gray.200",
},
})}
>
// ...
</div>
这里的 _groupHover 即为 group 选择器,当带有 group 类名的元素触发 hover 时,内部的元素都可以使用 _groupHover 设置特定样式。这样我们就实现了前文图中的效果。
在勾选时,会有一个蓝色色块放大渐出的效果,我们先来实现这个样式。
<Transition
enterFromClass={css({
transition: "all 0.2s",
transform: "scale(0.5)",
opacity: "0",
})}
enterToClass={css({
transition: "all 0.2s",
transform: "scale(1)",
opacity: "1",
})}
leaveToClass={css({
transition: "all 0.2s",
transform: "scale(0.5)",
opacity: "0",
})}
>
{api.isChecked && (
<div
class={cx(
props.color,
css({
width: "full",
height: "full",
background: "colorPalette.600",
borderRadius: "inherit",
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "all 0.3s",
}),
)}
>
<IconCheck
customCSS={css({
width: "18px",
height: "18px",
})}
/>
</div>
)}
</Transition>
这里我们使用 vue 中的 Transition 组件来实现动画效果,通过改变scale 和 opacity 实现元素大小和透明度的过渡动画,内部包裹着勾选的图标。
实现了勾选的效果,继续实现勾选后的 hover 样式。勾选后在 hover 时,勾选框的外层有一个与主题色相同的外层阴影效果:
这里我们依然使用 group 选择器来设置 hover 样式:
<div
class={cx(
props.color,
css({
width: "full",
height: "full",
background: "colorPalette.600",
borderRadius: "inherit",
display: "flex",
alignItems: "center",
justifyContent: "center",
transition: "all 0.3s",
+ _groupHover: {
+ boxShadow:
+ "0px 3px 15px 0px color-mix(in srgb, token(colors.colorPalette.600) 40%, transparent)",
+ },
}),
)}
>
<IconCheck
customCSS={css({
width: "18px",
height: "18px",
})}
></IconCheck>
</div>
在阴影效果的代码中 0px 3px 15px 0px color-mix(in srgb, token(colors.colorPalette.600) 40%, transparent) ,前面几个设置阴影大小的参数很容易理解,但是后面阴影颜色的实现大家可能比较陌生,单独解释一下:
我这里用法的含义是在 srgb 的色彩模式下,将主题色(token(colors.colorPalette.600)) 与透明色(transparent),以 40% 的比例进行混合,最终合成的颜色就是 40% 透明度的主题色。 color-mix() 函数是 pandaCSS 中推荐用户用于为内置颜色设置透明度的方法,除此以外并没有发现其他更简便的方式。
最后我们完善一下勾选框的双向绑定逻辑逻辑,
实现的代码如下:
const [state, send] = useMachine(
checkbox.machine({
id: useId("checkbox"),
onCheckedChange(detail) {
emit("update:modelValue", detail.checked);
},
}),
);
const apiRef = computed(() =>
checkbox.connect(state.value, send, normalizeProps),
);
watch(
() => props.modelValue,
() => {
apiRef.value.setChecked(props.modelValue);
},
);
✅ 使用 Vue+Zag+PandaCSS 实现一个超丝滑的勾选框组件
原文链接:https://juejin.cn/post/7295954109404463155
辑导语:如何才能解决原型设计中输入框校验的问题?本篇文章里,作者就对如何绘制可以校验的输入框的相应流程进行了梳理,一起来看一下吧,也许会对你有所帮助。
在原型设计中,输入框校验效果设计是一件令人头疼的事情,但是可以通过使用bootstrap元件库中的输入框设置,可以轻松解决输入框校验的问题。
预览地址:http://atomstudio.cn/demos/bootstrap_input
需要安装XSTAR2022.1.22版(或更高版本)
1)打开软件,选择bootstrap元件库,在元件列表中将输入框拖拽到编辑区。
2)选中编辑区的输入框组件,右侧显示出输入框设置面板。
3)在输入框设置面板中勾选验证成功提示和验证失败提示。
4)编辑提示文本内容
5)设置前缀、后缀
6)设置提示样式
7)设置验证规则
可选规则包括:
8)根据需要设置完成后,进行预览、导出或者分享。
本文由 @balabala 原创发布于人人都是产品经理,未经许可,禁止转载。
题图来自Unsplash,基于 CC0 协议。
心看完你一定有所收获
在前两篇文章中,我们主要讲到 B 端产品最为重要的信息展示组件 表格 的设计思路,根据不同的场景对表格进行了答疑,同时部分读者与我反馈,想让我聊聊选择组件的一些内容,今天就来简单聊聊在「数据录入场景」中的一个小点:选择录入。
提前说一句:其实这篇文章快写完时发现已经有类似的文章,由于自己写文章并不会在乎市面上是否有同类型的文章,文章的灵感也多来源于自己的工作中遇到的实际问题。自己也并不是想重复造轮子,有“内卷”的那味儿,最后发出来的原因主要有两点:
1.为了能给大家把 日期选择、树形选择、穿梭框、成员选择 等业务复杂的控件讲懂,没有基础的认知进行“凭空建楼”实属困难。
2.发现虽然有类似的文章,但大家所讲的方向并不相同,对于选择录入场景的理解也不太一样。
其实我在评审许多设计师的稿件中,经常发现大家对它使用的场景并不了解。比如在一个表单中,让用户选择性别时,是采取下拉选择、单选框、多选框甚至是开关呢?那如果我们选择家庭住址又应该如何设计呢?这一系列问题都需要去解决。
由于知识点很多,想要把它们完整讲清楚需要花大量时间,因此我会在后续的文章中与大家逐一拆解,掰开揉碎慢慢消化,篇幅有限,今天我们先来聊聊前面几个稍微容易理解的:「单选框、多选框、开关」,究竟应该如何设计~
先科普一个知识点,Tooltip / Popover的区别
这是来自我 B端交流群的一个讨论,起因是有一位同学展示了自己产品的一个截图
图上可以看到,他的 tooltips 巨长无比,并引发了一系列的讨论:
首先再讨论过后,有朋友私聊我,咨询我“这两个控件谁更加好”,但组件本身就不存在孰优孰劣,更多则是场景不同,使用的控件也会不尽相同。为了更好让大家的理解两个控件的区别,这里将它们进行简单的对比,通过相同点与不同点去分别讲解,让大家有更全面的了解。
首先,对于 Tooltip 来说,它其实就是一个信息提示的小玩意儿,当用户对某一未进行文字解释的图标进行 :hover 时,便可采用 Tooltip 对该图标的含义进行解释,这也是我们日常工作中用到最多的情况。
而对 Popover 则感觉大家会有些陌生,其实在我们常见的新手引导、确认提示中都会使用这个控件,并且在很多 B端设计场景中,往往都可以通过这个控件进行相应的简化。
相同点我们主要会从 形式、功能、方向 三个方面去思考
它们两者在设计的形式上都是非常相似的点,都是采取浮层进行信息的展示,并且通常都会跟着一个小“尾巴”来代表它所来源的方向,此外在窗口大小上也基本相同,因为小窗口也意味着它所承载信息会相对较少,后面会细讲内容
在对其的实际运用中,都会涉及到弹出方向的不同。在实际项目中,弹窗方向一般是以上下左右四个方向进行弹出,而从上方弹窗是一般的默认方向,智慧在外部容器限制的前提下,会对弹出方向进行改变,同时在容器角落时,会延容器反方向的角落进行弹窗即可
对于使用两者的功能而言,只能说大致相同,细枝末节上还是会有不小的区别。在功能上,两者几乎都是想要提示用户某些隐藏信息,并且信息的重要程度都不会太高,因此足够轻量就成为它的关键属性
其实两个除了在外观上较为接近,其实在很多地方都是不同的地方
一般 B端的内容共有:图标、文字、链接、按钮、图片等,我们首先说说 Tootip ,Tooltip 因其容器的特殊性,只能承载简单的文本,并且 Tooltip 的提示一般不会多于50个字,只能对当前的内容进行简单解释
比如在常见的输入框标题提示中,经常会有「问号、叹号」 图标的出现,用户就是通过 :hover 展示 Tooltip 对标题进行解释
而在 Popover 中,他所承载的内容要比 Tooltip 更加的多,小到按钮、链接,大到图片、视频,都在它的弹窗范围内
我们常见的触发方式一共有:Hover、Focus、Click 三种方式
由于承载内容的不同,进而影响触发方式的不同
因为 Tooltip 在内容上以纯文字为主,并且多是50字以内的辅助信息,所以在触发方式上,要就 Tooltip 能够及时触发,因此当你鼠标 :hover 后,就应该将该内容的解释信息告知给用户,方便用户使用
而在 Popover 则恰恰相反,因为其内容承载较多(有图片、按钮等等…),如果通过 :hover 进行内容的呈现显然是不太合理,并且通常使用会有确定、是否的对话,让用户进行操作,所以使用 :focus、:click 更友好
因为 Tooltip 本事是通过 :hover 进行触发,也就直接造成在设计上,对于Tooptip 会采取对比度更强的黑色底,这样用户对于信息提示能吸引更多注意力,而黑色的背景,对于用户阅读来说会更加困难,也因此侧面反应这里的文字不宜过多
Popover 则不会有上面的烦恼,因为是通过用户的明确点击而来,用户对弹出内容已经有所预期,因此可使用白色底,阅读性会稍好于前者
如果说了那么多,不讲怎么用便是在真正的耍流氓,因此最后我们来看看他的实际用法有哪些
其一就是用来帮助用户了解到某些图标的含义目的,这已经是桌面端必备的逻辑,比如微信中
其二为了展示截断的文本,因为 B端很多长文本的场景,用户想要提前知道,帮助用户进行判断
其一可以作为警告用户的一种提示信息,需要用户再次确认,同时它相比 Dialog 更为轻量
其二作为新手引导,可以让用户进行一步步的确认,它的轻量刚好能够适合进行新手引导
OK,了解完两者的区别后,我们继续选择录入,小本本准备好~
单选框,也常叫做 单选按钮、单选,它最早来源于收音机上的物理按钮,当时用于收音调频之间的相互切换。当一个按钮被按下时,另一个按钮将会被弹起,使收音机只能拥有一个“按下状态的”按钮。
而早在计算机用户界面诞生之初(The Xerox Alto)就已经有了单选框的出现。同时在HTML的底层中,Radio 就作为一个最基础的标签,拥有「无法撼动的地位」所以在各大设计系统中一直作为基础组件被沿用至今。
但随着移动互联网的普及,单选框这一形式在用户心中被逐渐的弱化,取而代之的是各类功能相同但形式繁多的按钮,这也是目前很多B端设计师存在的认知误区之一。
单选框:允许用户从多个选项中,选择一个选项,且选项之间存在互斥关系,这也是「单选」名称的由来。
单选框的外观一般是一个空白的圆洞,旁边则通常有文字标签;标签的用途除了描述之外,还可以作为操作热区,当用户点击标签,所应的单选框就会被选中;已选上的单选按钮一般会在圆洞内加上一小圆点。
说一个我实际工作中遇到的内容,在我之前负责的一个关于医美客户系统的的SCRM中,当客户到店后需要由医美咨询师为每一位顾客新建一个客户资料,而医美行业的特殊性导致我们的大多数医美客户都为女性,因此在设计表单中的性别一栏上便可将默认值选择为女性,这样方便医美咨询师快速补充用户信息,可以进行更高效的信息录入。
当然,我说了双刃剑肯定代表它也有弊的一面,我举一个反例,比如我们在设计一个调查问卷中,去预设一些默认值就不太合理,因为问卷中需要减少对选项值的干预,保证其真实性,才能让默认选项会导致录入的数据产生准确,避免为后期的数据分析埋下“深坑。”
多选框,也常叫做复选框、勾选框,它允许用户选择一个或多个独立选项,将自己想要的选项作为值,多个条件间的逻辑关系为并列关系。
多选框在实际业务中其实也分为两种不同形态:单个多选框与多选框。
单个多选框:英文叫做(single checkbox)只出现一个多选框提供给用户进行选择,只包含“是”与“否”两种逻辑,用户可以选择其一。它与之后「开关Switch」的逻辑十分接近,但两者的适用场景也有很大不同。
比如在我们经常遇到的用户协议的页面中,同意协议通常都是采取单个多选框的形式,而开关相比单个多选框,更加强调选中的状态。之后会与开关进行深度对比,不做延展。
多选框:是多选框的一种通用样式,允许用户选择多个项,主要用于各类表单设计中,所以用户对于它的认知、功能以及行为操作有明确的心理预期和感知。
多选框相比其它控件,增加了两个较为特殊的状态 “半选中” “禁用(已选中)” ,因此这里仅仅单独讲解,其他状态便不做过多赘述。
半选中状态(Indeterminate)出现的条件必须具备以下两点:
上面说到需要父子联动,全选是选中其下所有的选项,而我只选择了其下一个选项时,就应该展示半选中状态。同时,当前多选框正在处于半选中状态时,点击多选框会执行全选操作。
禁用-已选中状态(Checked-disable)出现多表示该多选框已经被激活,只是在当前情况下不能进行操作。通常不能进行操作的场景有以下两点:
当然多选框还会有很多不同状态,会在章节末尾进行表格总结
在实际工作中,多选框会出现在一些典型的页面场景中,针对不同的页面场景,我们来看看究竟应该如何进行处理。
在用户权限管理页面中,经常会出现多选框的身影,而在权限这类页面中,往往是一个不断重复排列的多选框,针对不同的角色,去选择不同的权限。且每一个权限都是开启或关闭状态,也因此采取多选框也尤为合适。我们来看看不同产品中,都有着哪些权限页面设置的技巧。
语雀:
权限作为语雀的一个亮点功能,便将所有角色分为三类:管理员、成员、只读成员
在定义中,因为管理员拥有全部权限,所以不需要用户单独进行配置。只读成员同样只会拥有单独的查看权限,而我们需要去对成员进行单独的配置,然后将成员的权限进行细分,由于权限的数量并不多,因此采取纵向排列,方便用户对于多个权限进行对比。
上面语雀的权限配置页面过于简单,在真实B端业务时就会显得有些弱不经风。我们实际工作中面对多维度多层级的权限管理时,又应该如何设计?我们来看看 Coding 它是如何做的。
因为在一个正常的B端软件中,权限通常会拆分得特别细,对于不同字段与角色,他们的权限也会不尽相同。
Coding首先在左侧会展示“用户组”也就是我们上面说到的角色分类,用户可以去自定义角色类型有哪些,其次在对角色权限的配置中,会展示出用户可以自定义的所有功能,粗略估算大概会有100+个权限,也就意味着会有100+个多选框需要展示。当100+的多选框放在你面前,最为基础的对齐则显得尤为关键。通过限制多选框标签的整体宽度,强制将其纵向对齐,虽然遇到长文本时省略给用户带来不太友好的体验,但对齐所带来的留白、节奏感是远比省略带来的好处要多(当然在对长文本宽度的定义中,需要多考虑下常见字段的长度即可)
其次,在每一个大功能中,Coding都设置有一个全选功能,目前放置在整个列表的末尾,是一个特别赞的设计,能够帮助用户对每一个字段的权限进行统一配置,是一个经常使用的快捷入口。
在流程管理页面中,多选框也是不可忽略的页面。因为在整个流程页面中会对每个状态执行开启与关闭操作,因此在这里同样会重复多选框。
比如在 ONES 的流程管理页面中,看起来像是表格,其中纵向代表每个「流程开始状态」,横向代表每个流程阶段所要去向哪些状态,每个表格都会展示一个复选框,去配置它是否要流转到此状态,从而实现业务流转的需求。
而在这里的设计,最令人头疼的是整体的表现形式,因为目前而言,需要将初始状态、新增状态、激活状态、禁用状态等在一个表格中进行表示,更重要的是要能够让用户理解整个表格所代表的含义,这也是目前能看到的最好的设计成品,大家有什么更好的建议,欢迎在评论区留言,大家一起讨论。
表格页面最为复杂多变,也因此在表格中的多选框出现了两种不同的形式,一种将多选框直接展示,让用户更直接选择。另一种则是Hover到每一行显示多选框,同时一定要去注意全选与半选中的逻辑,在表格的设计上尤为重要,不能出现逻辑上的漏洞,这里也就不过多赘述。
最后补充一下多选框的所有状态的交互逻辑
开关,它是一种特殊的选择,其含义代表你的选择非黑即白。它不同于上面的控件,当用户点击后,开关需要经历一个加载状态,然后「立即执行」。这样的差别就导致开关的用法与其它控件并不相同,立即执行所带来的及时性也是设计师最容易与其他组件进行混淆的点。
在开关的早期,为了降低用户的学习成本,模仿现实生活中的开关进行设计,而随着扁平风格的到来,开关便得到了精简,去掉原本产品中的质感、投影,转向更加明确的「状态信息」
转眼到了 B端产品中,很多设计师都会沿用这一习惯,但是在HTML的代码逻辑里,并没有 Switch 的标签,也就意味着开关并不是网页本身所支持的形式,在程序员处理上则需要花费更多心思。不过在目前常见的前端框架中,对开关都进行了支持,比如 Element、Antdesign 可以直接引用。
虽然在组件上可以直接进行引用,但并不代表我们作为设计者,不需要去考虑它基本的交互逻辑以及使用场景。
在最新 Big Sur 系统中,设置页面就采取了类似操作,我们打开设置-通知,发现开关与表单同时存在。这里也可看到,允许通知的开关在最顶层,是控制整个表单权重最高的操作,同时下方单选、多选框、下拉菜单都受到顶部开关控制。
当然,我们在实际的设计中,同样会遇到类似的情况,比如在飞书的自建应用免审规则配置中:首先用户需要去选择开启此功能,开启后下方会展开一个基本表单,用于用户对应用规则中更为细致的配置,并且要注意,所有的配置都是实时生效,因此在每一次修改配置时,飞书上都会有 Loading 效果。
当然我们可以将开关换成单一多选框,但切换后用户就很难理一二级之间的逻辑关系,因此开关更为适合权重更高的操作。
在互联网上,时常看到 DISS 开关不应该出现在网页设计中,这里看到了一篇文章中讨论到《为什么在web上使用Switch是愚蠢的设计》,其实我有不同的意见,简单说一说我对开关的看法:
因为在 B 端的场景中,会有很多特殊的要求,因此不能一杆子将 Switch 进行一杆子打死,凡事都得辩证看待,需要去看到开关好的一面,并且规避它的一些不足。
首先,在 Web 端中不能大面积的去使用开关,因为大量的开关导致的就是对页面设计的亵渎,当然开关依然有它的独特之处,在开关的交互逻辑中虽然已经提到了部分特点,但我还是来简单说说我的理由:
使用开关主要是想利用开关状态去“做文章”。使用开关在比起其他条件,会更加强调它的状态,并能让用户通过状态去控制下层的其他组件(单选框、多选框等…)这就是开关在 B端产品中的实际需求。
同时因为开关是立即执行的操作,因此需要在每一次变更状态时进行相应的反馈,比如表单组件状态上的提示,辅助用户进行判断状态即可。
回到我们讨论的开关在 WEB 中的使用上,并不能因为因为开关不是 HTML 基础标签而去否定,并且在我们 B端实际业务中,确实会遇到开关的场景,因此大家在使用开关时还是应该根据情况,进行使用。
由于选择录入场景的内容较多,02篇即将更新,欢迎大家关注
我是CE青年,一个 2 B 行业的 2B 设计师~
*请认真填写需求信息,我们会在24小时内与您取得联系。