@coyalabs/bts-style 1.3.12 → 1.3.13

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.
package/README.md CHANGED
@@ -124,6 +124,11 @@ Typography component with three text variants.
124
124
 
125
125
  **Props:**
126
126
  - `variant: 'title' | 'content' | 'button'` - Text style variant
127
+ - `content?: string | null` - Raw text content to render directly
128
+ - `markdown?: boolean` - Parse `content` as sanitized markdown
129
+ - `as?: string | null` - Override the wrapper element (`div` is used automatically for markdown)
130
+ - `textModifier?: string` - Size adjustment
131
+ - `textWeightModifier?: string` - Weight adjustment
127
132
 
128
133
  **Variants:**
129
134
  - **`title`** - Noto Serif KR 900, 30px - For headings
@@ -134,8 +139,19 @@ Typography component with three text variants.
134
139
  ```svelte
135
140
  <BaseText variant="title">Page Title</BaseText>
136
141
  <BaseText variant="content">This is content text.</BaseText>
142
+
143
+ <BaseText
144
+ variant="content"
145
+ content={messageBody}
146
+ markdown={true}
147
+ />
137
148
  ```
138
149
 
150
+ **Markdown notes:**
151
+ - Markdown rendering only applies when using the `content` prop.
152
+ - Output is sanitized before rendering.
153
+ - Supports GFM-style links, emphasis, headings, lists, blockquotes, tables, inline code, and fenced code blocks.
154
+
139
155
  ---
140
156
 
141
157
  #### BaseIcon
@@ -1,9 +1,11 @@
1
1
  <script>
2
+ import { renderMarkdown } from './markdown.js';
3
+
2
4
  /**
3
5
  * @type {'title' | 'content' | 'button' | 'code'}
4
6
  */
5
7
  export let variant = 'content';
6
- /**
8
+ /**
7
9
  * @type {string}
8
10
  */
9
11
  export let textModifier = '0px';
@@ -12,11 +14,47 @@
12
14
  * @type {string}
13
15
  */
14
16
  export let textWeightModifier = '0';
17
+
18
+ /**
19
+ * Raw text content. Use this with `markdown={true}` if you want markdown parsing.
20
+ * @type {string | null}
21
+ */
22
+ export let content = null;
23
+
24
+ /**
25
+ * Parse the `content` prop as markdown and render sanitized HTML.
26
+ * @type {boolean}
27
+ */
28
+ export let markdown = false;
29
+
30
+ /**
31
+ * Override the wrapper element. Defaults to `span`, or `div` when markdown is enabled.
32
+ * @type {string | null}
33
+ */
34
+ export let as = null;
35
+
36
+ $: usesContent = content !== null && content !== undefined;
37
+ $: tagName = as || (markdown && usesContent ? 'div' : 'span');
38
+ $: renderedContent = markdown && usesContent ? renderMarkdown(content) : '';
15
39
  </script>
16
40
 
17
- <span class="text" data-variant={variant} style="--text-modifier: {textModifier}; --text-weight-modifier: {textWeightModifier};">
18
- <slot />
19
- </span>
41
+ <svelte:element
42
+ this={tagName}
43
+ class="text"
44
+ data-variant={variant}
45
+ data-markdown={markdown && usesContent}
46
+ style="--text-modifier: {textModifier}; --text-weight-modifier: {textWeightModifier};"
47
+ >
48
+ {#if usesContent}
49
+ {#if markdown}
50
+ {@html renderedContent}
51
+ {:else}
52
+ {content}
53
+ {/if}
54
+ {:else}
55
+ <slot />
56
+ {/if}
57
+ </svelte:element>
20
58
 
21
59
  <style>
22
60
  @import url('https://fonts.googleapis.com/css2?family=Noto+Serif+KR:wght@200..900&display=swap');
@@ -28,6 +66,103 @@
28
66
  font-family: 'Satoshi', sans-serif;
29
67
  caret-color: #544a51;
30
68
  }
69
+ .text[data-markdown='true'] {
70
+ display: block;
71
+ width: 100%;
72
+ line-height: 1.6;
73
+ }
74
+ .text[data-markdown='true'] :global(p),
75
+ .text[data-markdown='true'] :global(ul),
76
+ .text[data-markdown='true'] :global(ol),
77
+ .text[data-markdown='true'] :global(blockquote),
78
+ .text[data-markdown='true'] :global(pre),
79
+ .text[data-markdown='true'] :global(table),
80
+ .text[data-markdown='true'] :global(hr) {
81
+ margin: 0 0 0.9rem;
82
+ }
83
+ .text[data-markdown='true'] :global(h1),
84
+ .text[data-markdown='true'] :global(h2),
85
+ .text[data-markdown='true'] :global(h3),
86
+ .text[data-markdown='true'] :global(h4),
87
+ .text[data-markdown='true'] :global(h5),
88
+ .text[data-markdown='true'] :global(h6) {
89
+ margin: 1.25rem 0 0.6rem;
90
+ font-family: 'Noto Serif KR', serif;
91
+ color: #E3D8D8;
92
+ line-height: 1.2;
93
+ }
94
+ .text[data-markdown='true'] :global(h1:first-child),
95
+ .text[data-markdown='true'] :global(h2:first-child),
96
+ .text[data-markdown='true'] :global(h3:first-child),
97
+ .text[data-markdown='true'] :global(h4:first-child),
98
+ .text[data-markdown='true'] :global(h5:first-child),
99
+ .text[data-markdown='true'] :global(h6:first-child),
100
+ .text[data-markdown='true'] :global(p:first-child),
101
+ .text[data-markdown='true'] :global(ul:first-child),
102
+ .text[data-markdown='true'] :global(ol:first-child),
103
+ .text[data-markdown='true'] :global(blockquote:first-child),
104
+ .text[data-markdown='true'] :global(pre:first-child),
105
+ .text[data-markdown='true'] :global(table:first-child) {
106
+ margin-top: 0;
107
+ }
108
+ .text[data-markdown='true'] :global(*:last-child) {
109
+ margin-bottom: 0;
110
+ }
111
+ .text[data-markdown='true'] :global(ul),
112
+ .text[data-markdown='true'] :global(ol) {
113
+ padding-left: 1.4rem;
114
+ }
115
+ .text[data-markdown='true'] :global(li + li) {
116
+ margin-top: 0.2rem;
117
+ }
118
+ .text[data-markdown='true'] :global(blockquote) {
119
+ border-left: 2px solid #3E353A;
120
+ padding-left: 0.9rem;
121
+ color: #C7BDC1;
122
+ }
123
+ .text[data-markdown='true'] :global(a) {
124
+ color: #FFEFF6;
125
+ text-decoration: underline;
126
+ text-decoration-thickness: 1px;
127
+ text-underline-offset: 0.14em;
128
+ }
129
+ .text[data-markdown='true'] :global(code) {
130
+ font-family: 'JetBrains Mono', monospace;
131
+ font-size: 0.92em;
132
+ background: rgba(255, 255, 255, 0.06);
133
+ border: 1px solid rgba(255, 255, 255, 0.08);
134
+ border-radius: 0.45rem;
135
+ padding: 0.12rem 0.32rem;
136
+ color: #E3D8D8;
137
+ }
138
+ .text[data-markdown='true'] :global(pre) {
139
+ overflow-x: auto;
140
+ padding: 0.85rem 1rem;
141
+ background: rgba(11, 7, 13, 0.8);
142
+ border: 1px solid rgba(255, 255, 255, 0.08);
143
+ border-radius: 0.85rem;
144
+ }
145
+ .text[data-markdown='true'] :global(pre code) {
146
+ display: block;
147
+ padding: 0;
148
+ border: none;
149
+ background: transparent;
150
+ white-space: pre;
151
+ }
152
+ .text[data-markdown='true'] :global(table) {
153
+ width: 100%;
154
+ border-collapse: collapse;
155
+ }
156
+ .text[data-markdown='true'] :global(th),
157
+ .text[data-markdown='true'] :global(td) {
158
+ text-align: left;
159
+ padding: 0.45rem 0.6rem;
160
+ border-bottom: 1px solid rgba(255, 255, 255, 0.08);
161
+ }
162
+ .text[data-markdown='true'] :global(hr) {
163
+ border: none;
164
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
165
+ }
31
166
  /* Title variant */
32
167
  .text[data-variant='title'] {
33
168
  font-family: 'Noto Serif KR', serif;
@@ -55,4 +190,27 @@
55
190
  font-size: calc(14px + var(--text-modifier));
56
191
  color: #E3D8D8;
57
192
  }
193
+ .text[data-variant='title'][data-markdown='true'] :global(h1) {
194
+ font-size: calc(30px + var(--text-modifier));
195
+ font-weight: calc(900 + var(--text-weight-modifier));
196
+ }
197
+ .text[data-variant='title'][data-markdown='true'] :global(h2) {
198
+ font-size: calc(24px + var(--text-modifier));
199
+ font-weight: calc(900 + var(--text-weight-modifier));
200
+ }
201
+ .text[data-variant='content'][data-markdown='true'] :global(h1) {
202
+ font-size: calc(24px + var(--text-modifier));
203
+ font-weight: calc(850 + var(--text-weight-modifier));
204
+ }
205
+ .text[data-variant='content'][data-markdown='true'] :global(h2) {
206
+ font-size: calc(21px + var(--text-modifier));
207
+ font-weight: calc(800 + var(--text-weight-modifier));
208
+ }
209
+ .text[data-variant='content'][data-markdown='true'] :global(h3),
210
+ .text[data-variant='button'][data-markdown='true'] :global(h1),
211
+ .text[data-variant='button'][data-markdown='true'] :global(h2),
212
+ .text[data-variant='button'][data-markdown='true'] :global(h3) {
213
+ font-size: calc(18px + var(--text-modifier));
214
+ font-weight: calc(700 + var(--text-weight-modifier));
215
+ }
58
216
  </style>
@@ -1,8 +1,11 @@
1
1
  export default BaseText;
2
2
  type BaseText = SvelteComponent<$$__sveltets_2_PropsWithChildren<{
3
+ as?: string | null | undefined;
4
+ content?: string | null | undefined;
3
5
  variant?: "button" | "code" | "title" | "content" | undefined;
4
6
  textModifier?: string | undefined;
5
7
  textWeightModifier?: string | undefined;
8
+ markdown?: boolean | undefined;
6
9
  }, {
7
10
  default: {};
8
11
  }>, {
@@ -13,9 +16,12 @@ type BaseText = SvelteComponent<$$__sveltets_2_PropsWithChildren<{
13
16
  $$bindings?: string | undefined;
14
17
  };
15
18
  declare const BaseText: $$__sveltets_2_IsomorphicComponent<$$__sveltets_2_PropsWithChildren<{
19
+ as?: string | null | undefined;
20
+ content?: string | null | undefined;
16
21
  variant?: "button" | "code" | "title" | "content" | undefined;
17
22
  textModifier?: string | undefined;
18
23
  textWeightModifier?: string | undefined;
24
+ markdown?: boolean | undefined;
19
25
  }, {
20
26
  default: {};
21
27
  }>, {
@@ -0,0 +1,4 @@
1
+ /**
2
+ * @param {string} source
3
+ */
4
+ export function renderMarkdown(source?: string): any;
@@ -0,0 +1,65 @@
1
+ import sanitizeHtml from 'sanitize-html';
2
+ import { marked } from 'marked';
3
+
4
+ marked.setOptions({
5
+ gfm: true,
6
+ breaks: true,
7
+ });
8
+
9
+ const ALLOWED_TAGS = [
10
+ 'a',
11
+ 'blockquote',
12
+ 'br',
13
+ 'code',
14
+ 'del',
15
+ 'em',
16
+ 'h1',
17
+ 'h2',
18
+ 'h3',
19
+ 'h4',
20
+ 'h5',
21
+ 'h6',
22
+ 'hr',
23
+ 'li',
24
+ 'ol',
25
+ 'p',
26
+ 'pre',
27
+ 'strong',
28
+ 'table',
29
+ 'tbody',
30
+ 'td',
31
+ 'th',
32
+ 'thead',
33
+ 'tr',
34
+ 'ul',
35
+ ];
36
+
37
+ const ALLOWED_ATTRIBUTES = {
38
+ a: ['href', 'title', 'target', 'rel'],
39
+ code: ['class'],
40
+ td: ['align'],
41
+ th: ['align'],
42
+ };
43
+
44
+ /**
45
+ * @param {string} source
46
+ */
47
+ export function renderMarkdown(source = '') {
48
+ const unsafeHtml = marked.parse(String(source));
49
+
50
+ return sanitizeHtml(unsafeHtml, {
51
+ allowedTags: ALLOWED_TAGS,
52
+ allowedAttributes: ALLOWED_ATTRIBUTES,
53
+ allowedSchemes: ['http', 'https', 'mailto'],
54
+ transformTags: {
55
+ a: sanitizeHtml.simpleTransform(
56
+ 'a',
57
+ {
58
+ target: '_blank',
59
+ rel: 'noreferrer noopener',
60
+ },
61
+ true,
62
+ ),
63
+ },
64
+ });
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coyalabs/bts-style",
3
- "version": "1.3.12",
3
+ "version": "1.3.13",
4
4
  "description": "BTS Theme Svelte component templates",
5
5
  "type": "module",
6
6
  "exports": {
@@ -48,5 +48,9 @@
48
48
  "type": "git",
49
49
  "url": "https://github.com/sparklescoya/svelte-bts-theme.git",
50
50
  "directory": "@bts-theme/svelte-templates"
51
+ },
52
+ "dependencies": {
53
+ "marked": "^17.0.4",
54
+ "sanitize-html": "^2.17.1"
51
55
  }
52
56
  }