@e280/shiny 0.1.0-10 → 0.1.0-12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +13 -8
  2. package/package.json +3 -3
  3. package/s/components/button/component.ts +30 -0
  4. package/s/components/button/showcase.ts +119 -0
  5. package/s/components/button/style.css.ts +63 -0
  6. package/s/components/copy/component.ts +3 -1
  7. package/s/components/copy/showcase.ts +51 -0
  8. package/s/components/drawer/component.ts +30 -32
  9. package/s/components/drawer/showcase.ts +93 -0
  10. package/s/components/drawer/style.css.ts +9 -4
  11. package/s/components/raw-components.ts +2 -0
  12. package/s/components/tabs/component.ts +13 -2
  13. package/s/components/tabs/control.ts +3 -3
  14. package/s/components/tabs/showcase.ts +73 -0
  15. package/s/components/tabs/style.css.ts +38 -1
  16. package/s/demo/demo.bundle.ts +14 -199
  17. package/s/demo/demo.css +1 -0
  18. package/s/demo/lipsum.ts +6 -0
  19. package/s/demo/utils/lipsum.ts +1 -1
  20. package/s/demo/views/demonstration/style.css.ts +8 -0
  21. package/s/demo/views/demonstration/view.ts +10 -6
  22. package/s/demo/views/exhibit/style.css.ts +82 -0
  23. package/s/demo/views/exhibit/view.ts +59 -0
  24. package/s/demo/views/showcase/style.css.ts +50 -0
  25. package/s/demo/views/showcase/view.ts +54 -0
  26. package/s/demo/viewsets.ts +12 -0
  27. package/s/index.html.ts +4 -7
  28. package/s/themes/aura.css.ts +52 -14
  29. package/s/themes/infra/css-vars.ts +19 -6
  30. package/s/themes/plain.css.ts +0 -1
  31. package/x/components/button/component.d.ts +6 -0
  32. package/x/components/button/component.js +25 -0
  33. package/x/components/button/component.js.map +1 -0
  34. package/x/components/button/showcase.d.ts +1 -0
  35. package/x/components/button/showcase.js +116 -0
  36. package/x/components/button/showcase.js.map +1 -0
  37. package/x/components/button/style.css.d.ts +2 -0
  38. package/x/components/button/style.css.js +62 -0
  39. package/x/components/button/style.css.js.map +1 -0
  40. package/x/components/copy/component.d.ts +2 -2
  41. package/x/components/copy/component.js +3 -1
  42. package/x/components/copy/component.js.map +1 -1
  43. package/x/components/copy/showcase.d.ts +1 -0
  44. package/x/components/copy/showcase.js +48 -0
  45. package/x/components/copy/showcase.js.map +1 -0
  46. package/x/components/drawer/component.d.ts +3 -3
  47. package/x/components/drawer/component.js +28 -31
  48. package/x/components/drawer/component.js.map +1 -1
  49. package/x/components/drawer/showcase.d.ts +1 -0
  50. package/x/components/drawer/showcase.js +87 -0
  51. package/x/components/drawer/showcase.js.map +1 -0
  52. package/x/components/drawer/style.css.js +9 -4
  53. package/x/components/drawer/style.css.js.map +1 -1
  54. package/x/components/example/component.d.ts +1 -1
  55. package/x/components/raw-components.d.ts +2 -0
  56. package/x/components/raw-components.js +2 -0
  57. package/x/components/raw-components.js.map +1 -1
  58. package/x/components/tabs/component.d.ts +1 -1
  59. package/x/components/tabs/component.js +13 -2
  60. package/x/components/tabs/component.js.map +1 -1
  61. package/x/components/tabs/control.d.ts +1 -1
  62. package/x/components/tabs/control.js +3 -3
  63. package/x/components/tabs/control.js.map +1 -1
  64. package/x/components/tabs/showcase.d.ts +1 -0
  65. package/x/components/tabs/showcase.js +69 -0
  66. package/x/components/tabs/showcase.js.map +1 -0
  67. package/x/components/tabs/style.css.js +38 -1
  68. package/x/components/tabs/style.css.js.map +1 -1
  69. package/x/demo/demo.bundle.js +14 -193
  70. package/x/demo/demo.bundle.js.map +1 -1
  71. package/x/demo/demo.bundle.min.js +497 -311
  72. package/x/demo/demo.bundle.min.js.map +4 -4
  73. package/x/demo/demo.css +1 -0
  74. package/x/demo/lipsum.d.ts +2 -0
  75. package/x/demo/lipsum.js +4 -0
  76. package/x/demo/lipsum.js.map +1 -0
  77. package/x/demo/utils/lipsum.js +1 -1
  78. package/x/demo/views/demonstration/style.css.js +8 -0
  79. package/x/demo/views/demonstration/style.css.js.map +1 -1
  80. package/x/demo/views/demonstration/view.js +8 -6
  81. package/x/demo/views/demonstration/view.js.map +1 -1
  82. package/x/demo/views/exhibit/style.css.d.ts +2 -0
  83. package/x/demo/views/exhibit/style.css.js +81 -0
  84. package/x/demo/views/exhibit/style.css.js.map +1 -0
  85. package/x/demo/views/exhibit/view.d.ts +29 -0
  86. package/x/demo/views/exhibit/view.js +38 -0
  87. package/x/demo/views/exhibit/view.js.map +1 -0
  88. package/x/demo/views/showcase/style.css.d.ts +2 -0
  89. package/x/demo/views/showcase/style.css.js +49 -0
  90. package/x/demo/views/showcase/style.css.js.map +1 -0
  91. package/x/demo/views/showcase/view.d.ts +7 -0
  92. package/x/demo/views/showcase/view.js +40 -0
  93. package/x/demo/views/showcase/view.js.map +1 -0
  94. package/x/demo/{aura-views.d.ts → viewsets.d.ts} +5 -2
  95. package/x/demo/viewsets.js +9 -0
  96. package/x/demo/viewsets.js.map +1 -0
  97. package/x/index.html +5 -4
  98. package/x/index.html.js +4 -7
  99. package/x/index.html.js.map +1 -1
  100. package/x/install/aura.bundle.min.js +210 -65
  101. package/x/install/aura.bundle.min.js.map +4 -4
  102. package/x/install/plain.bundle.min.js +159 -53
  103. package/x/install/plain.bundle.min.js.map +4 -4
  104. package/x/shiny.d.ts +8 -3
  105. package/x/themes/aura.css.js +52 -14
  106. package/x/themes/aura.css.js.map +1 -1
  107. package/x/themes/infra/css-vars.d.ts +6 -2
  108. package/x/themes/infra/css-vars.js +7 -3
  109. package/x/themes/infra/css-vars.js.map +1 -1
  110. package/x/themes/plain.css.js +0 -1
  111. package/x/themes/plain.css.js.map +1 -1
  112. package/s/demo/aura-views.ts +0 -6
  113. package/x/demo/aura-views.js +0 -4
  114. package/x/demo/aura-views.js.map +0 -1
package/README.md CHANGED
@@ -5,8 +5,10 @@
5
5
  > *web ui components*
6
6
 
7
7
  - 💁 ***see all the components at https://shiny.e280.org/*** 👈
8
- - 👷 built with [🦝sly](https://github.com/e280/sly) and [🔥lit](https://lit.dev/)
9
8
  - 🎭 duality: all components are available as ***web components*** or ***sly views***
9
+ - 👷 built with [🦝sly](https://github.com/e280/sly) and [🔥lit](https://lit.dev/)
10
+ - 🎨 totally customizable, via theme presets, custom themes, css vars and parts
11
+ - 🧩 using [tabler icons](https://github.com/tabler/tabler-icons)
10
12
  - 🧑‍💻 a project by https://e280.org/
11
13
 
12
14
 
@@ -92,13 +94,16 @@
92
94
  ```html
93
95
  <style>
94
96
  html {
95
- --shiny-bg: #111;
96
- --shiny-alpha: #afa;
97
- --shiny-happy: #0fa;
98
- --shiny-sad: #74f;
99
- --shiny-angry: #f50;
100
- --shiny-lame: #8888;
101
- --shiny-inactive-opacity: 0.5;
97
+ --shiny-bg: #111;
98
+ --shiny-alpha: #def;
99
+ --shiny-lame: #8888;
100
+ --shiny-inactive-opacity: 0.5;
101
+ --shiny-angry: #f50;
102
+ --shiny-zesty: #cf0;
103
+ --shiny-happy: #0fa;
104
+ --shiny-calm: #0af;
105
+ --shiny-sad: #74f;
106
+ --shiny-quirky: #f49;
102
107
  }
103
108
  </style>
104
109
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@e280/shiny",
3
- "version": "0.1.0-10",
3
+ "version": "0.1.0-12",
4
4
  "description": "✨ web ui components",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  "s"
15
15
  ],
16
16
  "peerDependencies": {
17
- "@e280/sly": "^0.2.0-25",
17
+ "@e280/sly": "^0.2.0-26",
18
18
  "lit": "^3.3.1"
19
19
  },
20
20
  "dependencies": {
@@ -26,7 +26,7 @@
26
26
  "@e280/scute": "^0.1.0",
27
27
  "http-server": "^14.1.1",
28
28
  "npm-run-all": "^4.1.5",
29
- "typescript": "^5.9.2"
29
+ "typescript": "^5.9.3"
30
30
  },
31
31
  "scripts": {
32
32
  "build": "run-s _clean _ln _tsc _scute",
@@ -0,0 +1,30 @@
1
+
2
+ import {html} from "lit"
3
+ import {Content, view} from "@e280/sly"
4
+ import styleCss from "./style.css.js"
5
+ import {foundationCss} from "../foundation.css.js"
6
+ import {ShinyContext, ShinyElement} from "../framework.js"
7
+
8
+ export class ShinyButton extends (
9
+ view(use => (context: ShinyContext, content?: Content) => {
10
+ use.name("shiny-button")
11
+ use.styles(foundationCss, context.theme, styleCss)
12
+
13
+ const attrs = use.attrs.spec({
14
+ disabled: Boolean,
15
+ hidden: Boolean,
16
+ })
17
+
18
+ return html`
19
+ <button
20
+ part=button
21
+ ?disabled="${attrs.disabled}"
22
+ ?hidden="${attrs.hidden}">
23
+ <slot>${content}</slot>
24
+ </button>
25
+ `
26
+ })
27
+ .component(ShinyElement)
28
+ .props(el => [el.context] as const)
29
+ ) {}
30
+
@@ -0,0 +1,119 @@
1
+
2
+ import {css, html} from "lit"
3
+ import {Showcase} from "../../demo/views/showcase/view.js"
4
+
5
+ const cssSnippet = `
6
+ shiny-button {
7
+ --padding: 0.3em;
8
+ font-size: 1em;
9
+ color: currentColor;
10
+ background: transparent;
11
+ }
12
+ `
13
+
14
+ export const buttonShowcase = () => Showcase({
15
+ name: "button",
16
+ style: css`
17
+ .box {
18
+ > * { font-size: 1.5em; }
19
+ }
20
+ `,
21
+ exhibits: [
22
+ {
23
+ label: "basic",
24
+ explain: html`<p>clicky-clacky pressy button.</p>`,
25
+ snippets: [
26
+ {label: "html", code: `<shiny-button>button</shiny-button>`},
27
+ {label: "view", code: `ShinyButton("button")`},
28
+ {label: "css", code: cssSnippet},
29
+ ],
30
+ style: css``,
31
+ presentation: views => html`
32
+ ${views.ShinyButton.props().children("button").render()}
33
+ `,
34
+ },
35
+ {
36
+ label: "gradient",
37
+ explain: html`<p>added <code>gradient</code> attribute.</p>`,
38
+ snippets: [
39
+ {label: "html", code: `<shiny-button gradient>button</shiny-button>`},
40
+ {label: "view", code: `
41
+ ShinyButton
42
+ .props("button")
43
+ .attr("gradient")
44
+ .render()
45
+ `},
46
+ {label: "css", code: cssSnippet},
47
+ ],
48
+ style: css``,
49
+ presentation: views => html`
50
+ ${views.ShinyButton.props().attr("gradient").children("button").render()}
51
+ `,
52
+ },
53
+ {
54
+ label: "catalog",
55
+ explain: html`<p>clicky-clacky pressy buttons.</p>`,
56
+ snippets: [
57
+ {label: "html", code: `
58
+ <shiny-button calm gradient>calm</shiny-button>
59
+ <shiny-button angry gradient>angry</shiny-button>
60
+ <shiny-button happy gradient>happy</shiny-button>
61
+ <shiny-button zesty gradient>zesty</shiny-button>
62
+ <shiny-button sad gradient>sad</shiny-button>
63
+ <shiny-button quirky gradient>quirky</shiny-button>
64
+ <shiny-button plain gradient>plain</shiny-button>
65
+ `},
66
+ {label: "view", code: `
67
+ [
68
+ ShinyButton.props().attr("calm").attr("gradient").children("calm").render(),
69
+ ShinyButton.props().attr("angry").attr("gradient").children("angry").render(),
70
+ ShinyButton.props().attr("happy").attr("gradient").children("happy").render(),
71
+ ShinyButton.props().attr("zesty").attr("gradient").children("zesty").render(),
72
+ ShinyButton.props().attr("sad").attr("gradient").children("sad").render(),
73
+ ShinyButton.props().attr("quirky").attr("gradient").children("quirky").render(),
74
+ ShinyButton.props().attr("plain").children("plain").render(),
75
+ ]
76
+ `},
77
+ {label: "css", code: cssSnippet},
78
+ ],
79
+ style: css``,
80
+ presentation: views => [
81
+ views.ShinyButton.props()
82
+ .attr("calm")
83
+ .attr("gradient")
84
+ .children("calm")
85
+ .render(),
86
+ views.ShinyButton.props()
87
+ .attr("angry")
88
+ .attr("gradient")
89
+ .children("angry")
90
+ .render(),
91
+ views.ShinyButton.props()
92
+ .attr("happy")
93
+ .attr("gradient")
94
+ .children("happy")
95
+ .render(),
96
+ views.ShinyButton.props()
97
+ .attr("zesty")
98
+ .attr("gradient")
99
+ .children("zesty")
100
+ .render(),
101
+ views.ShinyButton.props()
102
+ .attr("sad")
103
+ .attr("gradient")
104
+ .children("sad")
105
+ .render(),
106
+ views.ShinyButton.props()
107
+ .attr("quirky")
108
+ .attr("gradient")
109
+ .children("quirky")
110
+ .render(),
111
+ views.ShinyButton.props()
112
+ .attr("plain")
113
+ .children("plain")
114
+ .render(),
115
+ ],
116
+ },
117
+ ],
118
+ })
119
+
@@ -0,0 +1,63 @@
1
+
2
+ import {css} from "lit"
3
+ export default css`@layer view {
4
+
5
+ :host {
6
+ opacity: 0.8;
7
+ display: inline-flex;
8
+ width: max-content;
9
+ height: max-content;
10
+
11
+ --padding: 0.3em;
12
+ border-radius: 0.2em;
13
+ border: 0.1em solid currentColor;
14
+
15
+ cursor: pointer;
16
+ background: transparent;
17
+ user-select: none;
18
+ }
19
+
20
+ :host(:not([disabled]):is(:hover, :focus-visible)) { opacity: 1; }
21
+ :host(:not([disabled]):active) { opacity: 0.6; }
22
+
23
+ :host([disabled]) {
24
+ cursor: default;
25
+ color: var(--lame);
26
+ }
27
+
28
+ :host([hidden]) {
29
+ display: none !important;
30
+ }
31
+
32
+ :host([lame]) { color: var(--lame); }
33
+ :host([angry]) { color: var(--angry); }
34
+ :host([zesty]) { color: var(--zesty); }
35
+ :host([happy]) { color: var(--happy); }
36
+ :host([calm]) { color: var(--calm); }
37
+ :host([sad]) { color: var(--sad); }
38
+ :host([quirky]) { color: var(--quirky); }
39
+
40
+ button {
41
+ background: transparent;
42
+ border: none;
43
+
44
+ font: inherit;
45
+ color: inherit;
46
+ cursor: inherit;
47
+ text-shadow: inherit;
48
+
49
+ display: inline-flex;
50
+ justify-content: center;
51
+ align-items: center;
52
+
53
+ width: 100%;
54
+ height: 100%;
55
+ padding: var(--padding);
56
+ }
57
+
58
+ slot {
59
+ display: contents;
60
+ }
61
+
62
+ }`
63
+
@@ -33,6 +33,8 @@ export class ShinyCopy extends (
33
33
  async function click() {
34
34
  if (text === undefined) return
35
35
  try {
36
+ if (use.attrs.booleans.fail)
37
+ throw new Error("copy failed on purpose for testing purposes")
36
38
  await navigator.clipboard.writeText(text)
37
39
  await statusFlash("good")
38
40
  }
@@ -47,7 +49,7 @@ export class ShinyCopy extends (
47
49
  case "invalid": return clipboardSvg
48
50
  case "good": return clipboardCheckFilledSvg
49
51
  case "bad": return clipboardXFilledSvg
50
- default: throw new Error(`invalid copy status`)
52
+ default: throw new Error(`unknown copy status`)
51
53
  }})()
52
54
 
53
55
  return html`
@@ -0,0 +1,51 @@
1
+
2
+ import {css, html} from "lit"
3
+ import {Showcase} from "../../demo/views/showcase/view.js"
4
+
5
+ const cssSnippet = `
6
+ shiny-copy {
7
+ font-size: 1em;
8
+ --happy: #0fa;
9
+ --angry: #f50;
10
+ --lame: #8888;
11
+ --inactive-opacity: 0.5;
12
+ }
13
+ `
14
+
15
+ export const copyShowcase = () => Showcase({
16
+ name: "copy",
17
+ style: css`
18
+ .box {
19
+ > * { font-size: 4em; }
20
+ }
21
+ `,
22
+ exhibits: [
23
+ {
24
+ label: "succeed",
25
+ explain: html`<p>click-to-copy text button.</p>`,
26
+ snippets: [
27
+ {label: "html", code: `<shiny-copy text="hello world"></shiny-button>`},
28
+ {label: "view", code: `ShinyCopy("hello world")`},
29
+ {label: "css", code: cssSnippet},
30
+ ],
31
+ style: css``,
32
+ presentation: views => html`
33
+ ${views.ShinyCopy("hello world")}
34
+ `,
35
+ },
36
+ {
37
+ label: "fail",
38
+ explain: html`<p>copy text button, deliberately fails so you can see.</p>`,
39
+ snippets: [
40
+ {label: "html", code: `<shiny-copy fail></shiny-button>`},
41
+ {label: "view", code: `ShinyCopy.props("").attr("fail").render()`},
42
+ {label: "css", code: cssSnippet},
43
+ ],
44
+ style: css``,
45
+ presentation: views => html`
46
+ ${views.ShinyCopy.props("").attr("fail").render()}
47
+ `,
48
+ },
49
+ ],
50
+ })
51
+
@@ -12,7 +12,7 @@ import {ShinyContext, ShinyElement} from "../framework.js"
12
12
 
13
13
  export class ShinyDrawer extends (
14
14
  view(use => (context: ShinyContext, options: {
15
- button: boolean
15
+ button?: boolean
16
16
  side?: "left" | "right"
17
17
  control?: DrawerControl
18
18
  }) => {
@@ -21,44 +21,46 @@ export class ShinyDrawer extends (
21
21
  use.styles(foundationCss, context.theme, styleCss)
22
22
  const states = use.once(() => new States(use.element))
23
23
 
24
- const side = options.side ?? "left"
25
- const drawer = use.once(() => (options.control ?? new DrawerControl()))
24
+ const button = options.button ?? use.attrs.booleans.button
25
+ const side = options.side ?? (use.attrs.strings.side === "right" ? "right" : "left")
26
+ const control = use.once(() => (options.control ?? new DrawerControl()))
26
27
  states.assign(side)
27
28
 
28
29
  use.mount(() => dom.events(window, {keydown: (event: KeyboardEvent) => {
29
30
  if (event.code === "Escape")
30
- drawer.close()
31
+ control.close()
31
32
  }}))
32
33
 
33
- dom.attrs(use.element).booleans.open = drawer.isOpen
34
+ dom.attrs(use.element).booleans.open = control.isOpen
35
+
36
+ function renderButton() {
37
+ return html`
38
+ <button @click="${control.toggle}">
39
+ ${control.isOpen
40
+ ? html`
41
+ <slot name=button-x>
42
+ ${xSvg}
43
+ </slot>
44
+ `
45
+ : html`
46
+ <slot name=button>
47
+ ${menu2Svg}
48
+ </slot>
49
+ `}
50
+ </button>
51
+ `
52
+ }
34
53
 
35
54
  return html`
36
- <div class=shell ?data-open="${drawer.isOpen}" data-side="${side}">
37
- <slot name=plate ?inert="${drawer.isOpen}"></slot>
55
+ <div class=shell ?data-open="${control.isOpen}" data-side="${side}">
56
+ <slot name=plate ?inert="${control.isOpen}"></slot>
38
57
 
39
58
  <div class=clipper>
40
- <div part=blanket @click="${drawer.close}" ?inert="${!drawer.isOpen}"></div>
59
+ <div part=blanket @click="${control.close}" ?inert="${!control.isOpen}"></div>
41
60
 
42
61
  <div part=tray>
43
- <slot ?inert="${!drawer.isOpen}"></slot>
44
-
45
- ${options.button
46
- ? html`
47
- <button @click="${drawer.toggle}">
48
- ${drawer.isOpen
49
- ? html`
50
- <slot name=button-x>
51
- ${xSvg}
52
- </slot>
53
- `
54
- : html`
55
- <slot name=button>
56
- ${menu2Svg}
57
- </slot>
58
- `}
59
- </button>
60
- `
61
- : null}
62
+ <slot part=slate ?inert="${!control.isOpen}"></slot>
63
+ ${button ?renderButton() :null}
62
64
  </div>
63
65
  </div>
64
66
  </div>
@@ -83,10 +85,6 @@ export class ShinyDrawer extends (
83
85
  get open() { return this.control.open }
84
86
  get close() { return this.control.close }
85
87
  })
86
- .props(el => [el.context, {
87
- control: el.control,
88
- button: el.button,
89
- side: el.side,
90
- }] as const)
88
+ .props(el => [el.context, {control: el.control}] as const)
91
89
  ) {}
92
90
 
@@ -0,0 +1,93 @@
1
+
2
+ import {css, html} from "lit"
3
+ import {lipsum} from "../../demo/lipsum.js"
4
+ import {Showcase} from "../../demo/views/showcase/view.js"
5
+ import {ExhibitParams} from "../../demo/views/exhibit/view.js"
6
+
7
+ const lip1 = lipsum()
8
+ const lip2 = lipsum()
9
+ const lip3 = lipsum()
10
+
11
+ const cssSnippet = `
12
+ shiny-drawer {
13
+ --button-size: 2em;
14
+ --anim-duration: 200ms;
15
+ --slate-hidden-opacity: 1;
16
+ --blanket-backdrop-filter: blur(0.5em);
17
+ --blanket-bg: color-mix(
18
+ in oklab,
19
+ transparent,
20
+ var(--bg)
21
+ );
22
+ }
23
+ `
24
+
25
+ const makeExhibit = (side: "left" | "right"): ExhibitParams => ({
26
+ label: side,
27
+ explain: html`<p>slide-out panel. button optional.</p>`,
28
+ snippets: [
29
+ {label: "html", code: `
30
+ <shiny-drawer button side=${side}>
31
+ <header>example</header>
32
+ <section slot=plate>lorem kettlebell..</section>
33
+ </shiny-drawer>
34
+ `},
35
+ {label: "view", code: `
36
+ ShinyDrawer
37
+ .props({button: true, side: "${side}"})
38
+ .children(html\`
39
+ <header>example</header>
40
+ <section slot=plate>lorem kettlebell..</section>
41
+ \`)
42
+ .render()
43
+ `},
44
+ {label: "css", code: cssSnippet},
45
+ ],
46
+ style: css``,
47
+ presentation: views => html`
48
+ ${views.ShinyDrawer
49
+ .props({button: true, side})
50
+ .children(html`
51
+ <header>
52
+ <h2>example drawer</h2>
53
+ <p>you can put any content in here.</p>
54
+ <p class=lipsum>${lip1}</p>
55
+ </header>
56
+ <section slot=plate>
57
+ <p class=lipsum>${lip2}</p>
58
+ <p class=lipsum>${lip3}</p>
59
+ </section>
60
+ `)
61
+ .render()}
62
+ `,
63
+ })
64
+
65
+ export const drawerShowcase = () => Showcase({
66
+ name: "drawer",
67
+ style: css`
68
+ .box sly-view {
69
+ border-radius: 0.5em;
70
+ overflow: hidden;
71
+ --button-size: 3em;
72
+
73
+ header {
74
+ > * + * { margin-top: 0.5em; }
75
+ }
76
+
77
+ section {
78
+ display: flex;
79
+ flex-direction: column;
80
+ justify-content: center;
81
+ min-height: 100%;
82
+ padding: 1em;
83
+ padding-top: 3em;
84
+ > * + * { margin-top: 0.5em; }
85
+ }
86
+ }
87
+ `,
88
+ exhibits: [
89
+ makeExhibit("left"),
90
+ makeExhibit("right"),
91
+ ],
92
+ })
93
+
@@ -7,8 +7,8 @@ export default css`@layer view {
7
7
  width: 100%;
8
8
  height: 100%;
9
9
  --button-size: 2em;
10
- --anim-duration: 200ms;
11
10
  --blanket-backdrop-filter: blur(0.5em);
11
+ --slate-hidden-opacity: 1;
12
12
  --blanket-bg: color-mix(in oklab, transparent, var(--bg));
13
13
  }
14
14
 
@@ -52,12 +52,15 @@ export default css`@layer view {
52
52
  height: auto;
53
53
  max-height: 100%;
54
54
 
55
- opacity: 1;
56
55
  transform: translateX(-100%);
57
56
  will-change: opacity, transform;
58
57
  transition: all var(--anim-duration) ease;
59
58
 
60
- > slot {
59
+ > [part="slate"] {
60
+ opacity: var(--slate-hidden-opacity);
61
+ will-change: opacity;
62
+ transition: opacity var(--anim-duration) ease;
63
+
61
64
  display: block;
62
65
  height: 100%;
63
66
  overflow-y: auto;
@@ -110,8 +113,10 @@ export default css`@layer view {
110
113
  opacity: 1;
111
114
  }
112
115
  [part="tray"] {
113
- opacity: 1;
114
116
  transform: translateX(0%);
117
+ > [part="slate"] {
118
+ opacity: 1;
119
+ }
115
120
  }
116
121
  }
117
122
  }
@@ -1,10 +1,12 @@
1
1
 
2
+ import {ShinyButton} from "./button/component.js"
2
3
  import {ShinyCopy} from "./copy/component.js"
3
4
  import {ShinyDrawer} from "./drawer/component.js"
4
5
  import {ShinyExample} from "./example/component.js"
5
6
  import {ShinyTabs} from "./tabs/component.js"
6
7
 
7
8
  export const rawComponents = {
9
+ ShinyButton,
8
10
  ShinyCopy,
9
11
  ShinyDrawer,
10
12
  ShinyExample,
@@ -29,10 +29,21 @@ export class ShinyTabs extends (
29
29
  attrs.index = control.$index()
30
30
  control.length = $tabs().length
31
31
 
32
+ function isNeighborActive(index: number, delta: number) {
33
+ const nextIndex = control.clamp(index + delta)
34
+ if (nextIndex === index) return false
35
+ return (nextIndex === control.index)
36
+ }
37
+
32
38
  for (const [index, tab] of $tabs().entries()) {
33
39
  const active = (index === control.index)
34
- dom.attrs(tab).booleans.active = active
35
- dom.attrs(tab).booleans.disabled = active
40
+ const tabAttrs = dom.attrs(tab)
41
+ tabAttrs.booleans.disabled = active
42
+ tabAttrs.booleans["data-active"] = active
43
+ tabAttrs.booleans["data-first"] = (index === 0)
44
+ tabAttrs.booleans["data-last"] = (index === (control.length - 1))
45
+ tabAttrs.booleans["data-next-is-active"] = isNeighborActive(index, 1)
46
+ tabAttrs.booleans["data-previous-is-active"] = isNeighborActive(index, -1)
36
47
  tab.onclick = () => control.setIndex(index)
37
48
  }
38
49
 
@@ -9,14 +9,14 @@ export class TabControl {
9
9
  this.$index.value = start
10
10
  }
11
11
 
12
- #clamp(index: number) {
12
+ clamp(index: number) {
13
13
  index = Math.min(index, this.length - 1)
14
14
  index = Math.max(index, 0)
15
15
  return index
16
16
  }
17
17
 
18
18
  get index() {
19
- return this.#clamp(this.$index.get())
19
+ return this.clamp(this.$index.get())
20
20
  }
21
21
 
22
22
  async setIndex(index: number) {
@@ -24,7 +24,7 @@ export class TabControl {
24
24
  }
25
25
 
26
26
  async shimmy(delta: number) {
27
- const index = this.#clamp(this.index + delta)
27
+ const index = this.clamp(this.index + delta)
28
28
  return this.setIndex(index)
29
29
  }
30
30
  }