26 | ```
27 |
28 | ## Api
29 |
30 | ### Properties
31 |
32 | | Property | Type | Description | Default |
33 | | :--- | :--- | :--- | :--- |
34 | | `locale` | *string* | Represents a Unicode locale identifier. | `default` |
35 | | `date` | *string*, *Date* | A valid javascript date object or a parsable javascript date string. | `new Date()` |
36 |
37 | [0]:https://github.com/github/time-elements
38 |
--------------------------------------------------------------------------------
/src/markdown/components/router.md:
--------------------------------------------------------------------------------
1 | # Router
2 | The `Router` component will render its children components if the browser's current URL matches its `path` property. This component will detect `pushstate`, `popstate`, and `replacestate` events and re-render with the attributes of the URL as props.
3 |
4 | ## Demo
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 | Hello, World
17 |
18 |
19 | number prop has the value .
20 |
21 |
22 | 404
23 |
24 |
25 |
26 | ## Code
27 |
28 | #### HTML
29 |
30 | ```html
31 |
32 | Hello, World
33 |
34 |
35 |
36 | number prop has the value .
37 |
38 |
39 | 404
40 | ```
41 |
42 | ## Api
43 |
44 | ### Properties
45 |
46 | | Property | Type | Description | Default |
47 | | :--- | :--- | :--- | :--- |
48 | | `path` | *string* | A tokenized string to match against the current url, `/books/:book` for example. | |
49 | | `none` | *string* | If specified, and no matches have been made so far, this component will render. | |
50 | | `id` | *string* | If specified, provides a way to reference the component instance and listen for events on it. | |
51 |
52 |
53 | ### Events
54 |
55 | | Name | Description |
56 | | :--- | :--- |
57 | | `match` | Emitted when a content section receives the `show` class because it matches the current url. |
58 |
--------------------------------------------------------------------------------
/src/markdown/components/select.md:
--------------------------------------------------------------------------------
1 | # Select
2 |
3 | The `Select` component creates a select input.
4 |
5 | ## Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## Code
16 |
17 | #### HTML
18 | ```html
19 |
20 |
21 |
22 |
23 |
24 | ```
25 |
26 | ## API
27 |
28 | ### Properties
29 |
30 | | Property | Type | Description | Default |
31 | | :--- | :--- | :--- | :--- |
32 | | `id` | *string* | Select box with `id` attribute. | |
33 | | `required` | *boolean* | Makes the select box required. | `false` |
34 | | `disabled` | *boolean* | Makes the select box disabled. | `false` |
35 | | `label` | *string* | Adds a label to the select box. | |
36 | | `width` | *string* | Width of the select box. | `250px` |
37 | | `height` | *string* | Height of the select box. | |
38 | | `radius` | *string* | Radius of the select box. | `2px` |
39 | | `multiple` | *boolean* | Show as multiple select. | |
40 | | `size` | *string* | The number of visible items for a multiple select. | |
41 | | `value` | *string* | The default value that will be selected. | |
42 | | `tabindex` | *number* | Add a `tabindex` for the select box. | |
43 | | `theme` | *string* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
44 |
45 | ### Instance Methods
46 |
47 | | Method | Description |
48 | | :--- | :--- |
49 | | `change()` | The native `change` event on the select input. |
50 | | `loading(state)` | Adds loading to the current select box. | |
51 |
52 | ### Instance Members
53 |
54 | | Property | Description |
55 | | :--- | :--- |
56 | | `option` | A getter/setter that provides the currently selected option from the select input inside the component. |
57 | | `value` | A getter/setter that provides the current value of the select input from inside of the component. |
58 | | `selectedIndex` | A getter/setter that provides the selected index of the select input inside the component. |
59 |
--------------------------------------------------------------------------------
/src/markdown/components/split.md:
--------------------------------------------------------------------------------
1 | # Split
2 |
3 | A resizable `Split` component that can contain any number of deeply nested
4 | splits or child components (doesn't currently work on mobile).
5 |
6 | ## Demo
7 |
8 |
9 |
20 |
21 |
22 |
23 |
24 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
39 |
40 |
41 |
42 |
43 | ## Code
44 |
45 | #### HTML
46 |
47 | ```
48 |
49 |
50 |
51 |
52 |
55 |
56 |
57 |
60 |
61 |
62 |
63 |
64 |
67 |
68 |
69 | ```
70 |
71 | ## API
72 |
73 | ### Properties
74 |
75 | | Property | Type | Description | Default |
76 | | :--- | :--- | :--- | :--- |
77 | | `id` | *string* | Select box with `id` attribute. | |
78 | | `width` | *string* | Width of the select box. | `250px` |
79 | | `height` | *string* | Height of the select box. | |
80 |
81 | ### Instance Methods
82 |
83 | | Method | Description |
84 | | :--- | :--- |
85 | | `toggle(panel, force)` | Show or hide an area of the split. For a `vertical` split, `panel` will be `top` or `bottom`. For a `horizontal` split, `panel` will be `left` or `right`. The boolean `force` parameter will force it to show (true) or hide (false). |
86 |
87 | ### Instance Members
88 |
89 | | Property | Description |
90 | | :--- | :--- |
91 | | `meta` | Gives you some information about the state of the split. |
92 |
--------------------------------------------------------------------------------
/src/markdown/components/sprite.md:
--------------------------------------------------------------------------------
1 | # Sprite
2 |
3 | The `Sprite` component produces an inline svg that contains symbols
4 | which can be referred to by other components.
5 |
6 | You should include the `tonic-sprite` once, somewhere in the body, but before
7 | other components that use it.
8 |
9 | #### HTML
10 | ```html
11 |
12 | ```
13 |
14 | ---
15 |
16 | To use a symbol from another component, you can refer to the symbol by its `id`
17 | property, for example...
18 |
19 | #### JS
20 | ```js
21 | class MyComponent extends Tonic {
22 | render () {
23 | return this.html`
24 |
25 | `
26 | }
27 | }
28 | ```
29 |
--------------------------------------------------------------------------------
/src/markdown/components/tabs.md:
--------------------------------------------------------------------------------
1 | # Tabs
2 | The `tonic-tabs` and `tonic-tab-panel` components create a tab list that activates sections when clicked on.
3 |
4 | ## Demo
5 |
6 |
7 |
8 | One
11 | Two
14 | Three
17 |
18 |
19 | Content One
20 |
21 |
22 | Content Two
23 |
24 |
25 | Content Three
26 |
27 |
28 |
29 | ## Code
30 |
31 | The tabs are grouped under the `tonic-tabs` component. Each tab should be a
32 | `tonic-tab` element, and is associated with a `tonic-tab-panel` id with the
33 | `for` element.
34 |
35 | The selected tab should be specified by providing an `id` as the value
36 | for the `selected` attribute on the `tonic-tabs` component.
37 |
38 | #### HTML
39 | ```html
40 |
41 |
44 | One
45 |
46 |
49 | Two
50 |
51 |
54 | Three
55 |
56 |
57 | ```
58 |
59 | ---
60 |
61 | #### HTML
62 |
63 | ```html
64 |
65 | Content One
66 |
67 |
68 |
69 | Content Two
70 |
71 |
72 |
73 | Content Three
74 |
75 | ```
76 |
77 | ## API
78 |
79 | ### Properties
80 |
81 | *for `tonic-tabs`*
82 |
83 | | Property | Type | Description | Default |
84 | | :--- | :--- | :--- | :--- |
85 | | `selected` | *string* | Adds the `id` attribute. | |
86 |
87 | *for `tonic-tab`*
88 |
89 | | Property | Type | Description | Default |
90 | | :--- | :--- | :--- | :--- |
91 | | `id` | *string* | Adds the `id` attribute. required | |
92 | | `for` | *string* | Adds the `id` attribute. required | |
93 |
94 | *for `tonic-tab-panel`*
95 |
96 | | Property | Type | Description | Default |
97 | | :--- | :--- | :--- | :--- |
98 | | `id` | *string* | Adds the `id` attribute. required | |
99 |
100 | ### Instance Methods & Members
101 |
102 | *for `tonic-tabs`*
103 |
104 | | Method | Description |
105 | | :--- | :--- |
106 | | `click()` | Click event. |
107 | | `get value` | Get the currently selected tab. |
108 | | `set selected(String)` | Set the currently selected tab. |
109 |
110 | ### Events
111 |
112 | *for `tonic-tabs`*
113 |
114 | | Event | Description |
115 | | :--- | :--- |
116 | | `tabvisible` | Emitted when a tab becomes visible. Contains `event.detail.id` for which tab is visible. |
117 | | `tabhidden` | Emitted when a tab becomes hidden. Contains `event.detail.id` for which tab is hidden. |
118 | | `tabvisible`, `tabhidden` | Emitted when a tab is changed and gets triggered both from click & keyboard events. |
119 |
--------------------------------------------------------------------------------
/src/markdown/components/textarea.md:
--------------------------------------------------------------------------------
1 | # Textarea
2 |
3 | The `Textarea` component creates a text area.
4 |
5 | ## Demo
6 |
7 |
8 | It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness, it was the epoch of belief, it was the epoch of incredulity, it was the season of Light, it was the season of Darkness, it was the spring of hope, it was the winter of despair, we had everything before us, we had nothing before us, we were all going direct to Heaven, we were all going direct the other way—in short, the period was so far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.
9 |
10 |
11 | ## Code
12 |
13 | #### Html
14 | ```html
15 |
20 |
21 |
22 | ```
23 |
24 | ## Api
25 |
26 | ### Properties
27 |
28 | | Property | Type | Description | Default |
29 | | :--- | :--- | :--- | :--- |
30 | | `id` | *string* | Text area with `id` attribute. required |
31 | | `ariaLabelledby` | *string* | Sets the `area-labelledby` attribute. | |
32 | | `autofocus` | *boolean* | Enable `autofocus` on the text area. | `false` |
33 | | `cols` | *string* | Set number of columns. | |
34 | | `disabled` | *boolean* | Text area with `disabled` attribute. | `false` |
35 | | `height` | *string* | Set height of text area. | `100%` |
36 | | `label` | *string* | Creates a label. | |
37 | | `maxlength` | *string* | Set the maximum character length. | |
38 | | `minlength` | *string* | Set the minimum character length. | |
39 | | `name` | *string* | Text area with `name` attribute. | |
40 | | `persistSize` | *boolean* | Persist the resized width and height | `false` |
41 | | `placeholder` | *string* | Add placeholder to text area. | |
42 | | `radius` | *string* | Set radius of text area. | `2px` |
43 | | `readonly` | *boolean* | Set text area to `readonly`. | `false` |
44 | | `required` | *boolean* | Set text area to `required`. | `false` |
45 | | `resize` | *string* | Set to `none` to disable resize. | |
46 | | `rows` | *string* | Set number of rows. | |
47 | | `spellcheck` | *boolean* | Enable spellcheck. | `true` |
48 | | `tabindex` | *number* | Add a `tabindex` for the text area. | |
49 | | `theme` | *string* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
50 | | `width` | *string* | Set width of text area. | |
51 |
52 | ### Instance Methods & Members
53 |
54 | | Method | Description |
55 | | :--- | :--- |
56 | | `value` | A getter/setter that provides the current value of the text area from inside of the component. |
57 |
--------------------------------------------------------------------------------
/src/markdown/components/toaster-inline.md:
--------------------------------------------------------------------------------
1 | # ToasterInline
2 | The `ToasterInline` component creates an inline toaster item that appears on the
3 | screen either for a duration or until the user acknowledges it.
4 |
5 | > *__Note:__ This component requires the `tonic-sprite` component.*
6 |
7 | ## Demo
8 |
9 |
10 |
13 | Notify Me
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 | Hello,
27 | World
28 |
29 |
30 |
31 | ## Code
32 |
33 | #### HTML
34 | ```html
35 |
36 |
37 | ```
38 |
39 | #### JS
40 | ```js
41 | const toaster1 = document.getElementById('toaster-1')
42 | const toasterLink1 = document.getElementById('toaster-link-1')
43 |
44 | toasterLink1.addEventListener('click', e => {
45 | toaster1.show()
46 | })
47 | ```
48 |
49 | ---
50 |
51 | An inline toaster that is displayed initially:
52 |
53 | #### HTML
54 | ```html
55 |
59 | Displayed initially. Uses HTML.
60 |
61 | ```
62 |
63 | NOTE: An inline toaster item that is displayed initially must declare each property in the HTML and display it using `display="true"`. This will create the notification.
64 |
65 | Otherwise, the properties for the toaster item to be created must be specified in the javascript, where it is created.
66 |
67 | ## Api
68 |
69 | ### Properties
70 |
71 | | Property | Type | Description | Default |
72 | | :--- | :--- | :--- | :--- |
73 | | `id` | *string* | Adds an `id` attribute. | |
74 | | `name` | *string* | Adds a `name` attribute. | |
75 | | `title` | *string* | Adds a title. | |
76 | | `message` | *string* | Adds a message. If no message attribute is provided the inner HTML will be used. | |
77 | | `type` | *string* | Adds an alert type, `success`, `warning`, `danger` or `info`). | |
78 | | `duration` | *number* | The duration that the component will be displayed before being hidden. | |
79 | | `dismiss` | *boolean* | If set to `false`, the close button will not be added to the toaster item. | |
80 | | `display` | *boolean* | Specifies whether toaster is displayed initially. | `false` |
81 | | `theme` | *string* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
82 |
83 | ### Instance Methods & Members
84 |
85 | | Method | Description |
86 | | :--- | :--- |
87 | | `show()` | Shows the toaster. |
88 | | `hide()` | Hides a toaster item. |
89 | | `click()` | Removes a toaster item. |
90 |
--------------------------------------------------------------------------------
/src/markdown/components/toaster.md:
--------------------------------------------------------------------------------
1 | # Toaster
2 | The `Toaster` component creates a container for all toaster items to be added to.
3 |
4 | > *__Note:__ This component requires the `tonic-sprite` component.*
5 |
6 | ## Demo
7 |
8 |
9 |
10 |
13 | Notify Me
14 |
15 |
16 |
17 | ## Code
18 |
19 | The following code should be included once on the page:
20 |
21 | #### HTML
22 | ```html
23 |
24 | ```
25 |
26 | ---
27 |
28 | To create a new toaster item:
29 |
30 | #### JS
31 | ```js
32 | const notification = document.querySelector('tonic-toaster')
33 |
34 | document
35 | .getElementById('tonic-toaster-example')
36 | .addEventListener('click', e => {
37 | notification.create({
38 | type: 'success',
39 | title: 'Greetings',
40 | message: 'Hello, World'
41 | })
42 | })
43 | ```
44 |
45 | ## Api
46 |
47 | ### Properties
48 |
49 | | Property | Type | Description | Default |
50 | | :--- | :--- | :--- | :--- |
51 | | `id` | *string* | Adds an `id` attribute. | |
52 | | `name` | *string* | Adds a `name` attribute. | |
53 | | `type` | *string* | Adds an alert type (`success`, `warning`, `danger`, `info`). | |
54 | | `title` | *string* | Adds a title. | |
55 | | `message` | *string* | Adds a message. | |
56 | | `dismiss` | *boolean* | If set to `false`, the close button will not be added to the toaster. | `true` |
57 | | `duration` | *number* | Adds a duration. | `center` |
58 | | `position` | *string* | (On Parent `tonic-toaster` Element) Position of the toaster items, can be `left`, `right` or `center`. | `center` |
59 | | `theme` | *string* | (On Parent `tonic-toaster` Element) Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
60 |
61 | ### Instance Methods & Members
62 |
63 | | Method | Description |
64 | | :--- | :--- |
65 | | `click()` | Removes a toaster item. |
66 | | `create()` | Creates a toaster item. |
67 | | `destroy()` | Removes a toaster item. |
68 |
--------------------------------------------------------------------------------
/src/markdown/components/toggle.md:
--------------------------------------------------------------------------------
1 | # Toggle
2 |
3 | The `Toggle` component creates a toggle.
4 |
5 | ## Demo
6 |
7 |
8 |
12 |
13 |
14 |
15 | ## Code
16 |
17 | #### HTML
18 | ```html
19 |
22 |
23 | ```
24 |
25 | ## Api
26 |
27 | ### Properties
28 |
29 | | Property | Type | Description | Default |
30 | | :--- | :--- | :--- | :--- |
31 | | `id` | *string* | Adds the id
attribute required | |
32 | | `name` | *string* | Adds the name
attributes. | |
33 | | `disabled` | *boolean* | Makes the toggle disabled. | `false` |
34 | | `checked` | *boolean* | Turns the toggle "on". | `false` |
35 | | `label` | *string* | Adds a label to the toggle | |
36 | | `theme` | *string* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
37 |
38 | ### Instance Methods
39 |
40 | | Method | Description |
41 | | :--- | :--- |
42 | | `change()` | Bind to `change` event. |
43 |
44 | ### Instance Members
45 |
46 | | Method | Description |
47 | | :--- | :--- |
48 | | `value` | A getter/setter that returns the current value (state) of the toggle. |
49 |
--------------------------------------------------------------------------------
/src/markdown/components/tooltip.md:
--------------------------------------------------------------------------------
1 | # Tooltip
2 |
3 | The `Tooltip` component creates a dynamically positioned pop-up tooltip filled with custom content that shows during the `hover` state of the corresponding trigger element.
4 |
5 | ## Demo
6 |
7 |
8 |
9 | Hover over this text
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## Code
17 |
18 | The element that will be used to trigger the display of the tooltip must contain an `id` that matches the `for` attribute on the `tonic-tooltip` element.
19 |
20 | #### HTML
21 | ```html
22 | Hover over this text
23 |
24 |
25 |
26 |
27 | ```
28 |
29 | ## Api
30 |
31 | ### Properties
32 |
33 | | Property | Type | Description | Default |
34 | | :--- | :--- | :--- | :--- |
35 | | `for` | *string* | Adds a `for` attribute. required | |
36 | | `width` | *string* | Changes the `width` style. | |
37 | | `height` | *string* | Changes `height` style. | |
38 | | `theme` | *string* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
39 |
40 | ### Instance Methods & Members
41 |
42 | | Method | Description |
43 | | :--- | :--- |
44 | | `show()` | Shows the tooltip. |
45 | | `hide()` | Hides the tooltip. |
46 |
--------------------------------------------------------------------------------
/src/markdown/components/windowed.md:
--------------------------------------------------------------------------------
1 | # Windowed
2 | A base class is used for creating a windowed component.
3 |
4 | If you need to render large data sets (hundreds of thousands of rows for example), you can use a technique known as `windowing`. This technique renders a subset of your data while giving the user the impression that all the data
5 | has been rendered.
6 |
7 | ## Demo
8 |
9 | This demo generates the data after you click the overlay. Generating 500000 rows of data can take a second or two.
10 |
11 |
12 |
13 | Click to Load
14 |
15 |
16 |
17 |
18 |
19 | ## Code
20 |
21 | #### HTML
22 |
23 | ```html
24 |
25 |
26 | ```
27 |
28 | ## Api
29 |
30 | ### Properties
31 |
32 | | Property | Type | Description | Default |
33 | | :--- | :--- | :--- | :--- |
34 | | `row-height` | *Number* | Sets the height of each row. required | `30` |
35 | | `rows-page-page` | *Number* | The total number of rows per page to render. | `100` |
36 | | `height` | *String* | Sets the height of the outer container. | `inherit` |
37 | | `theme` | *String* | Adds a theme color (`light`, `dark` or whatever is defined in your base CSS. | `light` |
38 | | `debug` | *Boolean* | Add alternating page colors. | `false` |
39 |
40 | ### Instance Methods
41 |
42 | | Method | Description |
43 | | :--- | :--- |
44 | | `load(Array)` | Loads an array of data. |
45 | | `loaded()` | Called after the load function has been called. |
46 | | `getRows()` | Returns an array of all rows that are currently loaded. |
47 | | `getRow(Number)` | Get a row of data (returns an awaitable promise). |
48 |
49 | ### Instance Methods For Implementers
50 | | Method | Description |
51 | | :--- | :--- |
52 | | `render()` | Render the component, calling `super.render()` will render the row container structure. |
53 | | `renderEmptyState()` | If implemented, should return a structure that represents a state where there are no rows. |
54 | | `renderLoadingState()` | If implemented, should return a structure that represents a state that has not yet been completed. |
55 |
--------------------------------------------------------------------------------
/src/markdown/guides/composition.md:
--------------------------------------------------------------------------------
1 | # Composition
2 |
3 | ### Nesting
4 |
5 | With `Tonic` you can nest templates from other functions or methods.
6 |
7 | ```js
8 | class MyPage {
9 | renderHeader () {
10 | return this.html`Header
`
11 | }
12 | render () {
13 | return this.html`
14 | ${this.renderHeader()}
15 | My page
16 | `
17 | }
18 | }
19 | ```
20 |
21 | This means you can break up your `render() {}` method into multiple
22 | methods or re-usable functions.
23 |
24 | ### Conditionals
25 |
26 | If you want to do conditional rendering you can use if statements.
27 |
28 | ```js
29 | const LoginPage {
30 | render () {
31 | let message = 'Please Log in'
32 | if (this.props.user) {
33 | message = this.html`Welcome ${this.props.user.name}
`
34 | }
35 |
36 | return this.html`${message}
`
37 | }
38 | }
39 | ```
40 |
41 | ### Children
42 |
43 | Once you add components, they can be nested any way you want. The
44 | property `this.children` will get this component's child elements
45 | so that you can read, mutate or wrap them.
46 |
47 | ```js
48 | class ParentComponent extends Tonic {
49 | render () {
50 | return this.html`
51 |
52 |
53 | ${this.children}
54 |
55 |
56 | `
57 | }
58 | }
59 |
60 | Tonic.add(ParentComponent)
61 |
62 | class ChildComponent extends Tonic {
63 | render () {
64 | return this.html`
65 |
66 | ${this.props.value}
67 |
68 | `
69 | }
70 | }
71 |
72 | Tonic.add(ChildComponent)
73 | ```
74 |
75 | ### Input HTML
76 |
77 | ```html
78 |
79 |
80 |
81 | ```
82 |
83 | ### Output HTML
84 |
85 | ```html
86 |
87 |
88 |
89 |
90 | hello world
91 |
92 |
93 |
94 |
95 | ```
96 |
97 | ### Repeating templates
98 |
99 | You can embed an array of template results using `this.html`
100 |
101 | ```js
102 | class TodoList extends Tonic {
103 | render () {
104 | const todos = this.state.todos
105 |
106 | const lis = []
107 | for (const todo of todos) {
108 | lis.push(this.html`${todo.value}`)
109 | }
110 |
111 | return this.html``
112 | }
113 | }
114 | ```
115 |
116 | By using an array of template results, tonic will render your
117 | repeating templates for you.
118 |
--------------------------------------------------------------------------------
/src/markdown/guides/csp.md:
--------------------------------------------------------------------------------
1 | # CSP
2 |
3 | Tonic is `Content Security Policy` friendly. [This][0] is a good introduction to
4 | `CSP`s if you're not already familiar with how they work. This is an example policy,
5 | it's quite liberal, in a real app you would want these rules to be more specific.
6 |
7 | ```html
8 |
17 | ```
18 |
19 | For `Tonic` to work with a CSP, you need to set the [`nonce`][1] property. For
20 | example, given the above policy you would add the following to your javascript...
21 |
22 | ```js
23 | Tonic.nonce = 'c213ef6'
24 | ```
25 |
26 | [0]:https://developers.google.com/web/fundamentals/security/csp/
27 | [1]:https://en.wikipedia.org/wiki/Cryptographic_nonce
28 |
--------------------------------------------------------------------------------
/src/markdown/guides/events.md:
--------------------------------------------------------------------------------
1 | # Events
2 | There are two kinds of events. `Lifecycle Events` and `Interaction Events`.
3 | Tonic uses the regular web component lifecycle events but improves on them,
4 | see the API section for more details.
5 |
6 | Tonic helps you capture interaction events without turning your html into
7 | property spaghetti. It also helps you organize and optimize it.
8 |
9 | ```js
10 | class Example extends Tonic {
11 | //
12 | // You can listen to any DOM event that happens in your component
13 | // by creating a method with the corresponding name. The method will
14 | // receive the plain old Javascript event object.
15 | //
16 | mouseover (e) {
17 | // ...
18 | }
19 |
20 | change (e) {
21 | // ...
22 | }
23 |
24 | willConnect () {
25 | // The component will connect.
26 | }
27 |
28 | connected () {
29 | // The component has rendered.
30 | }
31 |
32 | disconnected () {
33 | // The component has disconnected.
34 | }
35 |
36 | updated () {
37 | // The component has re-rendered.
38 | }
39 |
40 | click (e) {
41 | //
42 | // You may want to check which element in the component was actually
43 | // clicked. You can also check the `e.path` attribute to see what was
44 | // clicked (helpful when handling clicks on top of SVGs).
45 | //
46 | if (!e.target.matches('.parent')) return
47 |
48 | // ...
49 | }
50 |
51 | render () {
52 | return this.html``
53 | }
54 | }
55 | ```
56 |
57 | The convention of most frameworks is to attach individual event listeners,
58 | such as `onClick={myHandler()}` or `click=myHandler`. In the case where
59 | you have a table with 2000 rows, this would create 2000 individual listeners.
60 |
61 | Tonic prefers the [event delegation][5] pattern. With event delegation, we
62 | attach a **single event listener** and watch for interactions on the child
63 | elements of a component. With this approach, fewer listeners are created and we
64 | do not need to rebind them when the DOM is re-created.
65 |
66 | Each event handler method will receive the plain old Javascript `event` object.
67 | This object contains a `target` property, the exact element that was clicked.
68 | The `path` property is an array of elements containing the exact hierarchy.
69 |
70 | Some helpful native DOM APIs for testing the properties of an element:
71 | - [`Element.matches(String)`][6] tests if an element matches a selector
72 | - [`Element.closest(String)`][7] finds the closest ancestor from the element
73 | that matches the given selector
74 |
75 | Tonic also provides a helper function that checks if the element matches the
76 | selector, and if not, tries to find the closest match.
77 |
78 | ```js
79 | Tonic.match(el, 'selector')
80 | ```
81 |
82 | You can attach an event handler in any component, for example here
83 | we attach an event handler in a `ParentElement` component that handles
84 | clicks from DOM elements in `ChildElement`.
85 |
86 | ### Example
87 | ```js
88 | class ChildElement extends Tonic {
89 | render () {
90 | return this.html`
91 | Click Me
92 | `
93 | }
94 | }
95 |
96 | class ParentElement extends Tonic {
97 | click (e) {
98 | const el = Tonic.match(e.target, '[data-event]')
99 |
100 | if (el.dataset.event === 'click-me') {
101 | console.log(el.dataset.bar)
102 | }
103 | }
104 |
105 | render () {
106 | return this.html`
107 |
108 |
109 | `
110 | }
111 | }
112 | ```
113 |
114 | The event object has an [`Event.stopPropagation()`][8] method that is useful for
115 | preventing an event from bubbling up to parent components. You may also be
116 | interested in the [`Event.preventDefault()`][9] method.
117 |
118 | [5]:https://davidwalsh.name/event-delegate
119 | [6]:https://developer.mozilla.org/en-US/docs/Web/API/Element/matches
120 | [7]:https://developer.mozilla.org/en-US/docs/Web/API/Element/closest
121 | [8]:https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation
122 | [9]:https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault
123 | [10]: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
124 |
--------------------------------------------------------------------------------
/src/markdown/guides/gettingstarted.md:
--------------------------------------------------------------------------------
1 | # Getting Started
2 |
3 | Building a component with Tonic starts by creating a function or a class.
4 | The class should have at least one method named *render* which returns a template literal
5 | of HTML.
6 |
7 | ```js
8 | import Tonic from '@socketsupply/tonic'
9 |
10 | class MyGreeting extends Tonic {
11 | render () {
12 | return this.html`Hello, World.
`
13 | }
14 | }
15 | ```
16 |
17 | or
18 |
19 | ```
20 | function MyGreeting () {
21 | return this.html`
22 | Hello, World.
23 | `
24 | }
25 | ```
26 |
27 | ---
28 |
29 | The HTML tag for your component will match the class or function name.
30 |
31 | > Note: Tonic is a thin wrapper around `web components`. Web
32 | > components require a name with two or more parts. So your class name should
33 | > be `CamelCased` (starting with an uppercase letter). For example, `MyGreeting`
34 | > becomes ``.
35 |
36 | ---
37 |
38 | Next, register your component with `Tonic.add(ClassName)`.
39 |
40 | ```js
41 | Tonic.add(MyGreeting)
42 | ```
43 |
44 | ---
45 |
46 | After adding your Javascript to your HTML, you can use your component anywhere.
47 |
48 | ```html
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | ```
57 |
58 | > Note: Custom tags (in all browsers) require a closing tag (even if
59 | > they have no children). Tonic doesn't add any "magic" to change how this works.
60 |
61 | ---
62 |
63 | When the component is rendered by the browser, the result of your render
64 | function will be inserted into the component tag.
65 |
66 | ```html
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | Hello, World.
75 |
76 |
77 |
78 | ```
79 |
80 | A component (or its render function) may be an `async` or an `async generator`.
81 |
82 | ```js
83 | class GithubUrls extends Tonic {
84 | async * render () {
85 | yield this.html`Loading...
`
86 |
87 | const res = await fetch('https://api.github.com/')
88 | const urls = await res.json()
89 |
90 | return this.html`
91 |
92 | ${JSON.stringify(urls, 2, 2)}
93 |
94 | `
95 | }
96 | }
97 | ```
98 |
99 | [0]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
100 | [1]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals
101 | [2]:https://caniuse.com/#search=domcontentloaded
102 |
--------------------------------------------------------------------------------
/src/markdown/guides/intro.md:
--------------------------------------------------------------------------------
1 |
2 |
A low profile component framework.
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | - 1 file. 1 class. ~350 lines of code.
11 | - No build tools required.
12 | - Native web components.
13 | - Ideal for JAM stacks.
14 |
15 |
16 |
17 | - Identical on client & server.
18 | - Composition oriented.
19 | - Event delegation by default
20 | - Lots of examples.
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/markdown/guides/methods.md:
--------------------------------------------------------------------------------
1 | # Methods
2 |
3 | A method is a function of a component. It can help to organize the internal
4 | logic of a component.
5 |
6 | The [constructor][0] is a special method that is called once each time an
7 | instance of your component is created.
8 |
9 | ```js
10 | class MyComponent extends Tonic {
11 | constructor () {
12 | super()
13 | // ...
14 | }
15 |
16 | myMethod (n) {
17 | this.state.number = n
18 | this.reRender()
19 | }
20 |
21 | render () {
22 | const n = this.state.number
23 |
24 | return this.html`
25 |
26 | The number is ${n}
27 |
28 | `
29 | }
30 | }
31 | ```
32 |
33 | After the component is created, the method `myMethod` can be called.
34 |
35 | ```js
36 | document.getElementById('foo').myMethod(42)
37 | ```
38 |
39 | [0]:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
40 |
--------------------------------------------------------------------------------
/src/markdown/guides/props.md:
--------------------------------------------------------------------------------
1 | # Properties
2 |
3 | `Props` are properties that are passed to the component in the form of HTML
4 | attributes. For example...
5 |
6 | ```js
7 | class MyApp extends Tonic {
8 | render () {
9 | return this.html`
10 |
11 |
12 | `
13 | }
14 | }
15 | ```
16 |
17 | ---
18 |
19 | Properties added to a component appear on `this.props` object.
20 |
21 | ---
22 |
23 | Tonic has no templating language, it uses HTML! But since HTML only
24 | understands string values, we need some help to pass more complex
25 | values to a component, and for that we use `this.html`.
26 |
27 | ```js
28 | const foo = {
29 | hi: 'Hello, world',
30 | bye: 'Goodbye, and thanks for all the fish'
31 | }
32 |
33 | class MyApp extends Tonic {
34 | render () {
35 | return this.html`
36 |
37 |
38 | `
39 | }
40 | }
41 | ```
42 |
43 | ```js
44 | class MyGreeting extends Tonic {
45 | render () {
46 | return this.html`
47 | ${this.props.messages.hi}
48 | `
49 | }
50 | }
51 | ```
52 |
53 | > Note: A property named `fooBar='30'` will become lowercased
54 | > (as per the HTML spec). If you want the property name to be camel cased when
55 | > added to the props object, use `foo-bar='30'` to get `this.props.fooBar`.
56 |
57 | ---
58 |
59 | You can use the "spread" operator to expand object literals into html properties.
60 |
61 | ```js
62 | class MyComponent extends Tonic {
63 | render () {
64 | const o = {
65 | a: 'testing',
66 | b: 2.2,
67 | fooBar: 'ok'
68 | }
69 |
70 | return this.html`
71 |
72 |
73 |
74 |
75 |
76 | `
77 | }
78 | }
79 | ```
80 |
81 | The above component renders the following output.
82 |
83 | ```html
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | ```
94 |
95 | ### Updating properties
96 |
97 | There is no evidence that Virtual DOMs improve performance across a broad set
98 | of use cases, but it's certain that they greatly increase complexity. Tonic doesn't
99 | use them. Instead, we recommend `incremental updates`. Simply put, you re-render a
100 | component when you think the time is right. The rule of thumb is to only re-render
101 | what is absolutely needed.
102 |
103 | ---
104 |
105 | To manually update a component you can use the `.reRender()` method. This method
106 | receives either an object or a function. For example...
107 |
108 | ```js
109 | // Update a component's properties
110 | this.reRender(props => ({
111 | ...props,
112 | color: 'red'
113 | }))
114 |
115 | // Reset a component's properties
116 | this.reRender({ color: 'red' })
117 |
118 | // Re-render a component with its existing properties
119 | this.reRender()
120 | ```
121 |
122 | ---
123 |
124 | The `.reRender()` method can also be called directly on a component.
125 |
126 | ```js
127 | document.getElementById('parent').reRender({ data: [1,2,3, ...9999] })
128 | ```
129 |
--------------------------------------------------------------------------------
/src/markdown/guides/ssr.md:
--------------------------------------------------------------------------------
1 | # Server Side Rendering.
2 |
3 | Tonic components are exactly the same on the server. You don't need
4 | any build tools or any special treatment. Just use the [`tonic-ssr`][0]
5 | module (it mocks-up a few dom apis that tonic needs).
6 |
7 | Check out the [`code for this site`][1] for a real life example.
8 |
9 | [0]:https://github.com/socketsupply/tonic-ssr
10 | [1]:https://github.com/socketsupply/tonic-docs
11 |
--------------------------------------------------------------------------------
/src/markdown/guides/state.md:
--------------------------------------------------------------------------------
1 | # Component State
2 |
3 | `this.state` is a plain-old javascript object. Its value will be persisted if
4 | the component is re-rendered. Any element that has an `id` attribute can use
5 | state, and any component that uses state must have an `id` property.
6 |
7 | ```js
8 | //
9 | // Update a component's state
10 | //
11 | this.state.color = 'red'
12 |
13 | //
14 | // Reset a component's state
15 | //
16 | this.state = { color: 'red' }
17 | ```
18 |
19 | ```html
20 |
21 |
22 |
23 |
24 | ```
25 |
26 | ---
27 |
28 | Setting the state will *not* cause a component to re-render. This way you can
29 | make incremental updates. Components can be updated independently. And
30 | rendering only happens only when necessary.
31 |
32 | Remember to clean up! States are just a set of key-value pairs on the `Tonic`
33 | object. So if you create temporary components that use state, clean up their
34 | state after you delete them. For example, if a list of a component with thousands
35 | of temporary child elements all uses state, I should delete their state after
36 | they get destroyed, `delete Tonic._states[someRandomId]`.
37 |
--------------------------------------------------------------------------------
/src/markdown/guides/styles.md:
--------------------------------------------------------------------------------
1 | # Styling
2 |
3 | Tonic supports multiple approaches to safely styling components.
4 |
5 | ### Option 1. Inline styles
6 |
7 | Inline styles are a security risk. Tonic provides the `styles()` method so you
8 | can inline styles safely. Tonic will apply the style properties when the `render()`
9 | method is called.
10 |
11 | ```js
12 | class MyGreeting extends Tonic {
13 | styles () {
14 | return {
15 | a: {
16 | color: this.props.fg,
17 | fontSize: '30px'
18 | },
19 | b: {
20 | backgroundColor: this.props.bg,
21 | padding: '10px'
22 | }
23 | }
24 | }
25 |
26 | render () {
27 | return this.html`${this.children}
`
28 | }
29 | }
30 | ```
31 |
32 | ```xml
33 | Hello, World
34 | ```
35 |
36 | ### Option 2. Dynamic Stylesheets
37 | The `stylesheet()` method will add a stylesheet to your component.
38 |
39 | ```js
40 | class MyGreeting extends Tonic {
41 | stylesheet () {
42 | return `
43 | my-greeting div {
44 | display: ${this.props.display};
45 | }
46 | `
47 | }
48 |
49 | render () {
50 | return this.html``
51 | }
52 | }
53 | ```
54 |
55 | ### Option 3. Static Stylesheets
56 | The static `stylesheet()` method will add a stylesheet to the document,
57 | but only once.
58 |
59 | ```js
60 | class MyGreeting extends Tonic {
61 | static stylesheet () {
62 | return `
63 | my-greeting div {
64 | border: 1px dotted #666;
65 | }
66 | `
67 | }
68 |
69 | render () {
70 | return this.html``
71 | }
72 | }
73 | ```
74 |
--------------------------------------------------------------------------------
/src/pages/examples.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs/promises'
2 | import path from 'node:path'
3 |
4 | import Tonic from 'tonic-ssr'
5 | import head from './mixins/head.js'
6 | import logo from './mixins/logo.js'
7 |
8 | const base = path.join('..', 'markdown', 'components')
9 | const __dirname = path.dirname(new URL(import.meta.url).pathname)
10 |
11 | class ExamplePage extends Tonic {
12 | async getComponentDocs () {
13 | const dir = path.join(__dirname, base)
14 | const files = await fs.readdir(dir)
15 | return files.map(file => ({
16 | name: file.replace('.md', ''),
17 | path: path.join(dir, file)
18 | }))
19 | }
20 |
21 | renderTableOfContents (files) {
22 | const html = []
23 |
24 | for (const file of files) {
25 | html.push(this.html`
26 |
27 | ${file.name}
28 |
29 | `)
30 | }
31 |
32 | return html
33 | }
34 |
35 | renderFiles (files) {
36 | const html = []
37 |
38 | for (const file of files) {
39 | html.push(this.html`
40 |
41 |
42 | `)
43 | }
44 |
45 | return html
46 | }
47 |
48 | async render () {
49 | const files = await this.getComponentDocs()
50 |
51 | const toc = this.renderTableOfContents(files)
52 | const content = this.renderFiles(files)
53 |
54 | return this.html`
55 |
56 |
57 |
58 |
59 | ${Tonic.unsafeRawString(head({
60 | title: 'Tonic',
61 | description: 'A component framework',
62 | siteName: 'Tonic Framework',
63 | image: 'https://tonicframework.dev/images/tonic_preview.png',
64 | url: 'https://tonicframework.dev'
65 | }))}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
82 |
83 |
84 |
85 | ${logo()}
86 |
87 |
88 | ${content}
89 |
90 |
91 |
92 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | `
109 | }
110 | }
111 |
112 | export default ExamplePage
113 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Tonic from 'tonic-ssr'
2 | import head from './mixins/head.js'
3 | import logo from './mixins/logo.js'
4 |
5 | class MainPage extends Tonic {
6 | async render () {
7 | return this.html`
8 |
9 |
10 |
11 |
12 | ${Tonic.unsafeRawString(head({
13 | title: 'Tonic',
14 | description: 'A low-profile component framework',
15 | siteName: 'Tonic Framework',
16 | image: 'https://tonicframework.dev/images/tonic_preview.png',
17 | url: 'https://tonicframework.dev'
18 | }))}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
65 |
66 |
75 |
76 |
77 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 | `
40 | }
41 |
42 | export default metaData
43 |
--------------------------------------------------------------------------------
/src/pages/mixins/logo.js:
--------------------------------------------------------------------------------
1 | import Tonic from 'tonic-ssr'
2 |
3 | export default () => {
4 | return Tonic.prototype.html`
5 |
34 | `
35 | }
36 |
--------------------------------------------------------------------------------
/src/styles/examples.css:
--------------------------------------------------------------------------------
1 | .example {
2 | position: relative;
3 | width: 100%;
4 | padding-right: 25%;
5 | min-height: 82px;
6 | max-height: 512px;
7 | display: grid;
8 | gap: 10px;
9 | }
10 |
11 | .example tonic-form {
12 | align-items: flex-end;
13 | }
14 |
15 | .example tonic-button {
16 | margin: 20px 20px 0px 0;
17 | }
18 |
19 | .example.full-width {
20 | padding-right: 0;
21 | }
22 |
23 | .example .header {
24 | font: 13px/16px var(--tonic-subheader);
25 | font-weight: 400;
26 | text-transform: uppercase;
27 | letter-spacing: 1px;
28 | padding: 12px 10px 10px 10px;
29 | background-color: var(--tonic-background);
30 | border-bottom: 1px solid var(--tonic-border);
31 | }
32 |
33 | .example .content {
34 | max-width: 450px;
35 | padding: 15px;
36 | }
37 |
38 | .example.buttons {
39 | grid-template-columns: 1fr 1fr;
40 | }
41 |
42 | tonic-button.danger {
43 | --tonic-button-text: rgba(255, 255, 255, 1);
44 | --tonic-button-background: rgba(240, 100, 83, 1);
45 | --tonic-button-background-hover: rgba(255, 80, 55, 1);
46 | --tonic-button-background-focus: rgba(220, 80, 60, 1);
47 | }
48 |
49 | tonic-button.outline {
50 | --tonic-button-text: var(--tonic-primary);
51 | --tonic-button-background: transparent;
52 | --tonic-button-background-hover: var(--tonic-primary);
53 | --tonic-button-background-focus: transparent;
54 |
55 | tonic-button.outline button {
56 | border-color: var(--tonic-primary);
57 | border-width: 1px !important;
58 |
59 | tonic-button.outline button:focus {
60 | border-width: 2px !important;
61 | }
62 |
63 | tonic-button.outline button:focus:hover {
64 | --tonic-button-text: rgba(255, 255, 255, 1);
65 | }
66 |
67 | @media (max-width: 850px) {
68 | div.example {
69 | padding-right: 0;
70 | }
71 | div.example ~ table {
72 | display: table;
73 | }
74 | }
75 |
76 | table {
77 | width: 100%;
78 | text-align: left;
79 | border: 1px solid blue;
80 | margin-bottom: 15px;
81 | border-collapse: collapse;
82 | border: 1px solid var(--tonic-border);
83 | }
84 |
85 | @media (max-width: 850px) {
86 | main#examples table,
87 | main#examples h2,
88 | main#examples h3,
89 | main#examples h4,
90 | main#examples pre {
91 | display: none;
92 | width: 200px;
93 | max-width: 100%;
94 | overflow: auto;
95 | }
96 | }
97 |
98 | th {
99 | font: 13px/16px var(--tonic-subheader);
100 | font-weight: 400;
101 | text-transform: uppercase;
102 | letter-spacing: 1px;
103 | padding: 12px 10px;
104 | background-color: var(--tonic-background);
105 | border-bottom: 1px solid var(--tonic-border);
106 | border-right: 1px solid var(--tonic-border);
107 | }
108 |
109 | td {
110 | line-height: 22px;
111 | padding: 15px;
112 | border-bottom: 1px solid var(--tonic-border);
113 | }
114 |
115 | td:not(:first-of-type) {
116 | border-left: 1px solid var(--tonic-border);
117 | }
118 |
119 | tr:last-of-type td {
120 | border-bottom: 0;
121 | }
122 |
123 | td:last-of-type {
124 | border-right: 0;
125 | }
126 |
127 | /* Component Styles */
128 |
129 | tonic-accordion {
130 | width: 100%;
131 | margin-bottom: 20px;
132 | }
133 |
134 | tonic-accordion .tonic--accordion-panel {
135 | line-height: 1.4em;
136 | }
137 |
138 | tonic-badge {
139 | display: inline-block;
140 | }
141 |
142 | tonic-button#tonic-button-example-4 {
143 | width: 30px;
144 | height: 30px;
145 | }
146 |
147 | .tonic--dialog,
148 | .tonic--dialog--overlay {
149 | position: fixed !important;
150 | }
151 |
152 | show-random {
153 | display: grid;
154 | grid-template-rows: 44px 1fr 80px;
155 | grid-template-columns: 1fr;
156 | }
157 |
158 | show-random header {
159 | line-height: 44px;
160 | text-align: center;
161 | color: var(--tonic-medium);
162 | display: block;
163 | }
164 |
165 | show-random main {
166 | padding: 30px;
167 | position: relative;
168 | text-align: center;
169 | }
170 |
171 | show-random footer {
172 | text-align: center;
173 | background-color: var(--tonic-window);
174 | padding: 16px;
175 | text-align: center;
176 | width: 100%;
177 | display: block;
178 | }
179 |
180 | #tonic-input-state {
181 | display: inline-block;
182 | padding-top: 15px;
183 | }
184 |
185 | tonic-form {
186 | display: grid;
187 | grid-template-columns: 1fr 1fr;
188 | gap: 20px;
189 | }
190 |
191 | tonic-loader {
192 | border: 1px solid var(--tonic-info);
193 | width: 75%;
194 | position: relative;
195 | }
196 |
197 | .loader-content {
198 | position: relative;
199 | height: 100px;
200 | width: 100px;
201 | }
202 |
203 | read-wikipedia h3 {
204 | margin: 0;
205 | }
206 |
207 | read-wikipedia header {
208 | padding: 20px;
209 | position: absolute;
210 | top: 0;
211 | left: 0;
212 | right: 0;
213 | height: 90px;
214 | }
215 |
216 | read-wikipedia main {
217 | padding: 40px 20px;
218 | position: absolute;
219 | text-align: left;
220 | top: 90px;
221 | left: 0;
222 | right: 0;
223 | bottom: 70px;
224 | overflow: scroll;
225 | }
226 |
227 | read-wikipedia footer {
228 | position: absolute;
229 | left: 0;
230 | right: 0;
231 | bottom: 0;
232 | height: 70px;
233 | padding: 10px;
234 | text-align: center;
235 | border-top: 1px solid var(--tonic-border);
236 | }
237 |
238 | tonic-router {
239 | margin-top: 20px;
240 | padding-top: 20px;
241 | }
242 |
243 | tonic-router.tonic--show {
244 | display: block;
245 | margin: 20px 0 6px;
246 | padding-top: 20px;
247 | border-top: 1px solid var(--tonic-border);
248 | }
249 |
250 | #tonic-router-select {
251 | display: inline-block;
252 | }
253 |
254 | tonic-tabs a.tonic--tab {
255 | border-bottom: 1px solid transparent;
256 | margin: 0;
257 | margin-right: 10px;
258 | display: inline-block;
259 | }
260 |
261 | tonic-tabs a.tonic--tab:focus {
262 | border-bottom: 1px solid var(--tonic-accent);
263 | }
264 |
265 | tonic-tabs a.tonic--tab[aria-selected="true"] {
266 | border-bottom: 1px solid var(--tonic-accent);
267 | }
268 |
269 | tonic-tab-panel {
270 | flex-basis: 100%;
271 | padding: 15px 0;
272 | }
273 |
274 | #tonic-tooltip-example {
275 | width: fit-content;
276 | }
277 |
278 | tonic-tooltip .tonic--tooltip {
279 | padding: 40px 10px;
280 | }
281 |
282 | body main div.example .content.windowed-example {
283 | max-width: initial;
284 | padding: 0;
285 | position: relative;
286 | height: 330px;
287 | }
288 |
289 | example-windowed {
290 | height: 300px;
291 | }
292 |
293 | example-windowed .th {
294 | background: var(--tonic-border);
295 | }
296 |
297 | example-windowed .th,
298 | example-windowed .tr {
299 | height: 30px;
300 | display: flex;
301 | overflow: hidden;
302 | border-bottom: 1px solid var(--tonic-border);
303 | }
304 |
305 | example-windowed .tr:hover {
306 | background: var(--tonic-accent);
307 | color: #fff;
308 | cursor: default;
309 | }
310 |
311 | example-windowed .td {
312 | white-space: nowrap;
313 | display: inline-block;
314 | text-overflow: ellipsis;
315 | overflow: hidden;
316 | width: 100px;
317 | font-family: var(--tonic-monospace);
318 | font-size: 14px;
319 | height: 30px;
320 | line-height: 30px;
321 | flex-basis: 33.3%;
322 | padding: 0 4px;
323 | }
324 |
325 | #click-to-load {
326 | position: absolute;
327 | display: flex;
328 | left: 0;
329 | right: 0;
330 | background: var(--tonic-window);
331 | z-index: 1;
332 | top: 0;
333 | bottom: 0;
334 | cursor: pointer;
335 | opacity: 1;
336 | transition: all 1.5s;
337 | }
338 |
339 | #click-to-load span {
340 | margin: auto;
341 | }
342 |
343 | #click-to-load.hidden {
344 | opacity: 0;
345 | z-index: -1;
346 | }
347 |
--------------------------------------------------------------------------------
/src/styles/highlight.js.css:
--------------------------------------------------------------------------------
1 | .hljs {
2 | display: block;
3 | overflow-x: auto;
4 | padding: 0.5em;
5 | background: #f0f0f0;
6 | }
7 | .hljs,
8 | .hljs-subst {
9 | color: var(--tonic-medium);
10 | }
11 | .hljs-comment {
12 | color: #aaa;
13 | }
14 | .hljs-keyword,
15 | .hljs-attribute,
16 | .hljs-selector-tag,
17 | .hljs-meta-keyword,
18 | .hljs-doctag,
19 | .hljs-name {
20 | font-weight: bold;
21 | }
22 | .hljs-type,
23 | .hljs-string,
24 | .hljs-number,
25 | .hljs-selector-id,
26 | .hljs-selector-class,
27 | .hljs-quote,
28 | .hljs-template-tag,
29 | .hljs-deletion {
30 | color: #38b9ff;
31 | }
32 | .hljs-title,
33 | .hljs-section {
34 | color: #38b9ff;
35 | font-weight: bold;
36 | }
37 | .hljs-regexp,
38 | .hljs-symbol,
39 | .hljs-variable,
40 | .hljs-template-variable,
41 | .hljs-link,
42 | .hljs-selector-attr,
43 | .hljs-selector-pseudo {
44 | color: #38b9ff;
45 | }
46 | .hljs-literal {
47 | color: #38b9ff;
48 | }
49 | .hljs-built_in,
50 | .hljs-bullet,
51 | .hljs-code,
52 | .hljs-addition {
53 | color: #38b9ff;
54 | }
55 | .hljs-meta {
56 | color: #1f7199;
57 | }
58 | .hljs-meta-string {
59 | color: #bc6060;
60 | }
61 | .hljs-emphasis {
62 | font-style: italic;
63 | }
64 | .hljs-strong {
65 | font-weight: bold;
66 | }
67 |
--------------------------------------------------------------------------------
/src/styles/index.css:
--------------------------------------------------------------------------------
1 | @import path("./theme.css");
2 | @import path("./highlight.js.css");
3 | @import path("./examples.css");
4 |
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
9 | ::-moz-placeholder {
10 | color: var(--tonic-medium);
11 | }
12 |
13 | :-ms-input-placeholder {
14 | color: var(--tonic-medium);
15 | }
16 |
17 | ::placeholder {
18 | color: var(--tonic-medium);
19 | }
20 |
21 | ::-webkit-scrollbar-corner,
22 | ::-webkit-scrollbar {
23 | background: transparent;
24 | }
25 |
26 | ::-webkit-scrollbar {
27 | width: 10px;
28 | height: 10px;
29 | }
30 |
31 | ::-webkit-scrollbar-thumb {
32 | background: var(--tonic-border);
33 | }
34 |
35 | html {
36 | scroll-behavior: smooth !important;
37 | }
38 |
39 | body {
40 | margin: 0;
41 | padding: 0;
42 | font-family: var(--tonic-body);
43 | display: grid;
44 | grid-template-columns: 25% 75%;
45 | line-height: 18px;
46 | font-size: 1em;
47 | background: var(--tonic-window);
48 | color: var(--tonic-primary);
49 | }
50 |
51 | h1, h2, h3 {
52 | font-family: var(--tonic-header);
53 | margin-top: 100px;
54 | line-height: 40px;
55 | }
56 |
57 | h1 {
58 | font-size: 32px;
59 | }
60 |
61 | h2 {
62 | margin-top: 40px;
63 | line-height: 32px;
64 | }
65 |
66 | h3 {
67 | margin-top: 40px;
68 | font-size: 22px;
69 | }
70 |
71 | hr {
72 | height: 2px;
73 | margin: 20px 0px;
74 | background-color: transparent;
75 | border: 0;
76 | }
77 |
78 | blockquote {
79 | border-left: 2px solid;
80 | padding-left: 10px;
81 | margin-left: 10px;
82 | color: var(--tonic-info);
83 | }
84 |
85 | code, pre {
86 | font-family: var(--tonic-monospace);
87 | background-color: var(--tonic-background);
88 | font-size: 14px;
89 | border: 1px solid var(--tonic-border);
90 | }
91 |
92 | pre code {
93 | border: none;
94 | }
95 |
96 | code {
97 | padding: 2px;
98 | }
99 |
100 | pre {
101 | padding: 14px;
102 | overflow: auto;
103 | }
104 |
105 | aside {
106 | padding: 50px 5% 0 5%;
107 | grid-area: 1 / 1;
108 | }
109 |
110 | table {
111 | display: block;
112 | overflow: auto;
113 | max-width: 100%;
114 | border-collapse: collapse;
115 | border: 1px solid var(--tonic-border);
116 | }
117 |
118 | p {
119 | line-height: 26px;
120 | }
121 |
122 | a, a:visted, a:active {
123 | text-decoration: none;
124 | }
125 |
126 | a {
127 | color: var(--tonic-accent);
128 | text-decoration: none;
129 | padding-bottom: 1px;
130 | border-bottom: 1px solid transparent;
131 | }
132 |
133 | a:not(.logo):hover {
134 | padding-bottom: 1px;
135 | border-bottom: 1px solid;
136 | }
137 |
138 | #dark {
139 | position: absolute;
140 | margin-top: -6px;
141 | }
142 |
143 | @media (max-width: 768px) {
144 | #dark {
145 | display: none;
146 | }
147 | }
148 |
149 | div.title {
150 | margin-bottom: 30px;
151 | }
152 |
153 | div.title h1 {
154 | margin-top: 30%;
155 | font-size: 60px;
156 | line-height: 60px;
157 | }
158 |
159 | aside {
160 | background-color: var(--tonic-background);
161 | border-right: 1px solid var(--tonic-border);
162 | font-size: 14px;
163 | text-transform: capitalize;
164 | }
165 |
166 | @media (max-width: 768px) {
167 | aside {
168 | display: none;
169 | }
170 | }
171 |
172 | toc-nav {
173 | display: grid;
174 | letter-spacing: 1.1;
175 | }
176 |
177 | toc-nav a {
178 | display: inline-block;
179 | margin: 10px 25%;
180 | }
181 |
182 | a {
183 | color: var(--tonic-dark);
184 | width: fit-content;
185 | }
186 |
187 | aside .logo {
188 | display: block;
189 | width: 50%;
190 | margin: auto;
191 | }
192 |
193 | aside .logo:hover {
194 | border-color: transparent;
195 | }
196 |
197 | aside svg {
198 | width: 100%;
199 | margin: 30px 0;
200 | }
201 |
202 | main#main,
203 | main#examples {
204 | grid-area: 1 / 2;
205 | padding: 50px 15% 0 15%;
206 | padding-bottom: 100px;
207 | }
208 |
209 | main .logo {
210 | display: none;
211 | }
212 |
213 | main .logo svg {
214 | width: 50%;
215 | color: black;
216 | margin-top: 50px;
217 | }
218 |
219 | @media (max-width: 768px) {
220 | main .logo {
221 | display: block;
222 | }
223 | }
224 |
225 | .checklists {
226 | display: grid;
227 | grid-template-columns: 1fr 1fr;
228 | gap: 20px;
229 | margin-top: 30px;
230 | margin-bottom: 30%;
231 | }
232 |
233 | .checklists a {
234 | border-bottom: 1px dotted;
235 | }
236 |
237 | .checklists h1,
238 | .checklists h2 {
239 | margin-top: 32;
240 | }
241 |
242 | .checklists ul {
243 | list-style: none;
244 | }
245 |
246 | .checklists ul.checklist {
247 | font: 16px var(--tonic-body);
248 | padding: 0;
249 | flex-basis: 50%;
250 | }
251 |
252 | .checklists ul.checklist li {
253 | line-height: 28px;
254 | }
255 |
256 | .checklists ul.checklist li a:not(.logo) {
257 | margin: 0;
258 | }
259 |
260 | .checklists ul.checklist li:before {
261 | content: '';
262 | width: 9px;
263 | height: 4px;
264 | display: inline-block;
265 | vertical-align: super;
266 | margin-right: 9px;
267 | border-bottom: 2px solid var(--tonic-primary);
268 | border-left: 2px solid var(--tonic-primary);
269 | transform: rotate(-45deg);
270 | position: relative;
271 | top: 2px;
272 | }
273 |
274 | @media (max-width: 768px) {
275 | body {
276 | display: block;
277 | }
278 |
279 | div.title h1 {
280 | font-size: 48px;
281 | line-height: 48px;
282 | }
283 |
284 | .checklists main {
285 | padding: 50px 10%;
286 | }
287 |
288 | .checklists {
289 | grid-template-columns: 1fr;
290 | }
291 |
292 | main#examples,
293 | main#main {
294 | padding: 50px 10% 0 10%;
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/src/styles/theme.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Body';
3 | src: url('fonts/Inter-Light.woff2');
4 | }
5 |
6 | @font-face {
7 | font-family: 'Header';
8 | src: url('fonts/Inter-Black.woff2');
9 | }
10 |
11 | @font-face {
12 | font-family: 'FiraMono-Regular';
13 | src: url('fonts/FiraMono-Regular.woff2');
14 | }
15 |
16 | body {
17 | --tonic-body: 'Body', sans-serif;
18 | --tonic-header: 'Header', sans-serif;
19 | --tonic-subheader: 'Header', sans-serif;
20 | --tonic-monospace: 'FiraMono-Regular', monospace;
21 | }
22 |
23 | body, *[theme="light"] {
24 | --tonic-background: rgba(244, 247, 249, 1);
25 | --tonic-window: rgba(255, 255, 255, 1);
26 | --tonic-accent: rgba(56, 185, 255, 1);
27 | --tonic-primary: rgba(54, 57, 61, 1);
28 | --tonic-secondary: rgba(232, 232, 228, 1);
29 | --tonic-light: rgba(153, 157, 160, 1);
30 | --tonic-medium: rgba(153, 157, 160, 1);
31 | --tonic-dark: rgba(90, 101, 113, 1);
32 | --tonic-shadow: rgba(150, 150, 150, 0.25);
33 | --tonic-disabled: rgba(152, 161, 175, 1);
34 | --tonic-button-text: rgba(54, 57, 61, 1);
35 | --tonic-button-shadow: rgba(0, 0, 0, 33%);
36 | --tonic-button-background: rgba(245, 245, 245, 1);
37 | --tonic-button-background-hover: rgba(230, 230, 230, 1);
38 | --tonic-button-background-focus: rgba(237, 237, 237, 1);
39 | --tonic-input-text: rgba(54, 57, 61, 1);
40 | --tonic-input-text-hover: rgba(228, 228, 228, 1);
41 | --tonic-input-border: rgba(201, 201, 201, 1);
42 | --tonic-input-border-hover: rgba(54, 57, 61, 1);
43 | --tonic-input-background-focus: rgba(238, 238, 238, 1);
44 | --tonic-border: rgba(231, 237, 241, 1);
45 | --tonic-error: rgba(240, 102, 83, 1);
46 | --tonic-notification: rgba(240, 102, 83, 1);
47 | --tonic-danger: rgba(240, 102, 83, 1);
48 | --tonic-success: rgba(133, 178, 116, 1);
49 | --tonic-warning: rgba(249, 169, 103, 1);
50 | --tonic-info: rgba(153, 157, 160, 1);
51 | --tonic-overlay: rgba(255, 255, 255, 0.4);
52 | }
53 |
54 | *[theme="dark"] {
55 | --tonic-background: rgba(40, 40, 40, 1);
56 | --tonic-window: rgba(49, 49, 49, 1);
57 | --tonic-accent: rgba(56, 185, 255, 1);
58 | --tonic-primary: rgba(255, 255, 255, 1);
59 | --tonic-secondary: rgba(195, 195, 195, 1);
60 | --tonic-medium: rgba(153, 157, 160, 1);
61 | --tonic-disabled: rgba(170, 170, 170, 1);
62 | --tonic-dark: rgba(128, 129, 134, 1);
63 | --tonic-shadow: rgba(0, 0, 0, 0.3);
64 | --tonic-button-text: rgba(255, 255, 255, 1);
65 | --tonic-button-shadow: rgba(0, 0, 0, 1);
66 | --tonic-button-background: rgba(74, 74, 74, 1);
67 | --tonic-button-background-hover: rgba(94, 94, 94, 1);
68 | --tonic-button-background-focus: rgba(84, 84, 84, 1);
69 | --tonic-input-text: rgba(255, 255, 255, 1);
70 | --tonic-input-text-hover: rgba(255, 255, 255, 1);
71 | --tonic-input-background-focus: rgba(30, 30, 30, 1);
72 | --tonic-input-border: rgba(80, 80, 80, 1);
73 | --tonic-input-border-hover: rgba(105, 105, 105, 1);
74 | --tonic-border: rgba(70, 72, 76, 1);
75 | --tonic-error: rgba(240, 102, 83, 1);
76 | --tonic-notification: rgba(240, 102, 83, 1);
77 | --tonic-caution: rgba(240, 102, 83, 1);
78 | --tonic-success: rgba(133, 178, 116, 1);
79 | --tonic-warn: rgba(249, 169, 103, 1);
80 | --tonic-overlay: rgba(0, 0, 0, 0.4);
81 | }
82 |
--------------------------------------------------------------------------------
/website.json:
--------------------------------------------------------------------------------
1 | {
2 | "localhost": {
3 | "provider": "aws",
4 | "spa": "",
5 | "lastDeployed": null,
6 | "properties": {
7 | "relativeFolder": {
8 | "value": [
9 | ".."
10 | ]
11 | },
12 | "cache": {
13 | "value": 7800
14 | },
15 | "gzip": {
16 | "value": true
17 | },
18 | "customLocalServer": {
19 | "enabled": true,
20 | "value": "node ./bin/loader.js"
21 | },
22 | "apiRoute": {
23 | "value": ""
24 | },
25 | "apiRouteDestination": {
26 | "value": null
27 | },
28 | "predeploy": {
29 | "directory": [
30 | "..",
31 | "build"
32 | ],
33 | "command": ""
34 | },
35 | "bucketName": {
36 | "value": ""
37 | },
38 | "bucketRegion": {
39 | "value": ""
40 | },
41 | "domain": {
42 | "value": ""
43 | }
44 | }
45 | },
46 | "137418857087": {
47 | "provider": "aws",
48 | "spa": "",
49 | "lastDeployed": null,
50 | "properties": {
51 | "relativeFolder": {
52 | "value": [
53 | ".."
54 | ],
55 | "deployed": false
56 | },
57 | "cache": {
58 | "value": 7800,
59 | "deployed": false
60 | },
61 | "gzip": {
62 | "value": true,
63 | "deployed": false
64 | },
65 | "customLocalServer": {
66 | "enabled": false,
67 | "value": "",
68 | "deployed": false
69 | },
70 | "apiRoute": {
71 | "value": "",
72 | "deployed": false
73 | },
74 | "apiRouteDestination": {
75 | "value": null,
76 | "deployed": false
77 | },
78 | "predeploy": {
79 | "value": "",
80 | "deployed": false
81 | },
82 | "bucketName": {
83 | "value": "",
84 | "deployed": false
85 | },
86 | "bucketRegion": {
87 | "value": "",
88 | "deployed": false
89 | },
90 | "domain": {
91 | "value": "",
92 | "deployed": false
93 | }
94 | },
95 | "created": false
96 | },
97 | "572141287366": {
98 | "provider": "aws",
99 | "spa": "",
100 | "lastDeployed": null,
101 | "properties": {
102 | "relativeFolder": {
103 | "value": [
104 | ".."
105 | ],
106 | "deployed": false
107 | },
108 | "cache": {
109 | "value": 7800,
110 | "deployed": false
111 | },
112 | "gzip": {
113 | "value": true,
114 | "deployed": false
115 | },
116 | "customLocalServer": {
117 | "enabled": false,
118 | "value": "",
119 | "deployed": false
120 | },
121 | "apiRoute": {
122 | "value": "",
123 | "deployed": false
124 | },
125 | "apiRouteDestination": {
126 | "value": null,
127 | "deployed": false
128 | },
129 | "predeploy": {
130 | "value": "",
131 | "deployed": false
132 | },
133 | "bucketName": {
134 | "value": "",
135 | "deployed": false
136 | },
137 | "bucketRegion": {
138 | "value": "",
139 | "deployed": false
140 | },
141 | "domain": {
142 | "value": "",
143 | "deployed": false
144 | }
145 | },
146 | "created": false
147 | },
148 | "633075417262": {
149 | "provider": "aws",
150 | "spa": "",
151 | "lastDeployed": "2023-01-23T12:56:38.770Z",
152 | "properties": {
153 | "relativeFolder": {
154 | "value": [
155 | ".."
156 | ]
157 | },
158 | "cache": {
159 | "value": 7800
160 | },
161 | "gzip": {
162 | "value": true
163 | },
164 | "customLocalServer": {
165 | "enabled": false,
166 | "value": "",
167 | "deployed": false
168 | },
169 | "apiRoute": {
170 | "value": "",
171 | "deployed": false
172 | },
173 | "apiRouteDestination": {
174 | "value": null,
175 | "deployed": false
176 | },
177 | "predeploy": {
178 | "command": "npm run build",
179 | "directory": [
180 | "..",
181 | "build"
182 | ],
183 | "deployed": false
184 | },
185 | "bucketName": {
186 | "value": "static.635e4eafbd5bf8.tonic-docs"
187 | },
188 | "bucketRegion": {
189 | "value": "us-east-1"
190 | },
191 | "domain": {
192 | "value": "tonicframework.dev",
193 | "hostedZoneId": "/hostedzone/Z102472634HUYI6EEHDUA"
194 | }
195 | },
196 | "createResponse": {
197 | "created": true,
198 | "bucketRegion": "us-east-1",
199 | "ctime": 1646340713417,
200 | "cloudfrontId": "E3T2L7U5MO2H60"
201 | },
202 | "cloudfrontId": "E3T2L7U5MO2H60",
203 | "cloudfrontDomain": "d3o710p4woajfw.cloudfront.net"
204 | },
205 | "104940042594": {
206 | "provider": "aws",
207 | "spa": "",
208 | "lastDeployed": null,
209 | "properties": {
210 | "relativeFolder": {
211 | "value": [
212 | ".."
213 | ],
214 | "deployed": false
215 | },
216 | "cache": {
217 | "value": 7800,
218 | "deployed": false
219 | },
220 | "gzip": {
221 | "value": true,
222 | "deployed": false
223 | },
224 | "customLocalServer": {
225 | "enabled": false,
226 | "value": "",
227 | "deployed": false
228 | },
229 | "apiRoute": {
230 | "value": "",
231 | "deployed": false
232 | },
233 | "apiRouteDestination": {
234 | "value": null,
235 | "deployed": false
236 | },
237 | "predeploy": {
238 | "directory": "",
239 | "command": "",
240 | "deployed": false
241 | },
242 | "bucketName": {
243 | "value": "",
244 | "deployed": false
245 | },
246 | "bucketRegion": {
247 | "value": "",
248 | "deployed": false
249 | },
250 | "domain": {
251 | "value": "",
252 | "deployed": false
253 | }
254 | },
255 | "created": false
256 | },
257 | "768180738139": {
258 | "provider": "aws",
259 | "spa": "",
260 | "lastDeployed": "2022-03-04T18:17:25.310Z",
261 | "properties": {
262 | "relativeFolder": {
263 | "value": [
264 | ".."
265 | ]
266 | },
267 | "cache": {
268 | "value": 7800
269 | },
270 | "gzip": {
271 | "value": true
272 | },
273 | "customLocalServer": {
274 | "enabled": false,
275 | "value": "",
276 | "deployed": false
277 | },
278 | "apiRoute": {
279 | "value": "",
280 | "deployed": false
281 | },
282 | "apiRouteDestination": {
283 | "value": null,
284 | "deployed": false
285 | },
286 | "predeploy": {
287 | "command": "npm run build",
288 | "directory": [
289 | "..",
290 | "build"
291 | ],
292 | "deployed": false
293 | },
294 | "bucketName": {
295 | "value": "static.da6f009d06dc2.tonic-docs"
296 | },
297 | "bucketRegion": {
298 | "value": "us-east-1"
299 | },
300 | "domain": {
301 | "value": "",
302 | "deployed": false
303 | }
304 | },
305 | "createResponse": {
306 | "created": true,
307 | "bucketRegion": "us-east-1",
308 | "ctime": 1646417792736,
309 | "cloudfrontId": "EPV6TQL159GW6",
310 | "cloudfrontDomain": "d28pq0m9139e1q.cloudfront.net"
311 | },
312 | "cloudfrontId": "EPV6TQL159GW6",
313 | "cloudfrontDomain": "d28pq0m9139e1q.cloudfront.net"
314 | },
315 | "305103105464": {
316 | "provider": "aws",
317 | "spa": "",
318 | "lastDeployed": null,
319 | "properties": {
320 | "relativeFolder": {
321 | "value": [
322 | ".."
323 | ]
324 | },
325 | "cache": {
326 | "value": 7800
327 | },
328 | "gzip": {
329 | "value": true
330 | },
331 | "customLocalServer": {
332 | "enabled": true,
333 | "value": "node ./bin/loader.js"
334 | },
335 | "apiRoute": {
336 | "value": ""
337 | },
338 | "apiRouteDestination": {
339 | "value": null
340 | },
341 | "predeploy": {
342 | "directory": [
343 | "..",
344 | "build"
345 | ],
346 | "command": ""
347 | },
348 | "bucketName": {
349 | "value": ""
350 | },
351 | "bucketRegion": {
352 | "value": ""
353 | },
354 | "domain": {
355 | "value": ""
356 | }
357 | },
358 | "created": false
359 | }
360 | }
361 |
--------------------------------------------------------------------------------