@nectary/labs 2.3.3 → 2.4.0

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 (35) hide show
  1. package/README.md +354 -0
  2. package/color-select/index.d.ts +39 -0
  3. package/color-select/index.js +96 -0
  4. package/index.d.ts +2 -0
  5. package/index.js +3 -0
  6. package/package.json +2 -6
  7. package/{phone-preview.d.ts → phone-preview/index.d.ts} +18 -7
  8. package/phone-preview/index.js +94 -0
  9. package/phone-preview-rcs-channel/index.d.ts +49 -0
  10. package/phone-preview-rcs-channel/index.js +175 -0
  11. package/phone-preview-rcs-channel-actions/index.d.ts +37 -0
  12. package/phone-preview-rcs-channel-actions/index.js +126 -0
  13. package/phone-preview-rcs-channel-info/index.d.ts +29 -0
  14. package/phone-preview-rcs-channel-info/index.js +64 -0
  15. package/phone-preview-rcs-channel-info-option/index.d.ts +40 -0
  16. package/phone-preview-rcs-channel-info-option/index.js +106 -0
  17. package/phone-preview-rcs-channel-options/index.d.ts +27 -0
  18. package/phone-preview-rcs-channel-options/index.js +46 -0
  19. package/phone-preview-rcs-channel-tabs/index.d.ts +34 -0
  20. package/phone-preview-rcs-channel-tabs/index.js +79 -0
  21. package/{phone-preview-rcs-chat.d.ts → phone-preview-rcs-chat/index.d.ts} +20 -8
  22. package/phone-preview-rcs-chat/index.js +79 -0
  23. package/phone-preview-rcs-chat-message/index.d.ts +35 -0
  24. package/phone-preview-rcs-chat-message/index.js +48 -0
  25. package/utils/element.d.ts +9 -0
  26. package/utils/element.js +35 -0
  27. package/utils/index.d.ts +1 -1
  28. package/utils/index.js +1 -1
  29. package/Readme.md +0 -1
  30. package/color-select.d.ts +0 -34
  31. package/color-select.js +0 -79
  32. package/phone-preview-rcs-channel.d.ts +0 -50
  33. package/phone-preview-rcs-channel.js +0 -319
  34. package/phone-preview-rcs-chat.js +0 -162
  35. package/phone-preview.js +0 -120
@@ -1,319 +0,0 @@
1
- import '@nectary/components/icon';
2
- import { customElement } from 'solid-element';
3
- import { createSignal, For } from 'solid-js';
4
- import html from 'solid-js/html';
5
- import pkg from './package.json';
6
- import { defineCustomElement } from './utils';
7
- const style = `
8
- :where(*, *::before, *::after) {
9
- box-sizing: border-box;
10
- padding: 0;
11
- border: 0;
12
- margin: 0;
13
- font: inherit;
14
- }
15
-
16
- .root {
17
- --banner-color: var(--sinch-sys-color-surface-tertiary-active);
18
- --logo-color: var(--sinch-sys-color-surface-secondary-default);
19
- display: flex;
20
- flex-flow: column;
21
- color: var(--sinch-sys-color-text-default);
22
-
23
- & > img:first-of-type {
24
- block-size: 70px;
25
- margin-block-end: -40px;
26
- background: var(--banner-color);
27
- }
28
-
29
- & > img:last-of-type {
30
- block-size: 64px;
31
- inline-size: 64px;
32
- border-radius: 100%;
33
- background: var(--logo-color);
34
- align-self: center;
35
- }
36
-
37
- & > h1 {
38
- padding: 8px 24px;
39
- font: var(--sinch-sys-font-body-m);
40
- text-align: center;
41
- text-wrap: balance;
42
- word-wrap: break-word;
43
- }
44
-
45
- & > p {
46
- padding-inline: 24px;
47
- font: var(--sinch-sys-font-body-xs);
48
- text-align: center;
49
- text-wrap: balance;
50
- word-wrap: break-word;
51
- }
52
-
53
- & > .actions {
54
- align-self: center;
55
- padding-block: 32px 24px;
56
- }
57
-
58
- & > .tabs {
59
- padding-block-end: 8px;
60
- }
61
- }
62
-
63
- .actions {
64
- display: grid;
65
- grid-auto-columns: 1fr;
66
- grid-auto-flow: column;
67
- gap: 24px;
68
- font: var(--sinch-sys-font-body-xs);
69
-
70
- & > a {
71
- display: flex;
72
- flex-flow: column;
73
- align-items: center;
74
- gap: 2px;
75
- color: inherit;
76
- text-decoration: none;
77
-
78
- &[inert] {
79
- --sinch-global-color-icon: currentColor;
80
- color: var(--sinch-sys-color-text-muted);
81
- }
82
- }
83
- }
84
-
85
-
86
- .info {
87
- display: flex;
88
- flex-flow: column;
89
- font: var(--sinch-sys-font-body-xs);
90
-
91
- & > a {
92
- display: grid;
93
- grid-template:
94
- "icon contact" auto
95
- "icon label " auto
96
- / auto 1fr;
97
- align-items: center;
98
- gap: 0 16px;
99
- padding: 8px 16px;
100
- border-block-end: 1px solid
101
- var(--sinch-sys-color-surface-secondary-active);
102
- color: currentColor;
103
- word-break: break-all;
104
- text-decoration: none;
105
-
106
- & > .icon-link {
107
- grid-area: icon;
108
- }
109
-
110
- & > span {
111
- grid-area: contact;
112
-
113
- &::before {
114
- content: "\\200b";
115
- }
116
- }
117
-
118
- & > p {
119
- grid-area: label;
120
-
121
- &::before {
122
- content: "\\200b";
123
- }
124
- }
125
-
126
- &[inert] {
127
- --sinch-global-color-icon: currentColor;
128
- color: var(--sinch-sys-color-text-muted);
129
- }
130
- }
131
- }
132
-
133
- .tabs {
134
- --highlight-color: var(--sinch-sys-color-text-default);
135
- display: flex;
136
-
137
- & > button {
138
- flex: 1;
139
- padding-block-end: 10px;
140
- border-block-end: 2px solid transparent;
141
- outline: none;
142
- background: transparent;
143
- color: var(--sinch-sys-color-text-disabled);
144
- font: var(--sinch-sys-font-desktop-title-xs);
145
-
146
- &.active {
147
- color: var(--sinch-sys-color-primary-default);
148
- border-block-end: 2px solid var(--highlight-color);
149
- }
150
- }
151
- }
152
-
153
-
154
- .options {
155
- display: flex;
156
- flex-flow: column;
157
- font: var(--sinch-sys-font-body-xs);
158
-
159
- & > header {
160
- padding-block-end: 8px;
161
- }
162
-
163
- & > span {
164
- font: var(--sinch-sys-font-body-xxs);
165
- }
166
-
167
- & > button {
168
- padding: 4px;
169
- outline: none;
170
- background: transparent;
171
- text-align: start;
172
- }
173
-
174
- & > hr {
175
- border-color: var(--sinch-sys-color-surface-secondary-active);
176
- }
177
- }
178
- `;
179
- const Actions = (props) => {
180
- const number = () => props.phones.at(0)?.number ?? '';
181
- const url = () => props.websites.at(0)?.url ?? '';
182
- const email = () => props.emails.at(0)?.address ?? '';
183
- const numberHref = () => `tel:${number()}`;
184
- const urlHref = url;
185
- const emailHref = () => `mailto:${email()}`;
186
- return html `
187
- <section class="actions">
188
- <a inert=${() => number() === ''} target="_blank" href=${numberHref}>
189
- <sinch-icon icons-version="2" name="fa-phone" class="icon-link" />
190
- Call
191
- </a>
192
- <a inert=${() => url() === ''} target="_blank" href=${urlHref}>
193
- <sinch-icon icons-version="2" name="fa-earth-americas" name="public" class="icon-link" />
194
- Website
195
- </a>
196
- <a inert=${() => email() === ''} target="_blank" href=${emailHref}>
197
- <sinch-icon icons-version="2" name="envelope" name="mail" class="icon-link" />
198
- Email
199
- </a>
200
- </section>
201
- `;
202
- };
203
- const Info = (props) => {
204
- const phones = () => ((props.phones.length > 0)
205
- ? props.phones
206
- : [{ label: 'Contact us', number: '+1234567890' }]);
207
- const websites = () => ((props.websites.length > 0)
208
- ? props.websites
209
- : [{ label: 'Contact us', url: 'https://company.com' }]);
210
- const emails = () => ((props.emails.length > 0)
211
- ? props.emails
212
- : [{ label: 'Contact us', address: 'mail@company.com' }]);
213
- return html `
214
- <section class="info">
215
- <${For} each=${phones}>
216
- ${({ label, number }) => html `
217
- <a
218
- inert=${() => props.phones.length === 0}
219
- target="_blank"
220
- href=${`tel:${number}`}
221
- >
222
- <sinch-icon icons-version="2" name="fa-phone" class="icon-link" />
223
- <span>${number}</span>
224
- <p>${label}</p>
225
- </a>
226
- `}
227
- <//>
228
- <${For} each=${websites}>
229
- ${({ label, url }) => html `
230
- <a inert=${() => props.websites.length === 0} target="_blank" href=${url}>
231
- <sinch-icon icons-version="2" name="fa-earth-americas" name="public" class="icon-link" />
232
- <span>${url}</span>
233
- <p>${label}</p>
234
- </a>
235
- `}
236
- <//>
237
- <${For} each=${emails}>
238
- ${({ label, address }) => html `
239
- <a
240
- inert=${() => props.emails.length === 0}
241
- target="_blank"
242
- href=${`mailto:${address}`}
243
- >
244
- <sinch-icon icons-version="2" name="envelope" name="mail" class="icon-link" />
245
- <span>${address}</span>
246
- <p>${label}</p>
247
- </a>
248
- `}
249
- <//>
250
- </section>
251
- `;
252
- };
253
- const Tabs = (props) => html `
254
- <section class="tabs" style=${() => ({ '--highlight-color': props.color })}>
255
- <${For} each=${['Info', 'Options']}>
256
- ${(label, i) => html `
257
- <button
258
- class=${() => (i() === props.tab ? 'active' : '')}
259
- on:click=${() => props.onTab?.(i())}
260
- >
261
- ${label}
262
- </button>
263
- `}
264
- <//>
265
- </section>
266
- `;
267
- const Options = () => html `
268
- <section class="options">
269
- <header>Notifications</header>
270
- <span>Business</span>
271
- <button>Block & report spam</button>
272
- <hr />
273
- <button>View Privacy Policy</button>
274
- <hr />
275
- <button>View Terms of Service</button>
276
- <hr />
277
- <button>Learn mode</button>
278
- </section>
279
- `;
280
- /**
281
- * RCS channel preview component.
282
- *
283
- * @param props.color Brand color, used in the banner (if no image provided) and tabs.
284
- * @param props.name Brand name.
285
- * @param props.description Brand description.
286
- * @param props.banner Brand banner image.
287
- * @param props.logo Brand logo image.
288
- * @param props.phone Brand phone numbers.
289
- * @param props.website Brand website URLs.
290
- * @param props.email Brand email addresses.
291
- */
292
- export const RcsChannelPreview = (props) => {
293
- const [tab, setTab] = createSignal(0);
294
- const transparentIcon = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
295
- return html `
296
- <style>
297
- ${style}
298
- </style>
299
- <section class="root" style=${() => ({ '--banner-color': props.color })}>
300
- <img src=${() => (props.banner !== '' ? props.banner : transparentIcon)} alt="" />
301
- <img src=${() => (props.logo !== '' ? props.logo : transparentIcon)} alt="" />
302
- <h1>${() => (props.name !== '' ? props.name : 'Brand name')}</h1>
303
- <p>${() => (props.description !== '' ? props.description : 'Brand description')}</p>
304
- <${Actions} ...${props} />
305
- <${Tabs} color=${() => props.color} tab=${tab} onTab=${setTab} />
306
- ${() => (tab() === 0 ? html `<${Info} ...${props} />` : html `<${Options} />`)}
307
- </section>
308
- `;
309
- };
310
- defineCustomElement('sinch-labs-phone-preview-rcs-channel', customElement(`sinch-labs-phone-preview-rcs-channel-${pkg.version}`, {
311
- name: '',
312
- description: '',
313
- color: '',
314
- banner: '',
315
- logo: '',
316
- phones: [],
317
- websites: [],
318
- emails: [],
319
- }, RcsChannelPreview));
@@ -1,162 +0,0 @@
1
- import '@nectary/components/icon';
2
- import { customElement } from 'solid-element';
3
- import { For } from 'solid-js';
4
- import html from 'solid-js/html';
5
- import pkg from './package.json';
6
- import { defineCustomElement } from './utils';
7
- const style = `
8
- :where(*, *::before, *::after) {
9
- box-sizing: border-box;
10
- padding: 0;
11
- border: 0;
12
- margin: 0;
13
- font: inherit;
14
- }
15
-
16
- .root {
17
- --logo-color: var(--sinch-sys-color-surface-secondary-default);
18
- block-size: 100%;
19
- display: flex;
20
- flex-flow: column;
21
-
22
- & > header {
23
- display: flex;
24
- gap: 8px;
25
- padding: 12px 4px;
26
- background: var(--sinch-sys-color-surface-tertiary-default);
27
- font: var(--sinch-sys-font-body-m);
28
-
29
- & > img {
30
- block-size: 24px;
31
- inline-size: 24px;
32
- margin-inline-start: 8px;
33
- border-radius: 100%;
34
- background: var(--logo-color);
35
- }
36
-
37
- & > h1 {
38
- flex: 1;
39
- overflow: hidden;
40
- min-inline-size: 0;
41
- text-wrap: nowrap;
42
- text-overflow: ellipsis;
43
- }
44
- }
45
-
46
- & > div {
47
- flex: 1;
48
- overflow-y: auto;
49
- scrollbar-width: none;
50
- display: flex;
51
- flex-flow: column;
52
- gap: 8px;
53
- padding: 8px;
54
- margin-block-end: 8px;
55
-
56
- & > img {
57
- block-size: 64px;
58
- inline-size: 64px;
59
- border-radius: 100%;
60
- align-self: center;
61
- background: var(--logo-color);
62
- }
63
-
64
- & > p {
65
- padding-inline: 24px;
66
- font: var(--sinch-sys-font-body-xs);
67
- text-align: center;
68
- text-wrap: balance;
69
- word-wrap: break-word;
70
- }
71
-
72
- & > hr {
73
- border-block-end: 1px solid var(--sinch-sys-color-border-subtle);
74
- }
75
- }
76
-
77
- & > footer {
78
- display: flex;
79
- align-items: center;
80
- gap: 8px;
81
- font: var(--sinch-sys-font-body-xs);
82
-
83
- & > div {
84
- flex: 1;
85
- display: flex;
86
- align-items: center;
87
- gap: 8px;
88
- padding: 8px 16px;
89
- border-radius: 24px;
90
- background: var(--sinch-sys-color-surface-primary-active);
91
-
92
- & > span {
93
- flex: 1;
94
- }
95
- }
96
- }
97
- }
98
-
99
- .message {
100
- padding: 8px 12px;
101
- margin-inline-end: 8px;
102
- border-radius: 16px;
103
- border-end-start-radius: 0;
104
- background: var(--sinch-sys-color-feedback-info-subtle);
105
- font: var(--sinch-sys-font-body-xs);
106
- }
107
- `;
108
- const Message = (props) => html `<section class="message">${props.message}</section>`;
109
- /**
110
- * RCS chat preview component.
111
- *
112
- * @param props.name Brand name.
113
- * @param props.description Brand description.
114
- * @param props.logo Brand logo image.
115
- * @param props.messages List of messages.
116
- */
117
- export const RcsChatPreview = (props) => {
118
- const transparentIcon = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
119
- return html ` <style>
120
- ${style}
121
- </style>
122
- <section class="root">
123
- <header>
124
- <sinch-icon icons-version="2" name="fa-arrow-left"></sinch-icon>
125
- <img
126
- src=${() => (props.logo !== '' ? props.logo : transparentIcon)}
127
- alt=""
128
- />
129
- <h1>${() => (props.name !== '' ? props.name : 'Brand name')}</h1>
130
- <sinch-icon icons-version="2" name="fa-shield-check" name="verified_user" />
131
- <sinch-icon icons-version="2" name="fa-ellipsis-vertical" name="more_vert" />
132
- </header>
133
- <div>
134
- <img
135
- src=${() => (props.logo !== '' ? props.logo : transparentIcon)}
136
- alt=""
137
- />
138
- <p>
139
- ${() => (props.description !== '' ? props.description : 'Brand description')}
140
- </p>
141
- <hr />
142
- <${For} each=${() => props.messages}>
143
- ${(message) => html `<${Message} message=${message} />`}
144
- <//>
145
- </div>
146
- <footer>
147
- <sinch-icon icons-version="2" name="fa-circle-plus" />
148
- <sinch-icon icons-version="2" name="fa-camera" />
149
- <div>
150
- <span>RCS Message</span>
151
- <sinch-icon icons-version="2" name="fa-face-smile" />
152
- <sinch-icon icons-version="2" name="microphone" name="mic" />
153
- </div>
154
- </footer>
155
- </section>`;
156
- };
157
- defineCustomElement('sinch-labs-phone-preview-rcs-chat', customElement(`sinch-labs-phone-preview-rcs-chat-${pkg.version}`, {
158
- name: '',
159
- description: '',
160
- logo: '',
161
- messages: [],
162
- }, RcsChatPreview));
package/phone-preview.js DELETED
@@ -1,120 +0,0 @@
1
- import { customElement } from 'solid-element';
2
- import { createComputed, createMemo, createSignal, onCleanup, onMount, } from 'solid-js';
3
- import html from 'solid-js/html';
4
- import pkg from './package.json';
5
- import { defineCustomElement } from './utils';
6
- const style = `
7
- :where(*, *::before, *::after) {
8
- box-sizing: border-box;
9
- padding: 0;
10
- border: 0;
11
- margin: 0;
12
- font: inherit;
13
- }
14
-
15
- :host {
16
- --base-size: 288px; /* 18rem */
17
- --aspect-ratio: 1 / 2.1;
18
- --scale: 1;
19
- inline-size: min(100%, var(--base-size));
20
- aspect-ratio: var(--aspect-ratio);
21
- overflow: hidden;
22
- display: block;
23
- }
24
-
25
- section {
26
- position: relative;
27
- inline-size: var(--base-size);
28
- aspect-ratio: var(--aspect-ratio);
29
- scale: var(--scale);
30
- transform-origin: top left;
31
- overflow: hidden;
32
- display: flex;
33
- flex-flow: column;
34
- padding: 12px;
35
- border: 1px solid var(--sinch-sys-color-border-strong);
36
- border-radius: 32px;
37
- background: var(--sinch-sys-color-surface-primary-default);
38
-
39
- & > header {
40
- position: sticky;
41
- inset-block-start: 0;
42
- display: flex;
43
- justify-content: space-between;
44
- padding: 16px;
45
- background: var(--sinch-sys-color-surface-primary-default);
46
- font: var(--sinch-sys-font-body-xxs);
47
-
48
- & > svg {
49
- inline-size: 48px;
50
- }
51
- }
52
-
53
- & > div {
54
- flex: 1;
55
- overflow-y: auto;
56
- scrollbar-width: none;
57
- border-end-start-radius: 16px;
58
- border-end-end-radius: 16px;
59
- }
60
- }
61
- `;
62
- const StatusSvg = () => html `
63
- <svg viewBox="0 0 50 12">
64
- <path
65
- d="M13.2 2.4h-.7c-.4 0-.7.3-.7.7v6.2c0 .4.3.8.7.8h.7c.4 0 .7-.4.7-.8V3.1c0-.4-.3-.7-.7-.7ZM9.4 4.2h.6c.4 0 .7.3.7.7v4.5c0 .3-.3.7-.7.7h-.6c-.4 0-.7-.4-.7-.7V4.9c0-.4.3-.7.7-.7ZM6.9 6h-.7c-.4 0-.7.3-.7.7v2.7c0 .4.3.7.7.7h.7c.4 0 .7-.3.7-.7V6.7c0-.4-.3-.7-.7-.7ZM3.7 7.3H3c-.3 0-.6.4-.6.7v1.4c0 .4.3.7.6.7h.7c.4 0 .7-.3.7-.7V8c0-.3-.3-.7-.7-.7Zm19.4-3.7c1.7 0 3.3.7 4.5 1.8h.3l.8-.9.1-.1-.1-.2a8 8 0 0 0-11.1 0l-.1.2.1.1.8.9h.3a6.7 6.7 0 0 1 4.4-1.8Zm0 2.8a4 4 0 0 1 2.5 1h.3l.9-.9v-.3a5.4 5.4 0 0 0-7.3 0v.3l.9.9h.3c.7-.6 1.5-1 2.4-1Zm1.8 1.9-.1.2-1.5 1.5-.2.1-.1-.1-1.5-1.5-.1-.2.1-.1c1-.8 2.3-.8 3.3 0l.1.1Z"
66
- />
67
- <rect width="12.6" height="5.4" x="33.3" y="3.5" rx=".9" />
68
- <path fill="#999" d="M48 4.9v2.7c.5-.3.9-.8.9-1.4 0-.6-.4-1.1-.9-1.3Z" />
69
- <path
70
- fill="none"
71
- stroke="#999"
72
- stroke-width=".7"
73
- d="M32.3 4.4a2 2 0 0 1 1.9-1.9H45c1.1 0 2 .9 2 1.9V8c0 1.1-.9 1.9-2 1.9H34.2c-1 0-1.9-.8-1.9-1.9V4.4Z"
74
- />
75
- </svg>
76
- `;
77
- /**
78
- * Container for channel previews in a styled phone container.
79
- * This container uses a custom scaling where the internal elements are scaled to fit the container from a fixed size.
80
- * Because of the fixed size, absolute units (px) are preferred over relative units (rem, em) for the internal elements.
81
- *
82
- * @param props.locale Clock locale.
83
- * @param props.clock Clock `Intl.DateTimeFormat` options.
84
- * @param props.children Content to display in the phone container.
85
- */
86
- const PhonePreview = (props, options) => {
87
- const host = options.element;
88
- const observer = new ResizeObserver(() => {
89
- const style = getComputedStyle(host);
90
- const baseSize = parseFloat(style.getPropertyValue('--base-size'));
91
- const currentSize = host.getBoundingClientRect().width;
92
- host.style.setProperty('--scale', `${currentSize / baseSize}`);
93
- });
94
- onMount(() => {
95
- const section = host.shadowRoot.querySelector('section');
96
- observer.observe(host);
97
- observer.observe(section);
98
- });
99
- onCleanup(() => observer.disconnect());
100
- const fmt = createMemo(() => Intl.DateTimeFormat(props.locale, props.clock));
101
- const [clock, setClock] = createSignal();
102
- const interval = setInterval(() => setClock(fmt().format()), 60000);
103
- createComputed(() => setClock(fmt().format()));
104
- onCleanup(() => clearInterval(interval));
105
- return html `
106
- <style>
107
- ${style}
108
- </style>
109
- <section>
110
- <header>
111
- <span>${clock}</span>
112
- <${StatusSvg} />
113
- </header>
114
- <div>
115
- <slot />
116
- </div>
117
- </section>
118
- `;
119
- };
120
- defineCustomElement('sinch-labs-phone-preview', customElement(`sinch-labs-phone-preview-${pkg.version}`, { locale: 'en-US', clock: { hour: '2-digit', minute: '2-digit' } }, PhonePreview));