2.1 #Components.Accordion Accordion
This implementation is not good for accessibility and should be completely rewritten. For more details, see this article on the matter.
A basic accordian component using Details / Summary
A summary!
I'm the detail content!
Markup
<details class="tv-summary">
<summary>A summary! <div class="indicator"><i class="fa fa-plus"></i><i class="fa fa-minus"></i></div></summary>
<p class="content">
I'm the detail content!
</p>
</details>
resources/sass/_components/details-summary-accordion.scss
, line 12
2.3 #Components.Dropdown Dropdown
The open / close click handling is normally handled by the JS framework. Remove the click handler included in the markup below when creating a dropdown in the application.
If a "selected state" variant is desired, you will need a means of setting a class of selected
on dropdown__menu-item elements.
A custom "select" or dropdown menu. There are variants that support showing a selected state for a single item, or multiple items.
Markup: ../../tv-styleguide/markup-files/dropdown.hbs
<div class="dropdown" dropdown>
<button type="button" class="dropdown__toggle" dropdown-toggle
onclick="this.parentElement.classList.toggle('open')">
Open / Close <i class="dropdown__toggle-indicator fa fa-angle-down"></i>
</button>
<ul class="dropdown__menu {{modifier_class}}">
<li class="dropdown__menu-item selected">Item 1</li>
<li class="dropdown__menu-item">Item 2</li>
<li class="dropdown__menu-item">Item 3</li>
</ul>
</div>
resources/sass/_components/dropdown.scss
, line 8
2.3.1 #Components.Dropdown.Multi Multi Menu Dropdown
The open / close click handling is normally handled by the JS framework. Remove the click handler included in the markup below when creating a dropdown in the application.
If a "selected state" variant is desired, you will need a means of setting a class of selected
on dropdown__menu-item elements.
A dropdown featuring multiple menus side-by-side. In practice this is used for the filters menu, where items in the menu are also togglable.
Markup: ../../tv-styleguide/markup-files/dropdown-multi.hbs
<div class="dropdown dropdown--multi-menu" dropdown>
<button type="button" class="button dropdown__toggle" dropdown-toggle
onclick="this.parentElement.classList.toggle('open')">
Open / Close <i class="dropdown__toggle-indicator fa fa-angle-down"></i>
</button>
<div class="dropdown__menu">
<ul class="dropdown__menu--multi-select">
<span class="dropdown__menu-header">Side 1</span>
<li class="dropdown__menu-item selected">
Item 1
</li>
<li class="dropdown__menu-item">
Item 2
</li>
<li class="dropdown__menu-item">
Item 3
</li>
</ul>
<ul class="dropdown__menu--multi-select">
<span class="dropdown__menu-header">Side 2</span>
<li class="dropdown__menu-item">
Item 1
</li>
<li class="dropdown__menu-item selected">
Item 2
</li>
<li class="dropdown__menu-item">
Item 3
</li>
</ul>
</div>
</div>
resources/sass/_components/dropdown.scss
, line 281
2.4 #Components.Overlays Overlays
Read the details on A11y Dialog's markup reference for an explanqtion regarding accessibility of overlay markup.
Overlays in ThankView leverage a library called A11y Dialog for most of their functionality. Opening and closing is normally handled by the framework, click handlers here are for demo purposes only (see script comments in markup).
Markup: ../../tv-styleguide/markup-files/tv-overlay.hbs
<button type="button" class="btn btn--primary" onclick="openDialog('example-dialog-basic')">Open Overlay</button>
<div id="example-dialog-basic" class="tv-overlay" tv-overlay aria-labelledby="my-dialog-title" aria-hidden="true">
<div class="tv-overlay__backdrop" data-a11y-dialog-hide></div>
<div class="tv-overlay__content-wrapper" role="document">
<div class="tv-overlay__header">
<button class="btn btn--ghost btn--close" onclick="closeDialog('example-dialog-basic')"
aria-label="Close dialog">
<svg class="tv-icon close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
<div class="tv-overlay__content">
<h1 class="tv-overlay__header-text" id="my-dialog-title">Some Header Text</h1>
<p class="tv-overlay__message" ng-bind-html="message">Message content can either be hard coded into HTML, or
dynamically bound via the framework. Multiple <code>tv-overlay__message</code> elements are allowed.</p>
</div>
{{!-- if you pass an array of button objects (see overlay-message.js for an exmaple) use the tv-overlay__actions
container with the compile-buttons directive --}}
<div class="tv-overlay__actions" compile-buttons buttons="{{buttonMarkup}}"></div>
</div>
</div>
<script>
// this open function is for documentation demonstrations only
// in the ThankView application opening is handled by the `tv-overlay` directive
// which will respond to the 'open-overlay' event
function openDialog(id) {
const dialog = new A11yDialog(document.getElementById(id));
dialog.show();
};
// this function is just for demonstrative purposes
// in the application, assign ng-click="close()" to the standard close button
function closeDialog(id) {
const dialog = new A11yDialog(document.getElementById(id));
dialog.hide();
}
</script>
resources/sass/_components/overlay.scss
, line 8
2.4.1 #Components.Overlays.Fullscreen Fullscreen Overlays
Fullscreen overlays in ThankView are overlays that appear to be a full page, but don't actually navigate the user to a new page. An example is the edit video view from the video library. They feature a larger header with "back" navigation, and their content area can be treated as a full page.
Markup: ../../tv-styleguide/markup-files/tv-overlay-fullscreen.hbs
<button type="button" class="btn btn--primary" onclick="openDialog('example-fullscreen-dialog')">Open Fullscreen
Overlay</button>
<div id="example-fullscreen-dialog" class="tv-overlay tv-overlay--fullscreen" tv-overlay
aria-labelledby="my-fullscreen-dialog-title" aria-hidden="true">
<div class="tv-overlay__content-wrapper" role="document">
<div class="tv-overlay__header">
<button class="btn btn--ghost btn--close" onclick="closeDialog('example-fullscreen-dialog')"
aria-label="Close dialog">
<svg class="tv-icon close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
<div class="tv-overlay__content">
<h1 id="my-fullscreen-dialog-title" class="tv-overlay__header-text">I'm Fullscreen!</h1>
<p class="tv-overlay__message">Whatever content or layout you need goes here.</p>
</div>
</div>
</div>
resources/sass/_components/overlay.scss
, line 260
2.4.2 #Components.Overlays.Modal Modal Overlays
Modal overlays in ThankView are a larger variant of the standard overlay that generally feature some content and a media (decorative or otherwise) element side-by-side. They also feature a header bar with a close button, and can contain action buttons using an element with the class tv-overlay__actions. Open and close interactions work the same way as the base overlay.
Markup: ../../tv-styleguide/markup-files/tv-overlay-modal.hbs
<button type="button" class="btn btn--primary" onclick="openDialog('example-modal-dialog')">Open Modal</button>
<div id="example-modal-dialog" class="tv-overlay tv-overlay--modal" tv-overlay aria-labelledby="my-dialog-title"
aria-hidden="true">
<div class="tv-overlay__backdrop" data-a11y-dialog-hide></div>
<div class="tv-overlay__content-wrapper" role="document">
<div class="tv-overlay__header">
<button class="btn btn--ghost btn--close" onclick="closeDialog('example-modal-dialog')"
aria-label="Close dialog">
<svg class="tv-icon close" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
<path d="M0 0h24v24H0z" fill="none" />
</svg>
</button>
</div>
<div class="tv-overlay__content">
<h1 id="my-dialog-title" ng-show="header">Modal Overlay</h1>
<p class="tv-overlay__message">This is a larger variant that typically displays some media on one half.</p>
</div>
<div class="tv-overlay__media width-100 bg-size-cover bg-pos-center"></div>
</div>
</div>
resources/sass/_components/overlay.scss
, line 236
2.5 #Components.Selection Selection Controls
Standard set of custom UI elements for selection uses (e.g. checkboxes, radio buttons, etc.)
resources/sass/_components/radio-button.scss
, line 2
2.5.1 #Components.Selection.Checkbox Checkbox
If for some reason your checkbox is not directly associated with some text, you must still provide text for a screen reader. Add the tv-checkbox__text--hidden
to hide text visually.
Component to create a custom checkbox visual. Makes use of the inclusive hiding method described by Sara Soueidan in this article. This could be significantly improved with SVG in place of Font Awesome icons
Markup
<label for="my-checkbox" class="tv-checkbox">
<input id="my-checkbox" type="checkbox" class="tv-checkbox__input" />
<div class="tv-checkbox__visual">
<i class="far fa-square"></i>
<i class="fas fa-check-square"></i>
</div>
<span class="tv-checkbox__text [modifier class]">Check Me!</span>
</label>
resources/sass/_components/checkbox.scss
, line 1
2.5.3 #Components.Selection.Toggle Switch Toggle Switch
Styles a checkbox as a toggle switch.
Markup
<div class="toggle-switch">
<input class="toggle-switch__input" id="my-toggle" type="checkbox" />
<label class="toggle-switch__label font-size-16px" for="my-toggle">
<div class="toggle-switch__visual"></div>
<span>I'm A Switch</span>
</label>
</div>
resources/sass/account/toggle-switch.scss
, line 1
2.6 #Components.Tables Table
Tables in the "account flow" should start with this base class to create a responsive table with horizontal overflow scrolling
Col 1 | Col 2 |
---|---|
Data 1 | Data 2 |
Data 1 | Data 2 |
Markup: ../../tv-styleguide/markup-files/account-table.hbs
<div class="account-table" tv-table-list>
<div class="account-table__container">
<div class="table-scroll table-scroll-2">
<div class="scroll-gradient"></div>
<table>
<thead>
<tr>
<th class="account-table__col">Col 1</th>
<th class="account-table__col">Col 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
resources/sass/_components/account-table.scss
, line 3
2.6.1 #Components.Tables.Data Table Data Table
The most comprehensive version of the data table is used for Contacts/Recipients pages. Most versions of the table have a sticky-positioned header container so that column headers are always visible when scrolling through the rows. The name column is also absolute-positioned for readability when scrolling horizontally. Because of the sticky-positioned header JavaScript is used to sync the horizontal scroll of the header container and rows. JavaScript is also used for the actions dropdown menu in order to give it the correct positioning as its direct ancestor/parent needed to be a static-positioned element to avoid clipping from an overflow rule further up the hierarchy.
John Doe | johndoe@thankview.com | Jan 1, 2020 | Jan 1, 2020 | ||
Jane Doe | janedoe@thankview.com | Jan 1, 2020 | Jan 1, 2020 |
Markup: ../../tv-styleguide/markup-files/data-table.hbs
<div class="data-table" role="table" aria-labelledby="page-title-header" aria-label="Guests Table" tabindex="0"
tv-data-table>
<div class="data-table__header-container" style="top:0;">
<div class="scroller" name="guestListHeader" role="row">
<div class="data-table__header check-col" role="columnheader">
<label class="tv-checkbox">
<input class="tv-checkbox__input" type="checkbox" />
<div class="tv-checkbox__visual">
<i class="far fa-square"></i>
<i class="fas fa-check-square"></i>
</div>
</label>
</div>
<div class="data-table__header name-col" role="columnheader">
Name
<div class="flex flex-dir-col margin-l-8px font-size-20px">
<i class="fa fa-caret-up"></i>
<i class="fa fa-caret-down"></i>
</div>
</div>
<div class="data-table__header email-col" role="columnheader">
Email / Phone
<div class="flex flex-dir-col margin-l-8px font-size-20px">
<i class="fa fa-caret-up"></i>
<i class="fa fa-caret-down"></i>
</div>
</div>
<div class="data-table__header recorded-on-col" role="columnheader">
Recorded On
</div>
<div class="data-table__header recorded-on-col" role="columnheader">
Recorded On
</div>
<div class="data-table__header actions-col" role="columnheader">
Actions
</div>
</div>
</div>
<div class="data-table__data" name="guestList">
<table>
<tbody>
<tr class="table-row">
<td class="data-table__col check-col">
<label class="tv-checkbox">
<input class="tv-checkbox__input" type="checkbox" />
<div class="tv-checkbox__visual">
<i class="far fa-square"></i>
<i class="fas fa-check-square"></i>
</div>
</label>
</td>
<td class="data-table__col name-col pad-r-4px">
John Doe
</td>
<td class="data-table__col email-col" title="johndoe@thankview.com">johndoe@thankview.com</td>
<td class="data-table__col recorded-on-col">Jan 1, 2020</td>
<td class="data-table__col recorded-on-col">Jan 1, 2020</td>
<td class="data-table__col dropdown actions-col" dropdown>
<button data-object-id="object-1" class="dropdown-toggle width-100 border-none font-gray-45"
dropdown-toggle onclick="positionActionsDropdown(event)">
<i class="fa fa-ellipsis-h font-size-24px pointer-events-none"></i>
</button>
<ul id="object-1-actions" class="dropdown__menu">
<li>Action 1</li>
<li>Action 2</li>
<li>Action 3</li>
</ul>
</td>
</tr>
<tr class="table-row">
<td class="data-table__col check-col">
<i class="fa fa-square-o font-size-20px relative top-2px" aria-hidden="true"></i>
</td>
<td class="data-table__col name-col pad-r-4px">
Jane Doe
</td>
<td class="data-table__col email-col" title="janedoe@thankview.com">janedoe@thankview.com</td>
<td class="data-table__col recorded-on-col">Jan 1, 2020</td>
<td class="data-table__col recorded-on-col">Jan 1, 2020</td>
<td class="data-table__col dropdown actions-col" dropdown>
<button data-object-id="object-2" class="dropdown-toggle width-100 border-none font-gray-45"
dropdown-toggle onclick="positionActionsDropdown(event)">
<i class="fa fa-ellipsis-h font-size-24px pointer-events-none"></i>
</button>
<ul id="object-2-actions" class="dropdown__menu">
<li>Action 1</li>
<li>Action 2</li>
<li>Action 3</li>
</ul>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<script>
// Only for demonstration in style guide. Refer to data-table component file for production code
const headers = document.querySelector('.data-table__header-container .scroller');
const dataTable = document.querySelector('.data-table__data');
dataTable.addEventListener('scroll', () => {
headers.scrollLeft = dataTable.scrollLeft;
});
const dropdowns = document.querySelectorAll('.data-table__col.actions-col');
const closeDropdowns = () => {
dropdowns.forEach((d) => {
d.classList.remove('open');
})
}
document.addEventListener('click', closeDropdowns);
const positionActionsDropdown = ($event) => {
$event.stopPropagation();
closeDropdowns();
$event.target.parentElement.classList.toggle('open');
const objectId = $event.target.dataset.objectId;
const parentHeight = $event.target.parentNode.offsetHeight;
const topNew = $event.target.offsetTop + (parentHeight / 2);
const rightNew = $event.target.parentNode.offsetWidth / 2;
const dropdownMenu = document.getElementById(objectId + '-actions');
dropdownMenu.style.top = topNew + 'px';
dropdownMenu.style.right = rightNew + 'px';
}
</script>
resources/sass/_components/data-table.scss
, line 33
2.6.2 #Components.Tables.With Header Table with Fixed Header
Fixed header tables have the header section "stick" to the top of the page when the table content scrolls vertically
Col 1 | Col 2 |
---|---|
Data 1 | Data 2 |
Data 1 | Data 2 |
Col 1 | Col 2 |
---|---|
Data 1 | Data 2 |
Data 1 | Data 2 |
Markup: ../../tv-styleguide/markup-files/account-table-with-header-fixed.hbs
<div class="account-table" tv-table-list>
<div class="account-table__header">
<div class="search-field margin-r-4px v-align-middle">
<form no-validate>
<i class="fa fa-search"></i>
<input type="text" placeholder="Search for a Result" />
<button class="fa fa-times btn--ghost" ng-click="clearSearch($event)"></button>
</form>
</div>
<div class="button-group inline-block v-align-middle width-100 md-width-auto">
<button class="btn btn--small btn--primary">
<i class="fa fa-plus" aria-hidden="true"></i> Action 1
</button>
<button class="btn btn--small btn--secondary">
<i class="fa fa-plus" aria-hidden="true"></i> Action 2
</button>
</div>
</div>
<div class="account-table__container">
<div class="table-scroll table-scroll-2">
<div class="scroll-gradient"></div>
<table>
<thead>
<tr>
<th class="account-table__col">Col 1</th>
<th class="account-table__col">Col 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
resources/sass/_components/account-table.scss
, line 18
2.6.2 #Components.Tables.With Header Table with Header
Tables can include a "header" section containing action buttons, menus or a search field. This is achieved by adding a header div before the account-table__container div
Col 1 | Col 2 |
---|---|
Data 1 | Data 2 |
Data 1 | Data 2 |
Col 1 | Col 2 |
---|---|
Data 1 | Data 2 |
Data 1 | Data 2 |
Markup: ../../tv-styleguide/markup-files/account-table-with-header.hbs
<div class="account-table" tv-table-list>
<div class="account-table__header">
<div class="search-field margin-r-4px v-align-middle">
<form no-validate>
<i class="fa fa-search"></i>
<input type="text" placeholder="Search for a Result" />
<button class="fa fa-times btn--ghost" ng-click="clearSearch($event)"></button>
</form>
</div>
<div class="button-group inline-block v-align-middle width-100 md-width-auto">
<button class="btn btn--small btn--primary">
<i class="fa fa-plus" aria-hidden="true"></i> Action 1
</button>
<button class="btn btn--small btn--secondary">
<i class="fa fa-plus" aria-hidden="true"></i> Action 2
</button>
</div>
</div>
<div class="account-table__container">
<div class="table-scroll table-scroll-2">
<div class="scroll-gradient"></div>
<table>
<thead>
<tr>
<th class="account-table__col">Col 1</th>
<th class="account-table__col">Col 2</th>
</tr>
</thead>
<tbody>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
<tr>
<td>Data 1</td>
<td>Data 2</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
resources/sass/_components/account-table.scss
, line 51
2.7 #Components.Text Fields Text Field
The associated label for a text input should primarily be styled using utility classes.
Text inputs must have an associated label. The for
attribute should contain the id
of the corresponding input. We cannot use placeholder
text as the only label. In general we should never hide input labels, but if we must, use the sr-only
utility class on the label element.
Component for basic text input.
Markup
<label for="my-text-input" class="block font-size-16px font-w-7">My Text Input:</label>
<input class="tv-text-input" type="text" id="my-text-input" placeholder="Type Something Great" />
resources/sass/_components/text-input.scss
, line 1
2.7.1 #Components.Text Fields.Search Field Search Field
TODO: This implementation could be improved using input type="search"
. Doing so at the moment would likely break visual styles. We also need to document how to label the search field for screen readers.
Component to filter search via text input. Wrapper div is styled to match the basic text input field.
Markup
<div class="search-field">
<form id="search-for-thing">
<i class="fa fa-search"></i>
<input type="text" placeholder="Search for a Result" ng-change="updateSearch($event)" />
<button type="button" class="fa fa-times btn--ghost" title="Clear Search" ng-click="clearSearch($event)">
</button>
</form>
</div>
resources/sass/_components/text-input.scss
, line 16