跳到内容
栏目
语言,当前是简体中文
Rastrillo 设计系统

选择与操作

从选项中挑选的控件,以及给表单收尾的几行。每个都放在有内边距的 rst-box 中,因为这些片段就是这么假定的。

每个示例都能真实操作,但其中的链接不会跳转到任何地方。 HTML 显示可复制的标记。

field-select

field 的外壳包住一个原生 select。超过十个选项时,这个 select 会启用 select.js 里可筛选的组合框;但表单提交的仍然是那个原生 select。

三个选项:原生 select

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-field>
  <label rst-field-label for="sel_few">角色</label>
  <select rst-input id="sel_few" name="sel_few">
    <option value="admin" selected>管理员</option>
    <option value="editor">编辑者</option>
    <option value="member">成员</option>
  </select>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-select" dict
    "ID" "sel_few"
    "Name" "sel_few"
    "Label" "角色"
    "Options" (list
        (dict "Label" "管理员" "Selected" true "Value" "admin")
        (dict "Label" "编辑者" "Value" "editor")
        (dict "Label" "成员" "Value" "member"))}}

十二个选项:可筛选的组合框

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-field>
  <label rst-field-label for="sel_many">城市</label>
  <select rst-input id="sel_many" name="sel_many" data-rst-select data-rst-select-filter="输入以筛选" data-rst-select-results="{n} 个结果" data-rst-select-result-one="1 个结果" data-rst-select-no-matches="没有匹配的结果" aria-describedby="sel_many-help">
    <option value="dublin" selected>都柏林</option>
    <option value="lisbon">里斯本</option>
    <option value="madrid">马德里</option>
    <option value="berlin">柏林</option>
    <option value="rome">罗马</option>
    <option value="athens">雅典</option>
    <option value="helsinki">赫尔辛基</option>
    <option value="reykjavík">雷克雅未克</option>
    <option value="warsaw">华沙</option>
    <option value="zagreb">萨格勒布</option>
    <option value="tallinn">塔林</option>
    <option value="valletta">瓦莱塔</option>
  </select>
  <p rst-field-help id="sel_many-help">开始输入即可筛选。</p>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-select" dict
    "ID" "sel_many"
    "Name" "sel_many"
    "Label" "城市"
    "Options" (list
        (dict "Label" "都柏林" "Selected" true "Value" "dublin")
        (dict "Label" "里斯本" "Value" "lisbon")
        (dict "Label" "马德里" "Value" "madrid")
        (dict "Label" "柏林" "Value" "berlin")
        (dict "Label" "罗马" "Value" "rome")
        (dict "Label" "雅典" "Value" "athens")
        (dict "Label" "赫尔辛基" "Value" "helsinki")
        (dict "Label" "雷克雅未克" "Value" "reykjavík")
        (dict "Label" "华沙" "Value" "warsaw")
        (dict "Label" "萨格勒布" "Value" "zagreb")
        (dict "Label" "塔林" "Value" "tallinn")
        (dict "Label" "瓦莱塔" "Value" "valletta"))
    "Help" "开始输入即可筛选。"}}

超过十个选项,片段就会启用 select.js;不到十个就不会:在几项之间搜索只是摆设,不是帮助。

十二个选项,Plain:明确不要增强

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-field>
  <label rst-field-label for="sel_plain">城市</label>
  <select rst-input id="sel_plain" name="sel_plain">
    <option value="dublin" selected>都柏林</option>
    <option value="lisbon">里斯本</option>
    <option value="madrid">马德里</option>
    <option value="berlin">柏林</option>
    <option value="rome">罗马</option>
    <option value="athens">雅典</option>
    <option value="helsinki">赫尔辛基</option>
    <option value="reykjavík">雷克雅未克</option>
    <option value="warsaw">华沙</option>
    <option value="zagreb">萨格勒布</option>
    <option value="tallinn">塔林</option>
    <option value="valletta">瓦莱塔</option>
  </select>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-select" dict
    "ID" "sel_plain"
    "Name" "sel_plain"
    "Label" "城市"
    "Options" (list
        (dict "Label" "都柏林" "Selected" true "Value" "dublin")
        (dict "Label" "里斯本" "Value" "lisbon")
        (dict "Label" "马德里" "Value" "madrid")
        (dict "Label" "柏林" "Value" "berlin")
        (dict "Label" "罗马" "Value" "rome")
        (dict "Label" "雅典" "Value" "athens")
        (dict "Label" "赫尔辛基" "Value" "helsinki")
        (dict "Label" "雷克雅未克" "Value" "reykjavík")
        (dict "Label" "华沙" "Value" "warsaw")
        (dict "Label" "萨格勒布" "Value" "zagreb")
        (dict "Label" "塔林" "Value" "tallinn")
        (dict "Label" "瓦莱塔" "Value" "valletta"))
    "Plain" true}}

必填,并带错误

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-field>
  <label rst-field-label for="sel_error">角色</label>
  <select rst-input id="sel_error" name="sel_error" required aria-invalid="true" aria-describedby="sel_error-error">
    <option value="admin" selected>管理员</option>
    <option value="editor">编辑者</option>
    <option value="member">成员</option>
  </select>
  <p rst-field-error id="sel_error-error" role="alert">请为此人选择一个角色。</p>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-select" dict
    "ID" "sel_error"
    "Name" "sel_error"
    "Label" "角色"
    "Options" (list
        (dict "Label" "管理员" "Selected" true "Value" "admin")
        (dict "Label" "编辑者" "Value" "editor")
        (dict "Label" "成员" "Value" "member"))
    "Required" true
    "Error" "请为此人选择一个角色。"}}

手写的,带 optgroup 分组

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-field>
  <label rst-field-label for="sel_grouped">地区</label>
  <select rst-input id="sel_grouped" name="sel_grouped" data-rst-select data-rst-select-filter="输入以筛选" data-rst-select-results="{n} 个结果" data-rst-select-result-one="1 个结果" data-rst-select-no-matches="没有匹配的结果">
    <optgroup label="欧洲">
      <option value="dublin" selected>都柏林</option>
      <option value="lisbon">里斯本</option>
      <option value="warsaw">华沙</option>
    </optgroup>
    <optgroup label="美洲">
      <option value="montreal">蒙特利尔</option>
      <option value="lima">利马</option>
      <option value="austin">奥斯汀</option>
    </optgroup>
    <optgroup label="亚洲">
      <option value="osaka">大阪</option>
      <option value="hanoi">河内</option>
      <option value="dhaka">达卡</option>
    </optgroup>
    <optgroup label="非洲">
      <option value="accra">阿克拉</option>
      <option value="nairobi">内罗毕</option>
      <option value="tunis">突尼斯</option>
    </optgroup>
  </select>
</div>

这里的 Options 是一个扁平列表,所以分组的选项要由应用自己写成标记。select.js 会把分组渲染出来而不是丢掉;手写的 select 也可以用 data-rst-select="false" 彻底拒绝增强。

field-check

唯一的一种开关做法:一个真正的复选框,外观做成开关,键盘和读屏器都照常可用。

开

预览

把这段放进 <section rst-box><form rst-form> 里。

<label rst-switch>
  <input type="checkbox" name="notify_on" checked>
  <span rst-switch-track aria-hidden="true"></span>
  有回复时给我发邮件
</label>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-check" dict
    "Name" "notify_on"
    "Label" "有回复时给我发邮件"
    "Checked" true}}

关

预览

把这段放进 <section rst-box><form rst-form> 里。

<label rst-switch>
  <input type="checkbox" name="notify_off">
  <span rst-switch-track aria-hidden="true"></span>
  有人提到我时给我发邮件
</label>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-check" dict "Name" "notify_off" "Label" "有人提到我时给我发邮件"}}

已禁用

预览

把这段放进 <section rst-box><form rst-form> 里。

<label rst-switch>
  <input type="checkbox" name="notify_disabled" disabled>
  <span rst-switch-track aria-hidden="true"></span>
  每周给我发一封摘要邮件
</label>

把这段放进 <section rst-box><form rst-form> 里。

{{template "field-check" dict
    "Name" "notify_disabled"
    "Label" "每周给我发一封摘要邮件"
    "Disabled" true}}

choice-field

选项卡片:推荐的单选或多选分组。Title 保持简短,解释交给 Desc。

只能选一个

预览

把这段放进 <section rst-box><form rst-form> 里。

<fieldset rst-choice>
  <legend>方案</legend>
  <div rst-choice-cards>
    <label>
      <input type="radio" name="plan" value="free">
      <span>
        <span rst-choice-title>免费</span>
        <span rst-choice-desc>一个项目,社区支持。</span>
      </span>
    </label>
    <label>
      <input type="radio" name="plan" value="pro" checked>
      <span>
        <span rst-choice-title>专业版</span>
        <span rst-choice-desc>不限项目数量,另有支持邮箱。</span>
      </span>
    </label>
  </div>
</fieldset>

把这段放进 <section rst-box><form rst-form> 里。

{{template "choice-field" dict
    "Legend" "方案"
    "Name" "plan"
    "Options" (list
        (dict "Desc" "一个项目,社区支持。" "Title" "免费" "Value" "free")
        (dict "Checked" true "Desc" "不限项目数量,另有支持邮箱。" "Title" "专业版" "Value" "pro"))}}

可多选

预览

把这段放进 <section rst-box><form rst-form> 里。

<fieldset rst-choice>
  <legend>发送给我</legend>
  <div rst-choice-cards>
    <label>
      <input type="checkbox" name="digest" value="replies" checked>
      <span>
        <span rst-choice-title>回复</span>
        <span rst-choice-desc>实时发送。</span>
      </span>
    </label>
    <label>
      <input type="checkbox" name="digest" value="weekly">
      <span>
        <span rst-choice-title>每周摘要</span>
        <span rst-choice-desc>每周一上午。</span>
      </span>
    </label>
  </div>
</fieldset>

把这段放进 <section rst-box><form rst-form> 里。

{{template "choice-field" dict
    "Legend" "发送给我"
    "Name" "digest"
    "Type" "checkbox"
    "Options" (list
        (dict "Checked" true "Desc" "实时发送。" "Title" "回复" "Value" "replies")
        (dict "Desc" "每周一上午。" "Title" "每周摘要" "Value" "weekly"))}}

form-foot

表单的收尾行:一个主提交按钮,外加可选的取消链接。取消是一个链接,因为离开表单是导航。

保存和取消

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-form-foot>
  <button rst-btn="primary lg" type="submit">保存文章</button>
  <a rst-btn="lg" href="/posts">返回文章列表</a>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "form-foot" dict
    "Submit" "保存文章"
    "CancelHref" "/posts"
    "CancelLabel" "返回文章列表"}}

只有提交

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-form-foot>
  <button rst-btn="primary lg" type="submit">发送邀请</button>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "form-foot" dict "Submit" "发送邀请"}}

空闲:任何事情发生之前的按钮

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-form-foot>
  <button rst-btn="primary lg" type="submit">发布</button>
  <a rst-btn="lg" href="/posts">返回文章列表</a>
</div>

把这段放进 <section rst-box><form rst-form> 里。

{{template "form-foot" dict
    "Submit" "发布"
    "CancelHref" "/posts"
    "CancelLabel" "返回文章列表"}}

会改变什么的按钮有加载状态;只是展开什么的按钮(例如折叠、下拉、标签页)没有。rastrillo.js 对每个表单里的每个提交按钮都套用这条规则,无需选择启用。

工作中:表单送出时 rastrillo.js 写下的样子

预览

把这段放进 <section rst-box><form rst-form> 里。

<div rst-form-foot>
  <button rst-btn="primary lg" type="submit" aria-busy="true" disabled>
    <span rst-spin aria-hidden="true"></span>
    发布
  </button>
  <a rst-btn="lg" href="/posts">返回文章列表</a>
</div>

只有被点击的那个按钮:表单里其他提交按钮都保留自己的 name 和 value,表单本身也会防住第二次提交。data-busy="false" 可以退出,写在表单上或某一个按钮上;data-busy-label 用来替换文字。关掉脚本,这些都不会发生,表单照旧提交,所以幂等仍然是服务器的事。

confirm-form

确认页的操作行。破坏性操作有自己的路由而不是一个对话框,所以没有什么需要写脚本。

破坏性操作

预览

把这段放进 <section rst-box> 里。

<form rst-form-actions method="post" action="/posts/1/delete">
  <input type="hidden" name="csrf" value="tok-123">
  <input type="hidden" name="id" value="1">
  <a rst-btn="ghost lg" href="/posts/1">取消</a>
  <button type="submit" rst-btn="danger lg">删除这篇文章</button>
</form>

把这段放进 <section rst-box> 里。

{{template "confirm-form" dict
    "Action" "/posts/1/delete"
    "Label" "删除这篇文章"
    "Danger" true
    "Hidden" (list (list "csrf" "tok-123") (list "id" "1"))
    "CancelHref" "/posts/1"}}

普通情况,取消用默认措辞

预览

把这段放进 <section rst-box> 里。

<form rst-form-actions method="post" action="/posts/1/publish">
  <a rst-btn="ghost lg" href="/posts/1">取消</a>
  <button type="submit" rst-btn="primary lg">发布</button>
</form>

把这段放进 <section rst-box> 里。

{{template "confirm-form" dict
    "Action" "/posts/1/publish"
    "Label" "发布"
    "CancelHref" "/posts/1"}}

没有 CancelLabel,所以取消链接的措辞取自目录。