@fpkit/acss 0.4.4
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/LICENSE +21 -0
- package/README.md +52 -0
- package/dist/chunk-77CZU5XZ.cjs +9 -0
- package/dist/chunk-77CZU5XZ.cjs.map +1 -0
- package/dist/chunk-D43FJIRQ.cjs +31 -0
- package/dist/chunk-D43FJIRQ.cjs.map +1 -0
- package/dist/chunk-GJWMCDFS.js +9 -0
- package/dist/chunk-GJWMCDFS.js.map +1 -0
- package/dist/chunk-PCDUGD3C.js +5 -0
- package/dist/chunk-PCDUGD3C.js.map +1 -0
- package/dist/hooks.cjs +10 -0
- package/dist/hooks.cjs.map +1 -0
- package/dist/hooks.d.cts +32 -0
- package/dist/hooks.d.ts +32 -0
- package/dist/hooks.js +8 -0
- package/dist/hooks.js.map +1 -0
- package/dist/icon-e6044c73.d.ts +227 -0
- package/dist/icons.cjs +73 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.d.cts +252 -0
- package/dist/icons.d.ts +252 -0
- package/dist/icons.js +4 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.cjs +59 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +566 -0
- package/dist/index.d.ts +566 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/libs/chunk-GCGKYLDG.js +7 -0
- package/libs/chunk-GCGKYLDG.js.map +1 -0
- package/libs/chunk-PDD4N5P5.cjs +10 -0
- package/libs/chunk-PDD4N5P5.cjs.map +1 -0
- package/libs/chunk-QHIABQNQ.js +8 -0
- package/libs/chunk-QHIABQNQ.js.map +1 -0
- package/libs/chunk-ZOHIKF6I.cjs +31 -0
- package/libs/chunk-ZOHIKF6I.cjs.map +1 -0
- package/libs/components/badge/badge.css +1 -0
- package/libs/components/badge/badge.css.map +1 -0
- package/libs/components/badge/badge.min.css +3 -0
- package/libs/components/breadcrumbs/breadcrumb.css +1 -0
- package/libs/components/breadcrumbs/breadcrumb.css.map +1 -0
- package/libs/components/breadcrumbs/breadcrumb.min.css +3 -0
- package/libs/components/buttons/button.css +1 -0
- package/libs/components/buttons/button.css.map +1 -0
- package/libs/components/buttons/button.min.css +3 -0
- package/libs/components/cards/card-style.css +1 -0
- package/libs/components/cards/card-style.css.map +1 -0
- package/libs/components/cards/card-style.min.css +3 -0
- package/libs/components/cards/card.css +1 -0
- package/libs/components/cards/card.css.map +1 -0
- package/libs/components/cards/card.min.css +3 -0
- package/libs/components/details/details.css +1 -0
- package/libs/components/details/details.css.map +1 -0
- package/libs/components/details/details.min.css +3 -0
- package/libs/components/form/form.css +1 -0
- package/libs/components/form/form.css.map +1 -0
- package/libs/components/form/form.min.css +3 -0
- package/libs/components/icons/icon.css +1 -0
- package/libs/components/icons/icon.css.map +1 -0
- package/libs/components/icons/icon.min.css +3 -0
- package/libs/components/images/img.css +1 -0
- package/libs/components/images/img.css.map +1 -0
- package/libs/components/images/img.min.css +3 -0
- package/libs/components/layout/landmarks.css +1 -0
- package/libs/components/layout/landmarks.css.map +1 -0
- package/libs/components/layout/landmarks.min.css +3 -0
- package/libs/components/link/link.css +1 -0
- package/libs/components/link/link.css.map +1 -0
- package/libs/components/link/link.min.css +3 -0
- package/libs/components/nav/nav.css +1 -0
- package/libs/components/nav/nav.css.map +1 -0
- package/libs/components/nav/nav.min.css +3 -0
- package/libs/components/progress/progress.css +1 -0
- package/libs/components/progress/progress.css.map +1 -0
- package/libs/components/progress/progress.min.css +3 -0
- package/libs/components/styles/index.css +1 -0
- package/libs/components/styles/index.css.map +1 -0
- package/libs/components/styles/index.min.css +3 -0
- package/libs/components/tag/tag.css +1 -0
- package/libs/components/tag/tag.css.map +1 -0
- package/libs/components/tag/tag.min.css +3 -0
- package/libs/components/text-to-speech/text-to-speech.css +1 -0
- package/libs/components/text-to-speech/text-to-speech.css.map +1 -0
- package/libs/components/text-to-speech/text-to-speech.min.css +3 -0
- package/libs/hooks.cjs +12 -0
- package/libs/hooks.cjs.map +1 -0
- package/libs/hooks.d.cts +32 -0
- package/libs/hooks.d.ts +32 -0
- package/libs/hooks.js +3 -0
- package/libs/hooks.js.map +1 -0
- package/libs/icons-1f5afc0c.d.ts +318 -0
- package/libs/icons.cjs +12 -0
- package/libs/icons.cjs.map +1 -0
- package/libs/icons.d.cts +2 -0
- package/libs/icons.d.ts +2 -0
- package/libs/icons.js +3 -0
- package/libs/icons.js.map +1 -0
- package/libs/index.cjs +71 -0
- package/libs/index.cjs.map +1 -0
- package/libs/index.css +1 -0
- package/libs/index.css.map +1 -0
- package/libs/index.d.cts +551 -0
- package/libs/index.d.ts +551 -0
- package/libs/index.js +11 -0
- package/libs/index.js.map +1 -0
- package/package.json +125 -0
- package/src/App.css +42 -0
- package/src/App.tsx +35 -0
- package/src/__snapshots__/App.test.tsx.snap +56 -0
- package/src/components/.gitkeep +0 -0
- package/src/components/__snapshots__/fp.test.tsx.snap +3 -0
- package/src/components/badge/badge.scss +20 -0
- package/src/components/badge/badge.stories.tsx +54 -0
- package/src/components/badge/badge.tsx +17 -0
- package/src/components/breadcrumbs/bc-item.tsx +20 -0
- package/src/components/breadcrumbs/breadcrumb.scss +35 -0
- package/src/components/breadcrumbs/breadcrumb.stories.tsx +92 -0
- package/src/components/breadcrumbs/breadcrumb.tsx +218 -0
- package/src/components/buttons/button.scss +115 -0
- package/src/components/buttons/button.stories.tsx +57 -0
- package/src/components/buttons/button.test.tsx +104 -0
- package/src/components/buttons/button.tsx +64 -0
- package/src/components/cards/card-style.scss +0 -0
- package/src/components/cards/card.scss +43 -0
- package/src/components/cards/card.stories.tsx +114 -0
- package/src/components/cards/card.test.tsx +30 -0
- package/src/components/cards/card.tsx +135 -0
- package/src/components/cards/flex-card.tsx +15 -0
- package/src/components/details/details.scss +75 -0
- package/src/components/details/details.stories.tsx +122 -0
- package/src/components/details/details.tsx +77 -0
- package/src/components/form/README.mdx +70 -0
- package/src/components/form/fields.tsx +45 -0
- package/src/components/form/form.scss +87 -0
- package/src/components/form/form.stories.tsx +49 -0
- package/src/components/form/form.tsx +71 -0
- package/src/components/form/input.stories.tsx +155 -0
- package/src/components/form/inputs.tsx +84 -0
- package/src/components/form/select.stories.tsx +38 -0
- package/src/components/form/select.tsx +112 -0
- package/src/components/form/textarea.tsx +87 -0
- package/src/components/fp.test.tsx +56 -0
- package/src/components/fp.tsx +78 -0
- package/src/components/heading/heading.stories.tsx +75 -0
- package/src/components/heading/heading.tsx +27 -0
- package/src/components/icons/components/add.tsx +42 -0
- package/src/components/icons/components/arrow-down.tsx +52 -0
- package/src/components/icons/components/arrow-left.tsx +49 -0
- package/src/components/icons/components/arrow-right.tsx +52 -0
- package/src/components/icons/components/arrow-up.tsx +49 -0
- package/src/components/icons/components/chat.tsx +44 -0
- package/src/components/icons/components/code.tsx +50 -0
- package/src/components/icons/components/copy.tsx +51 -0
- package/src/components/icons/components/down.tsx +33 -0
- package/src/components/icons/components/home.tsx +57 -0
- package/src/components/icons/components/left.tsx +43 -0
- package/src/components/icons/components/minus.tsx +42 -0
- package/src/components/icons/components/pause-solid.tsx +48 -0
- package/src/components/icons/components/pause.tsx +63 -0
- package/src/components/icons/components/play-solid.tsx +44 -0
- package/src/components/icons/components/play.tsx +51 -0
- package/src/components/icons/components/remove.tsx +42 -0
- package/src/components/icons/components/resume-solid.tsx +52 -0
- package/src/components/icons/components/resume.tsx +57 -0
- package/src/components/icons/components/right.tsx +43 -0
- package/src/components/icons/components/star.tsx +38 -0
- package/src/components/icons/components/stop-solid.tsx +44 -0
- package/src/components/icons/components/stop.tsx +54 -0
- package/src/components/icons/components/svg.tsx +44 -0
- package/src/components/icons/components/up.tsx +31 -0
- package/src/components/icons/components/user.tsx +46 -0
- package/src/components/icons/icon.scss +15 -0
- package/src/components/icons/icon.stories.tsx +208 -0
- package/src/components/icons/icon.tsx +100 -0
- package/src/components/icons/index.ts +29 -0
- package/src/components/icons/types.ts +12 -0
- package/src/components/images/README.mdx +43 -0
- package/src/components/images/figure.stories.tsx +34 -0
- package/src/components/images/figure.tsx +44 -0
- package/src/components/images/img.scss +43 -0
- package/src/components/images/img.stories.tsx +24 -0
- package/src/components/images/img.test.tsx +43 -0
- package/src/components/images/img.tsx +93 -0
- package/src/components/images/place-holder.png +0 -0
- package/src/components/kit.tsx +56 -0
- package/src/components/layout/_header.scss +72 -0
- package/src/components/layout/footer.stories.tsx +34 -0
- package/src/components/layout/landmarks.scss +51 -0
- package/src/components/layout/landmarks.stories.tsx +54 -0
- package/src/components/layout/landmarks.tsx +149 -0
- package/src/components/layout/main.stories.tsx +90 -0
- package/src/components/link/link.scss +92 -0
- package/src/components/link/link.stories.tsx +74 -0
- package/src/components/link/link.tsx +48 -0
- package/src/components/list/list.stories.tsx +52 -0
- package/src/components/list/list.tsx +74 -0
- package/src/components/modal/dialog.tsx +50 -0
- package/src/components/modal/modal.tsx +85 -0
- package/src/components/nav/nav.scss +90 -0
- package/src/components/nav/nav.stories.tsx +96 -0
- package/src/components/nav/nav.tsx +76 -0
- package/src/components/popover/node_modules/.vitest/results.json +1 -0
- package/src/components/popover/popover.stories.tsx +31 -0
- package/src/components/popover/popover.test.tsx +39 -0
- package/src/components/popover/popover.tsx +85 -0
- package/src/components/progress/progress.scss +70 -0
- package/src/components/progress/progress.stories.tsx +51 -0
- package/src/components/progress/progress.tsx +82 -0
- package/src/components/readme.stories.mdx +7 -0
- package/src/components/styles/index.css +520 -0
- package/src/components/styles/index.css.map +1 -0
- package/src/components/tables/table-elements.tsx +57 -0
- package/src/components/tables/table.tsx +57 -0
- package/src/components/tag/tag.scss +56 -0
- package/src/components/tag/tag.stories.tsx +39 -0
- package/src/components/tag/tag.tsx +25 -0
- package/src/components/text/text.stories.tsx +67 -0
- package/src/components/text/text.tsx +93 -0
- package/src/components/text-to-speech/README.mdx +192 -0
- package/src/components/text-to-speech/TextInput.tsx +19 -0
- package/src/components/text-to-speech/TextToSpeech.stories.tsx +145 -0
- package/src/components/text-to-speech/TextToSpeech.tsx +94 -0
- package/src/components/text-to-speech/text-to-speech.scss +31 -0
- package/src/components/text-to-speech/useTextToSpeech.mdx +182 -0
- package/src/components/text-to-speech/useTextToSpeech.tsx +176 -0
- package/src/components/text-to-speech/views/TextToSpeechControls.tsx +117 -0
- package/src/components/ui.tsx +67 -0
- package/src/favicon.svg +15 -0
- package/src/hooks/popover/__snapshots__/popover.test.tsx.snap +88 -0
- package/src/hooks/popover/node_modules/.vitest/results.json +1 -0
- package/src/hooks/popover/popover.tsx +71 -0
- package/src/hooks/popover/use-popover.tsx +83 -0
- package/src/hooks.ts +1 -0
- package/src/icons.ts +1 -0
- package/src/index.css +13 -0
- package/src/index.scss +19 -0
- package/src/index.ts +35 -0
- package/src/libs/content.ts +30 -0
- package/src/logo.svg +7 -0
- package/src/main.tsx +10 -0
- package/src/patterns/.gitkeep +0 -0
- package/src/patterns/page/page-header.stories.tsx +44 -0
- package/src/patterns/page/page-header.tsx +78 -0
- package/src/sass/_elements.scss +17 -0
- package/src/sass/_globals.scss +162 -0
- package/src/sass/_layout.scss +51 -0
- package/src/sass/_loading-animation.scss +35 -0
- package/src/sass/_mixins.scss +10 -0
- package/src/sass/_properties.scss +106 -0
- package/src/sass/_reset.scss +183 -0
- package/src/sass/_type.scss +43 -0
- package/src/setupTest.ts +1 -0
- package/src/styles/badge/badge.css +22 -0
- package/src/styles/badge/badge.css.map +1 -0
- package/src/styles/breadcrumbs/breadcrumb.css +42 -0
- package/src/styles/breadcrumbs/breadcrumb.css.map +1 -0
- package/src/styles/buttons/button.css +93 -0
- package/src/styles/buttons/button.css.map +1 -0
- package/src/styles/cards/card-style.css +3 -0
- package/src/styles/cards/card-style.css.map +1 -0
- package/src/styles/cards/card.css +48 -0
- package/src/styles/cards/card.css.map +1 -0
- package/src/styles/details/details.css +69 -0
- package/src/styles/details/details.css.map +1 -0
- package/src/styles/dropdowns/dropdown.css.map +1 -0
- package/src/styles/form/form.css +93 -0
- package/src/styles/form/form.css.map +1 -0
- package/src/styles/form/style.css.map +1 -0
- package/src/styles/icons/icon.css +16 -0
- package/src/styles/icons/icon.css.map +1 -0
- package/src/styles/images/img.css +42 -0
- package/src/styles/images/img.css.map +1 -0
- package/src/styles/index.css +1330 -0
- package/src/styles/index.css.map +1 -0
- package/src/styles/layout/landmarks.css +155 -0
- package/src/styles/layout/landmarks.css.map +1 -0
- package/src/styles/link/link.css +88 -0
- package/src/styles/link/link.css.map +1 -0
- package/src/styles/nav/nav.css +85 -0
- package/src/styles/nav/nav.css.map +1 -0
- package/src/styles/progress/progress.css +54 -0
- package/src/styles/progress/progress.css.map +1 -0
- package/src/styles/progress/sass/progress.css.map +1 -0
- package/src/styles/styles/index.css +562 -0
- package/src/styles/styles/index.css.map +1 -0
- package/src/styles/tag/badge.css.map +1 -0
- package/src/styles/tag/tag.css +71 -0
- package/src/styles/tag/tag.css.map +1 -0
- package/src/styles/text-to-speech/text-to-speech.css +32 -0
- package/src/styles/text-to-speech/text-to-speech.css.map +1 -0
- package/src/test/setup.ts +6 -0
- package/src/types/component-props.ts +36 -0
- package/src/types/index.ts +2 -0
- package/src/types/input-props.ts +28 -0
- package/src/types/shared.ts +57 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StoryObj, Meta } from '@storybook/react'
|
|
2
|
+
import { within, userEvent, screen } from '@storybook/testing-library'
|
|
3
|
+
import { expect } from '@storybook/jest'
|
|
4
|
+
|
|
5
|
+
import Tag from './tag'
|
|
6
|
+
import './tag.scss'
|
|
7
|
+
|
|
8
|
+
const meta: Meta<typeof Tag> = {
|
|
9
|
+
title: 'FP.React Components/Tag',
|
|
10
|
+
component: Tag,
|
|
11
|
+
args: {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
children: 'Basic Tag',
|
|
14
|
+
// styles: Tag.styles,
|
|
15
|
+
},
|
|
16
|
+
} as Meta
|
|
17
|
+
|
|
18
|
+
export default meta
|
|
19
|
+
type Story = StoryObj<typeof Tag>
|
|
20
|
+
|
|
21
|
+
export const TagComponent: Story = {
|
|
22
|
+
args: {},
|
|
23
|
+
play: async ({ canvasElement }) => {
|
|
24
|
+
const canvas = within(canvasElement)
|
|
25
|
+
expect(canvas.queryByText(/basic badge/i)).toBeInTheDocument()
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const Beta: Story = {
|
|
30
|
+
args: {
|
|
31
|
+
'data-tag': 'beta',
|
|
32
|
+
},
|
|
33
|
+
} as Story
|
|
34
|
+
|
|
35
|
+
export const Production: Story = {
|
|
36
|
+
args: {
|
|
37
|
+
'data-tag': 'production',
|
|
38
|
+
},
|
|
39
|
+
} as Story
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import UI from '#components/ui'
|
|
3
|
+
|
|
4
|
+
export type TagProps = {
|
|
5
|
+
/** HTML element to display the badge as span or p */
|
|
6
|
+
elm?: 'span' | 'p'
|
|
7
|
+
/** Aria role for the component - conditional */
|
|
8
|
+
role: 'note' | 'status'
|
|
9
|
+
} & React.ComponentProps<typeof UI>
|
|
10
|
+
|
|
11
|
+
export const Tag = ({
|
|
12
|
+
elm = 'span',
|
|
13
|
+
role = 'note',
|
|
14
|
+
children,
|
|
15
|
+
styles,
|
|
16
|
+
...props
|
|
17
|
+
}: TagProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<UI as={elm} role={role} styles={styles} {...props}>
|
|
20
|
+
{children}
|
|
21
|
+
</UI>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
export default Tag
|
|
25
|
+
Tag.displayName = 'Tag'
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { StoryObj, Meta } from '@storybook/react'
|
|
2
|
+
import { within, userEvent, screen } from '@storybook/testing-library'
|
|
3
|
+
import { expect } from '@storybook/jest'
|
|
4
|
+
|
|
5
|
+
import Text from './text'
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof Text> = {
|
|
8
|
+
title: 'FP.REACT Components/Text',
|
|
9
|
+
component: Text,
|
|
10
|
+
args: {
|
|
11
|
+
// @ts-ignore
|
|
12
|
+
children:
|
|
13
|
+
'Exercitation non voluptate fugiat amet dolor tempor consectetur. Eu esse adipisicing laboris duis et velit in quis et sunt pariatur tempor laborum nisi. Et id amet ullamco culpa irure nulla esse dolore velit esse.',
|
|
14
|
+
},
|
|
15
|
+
} as Story
|
|
16
|
+
|
|
17
|
+
export default meta
|
|
18
|
+
type Story = StoryObj<typeof Text>
|
|
19
|
+
|
|
20
|
+
export const ParagraphText: Story = {
|
|
21
|
+
args: {},
|
|
22
|
+
play: async ({ canvasElement }) => {
|
|
23
|
+
const canvas = within(canvasElement)
|
|
24
|
+
expect(canvas.getByText(/link/i)).toBeInTheDocument()
|
|
25
|
+
},
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const span: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
as: 'span',
|
|
31
|
+
children: 'Heading Text',
|
|
32
|
+
},
|
|
33
|
+
play: async ({ canvasElement }) => {
|
|
34
|
+
const canvas = within(canvasElement)
|
|
35
|
+
expect(canvas.getByText(/heading/i)).toBeInTheDocument()
|
|
36
|
+
},
|
|
37
|
+
} as Story
|
|
38
|
+
|
|
39
|
+
export const Blockquote: Story = {
|
|
40
|
+
args: {
|
|
41
|
+
as: 'blockquote',
|
|
42
|
+
children:
|
|
43
|
+
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.',
|
|
44
|
+
},
|
|
45
|
+
} as Story
|
|
46
|
+
|
|
47
|
+
export const Strong: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
as: 'strong',
|
|
50
|
+
children: 'Emphasis Text',
|
|
51
|
+
},
|
|
52
|
+
play: async ({ canvasElement }) => {
|
|
53
|
+
const canvas = within(canvasElement)
|
|
54
|
+
expect(canvas.getByText(/emphasis/i)).toBeInTheDocument()
|
|
55
|
+
},
|
|
56
|
+
} as Story
|
|
57
|
+
|
|
58
|
+
export const Code: Story = {
|
|
59
|
+
args: {
|
|
60
|
+
as: 'code',
|
|
61
|
+
children: 'Code Text',
|
|
62
|
+
},
|
|
63
|
+
play: async ({ canvasElement }) => {
|
|
64
|
+
const canvas = within(canvasElement)
|
|
65
|
+
expect(canvas.getByText(/code/i)).toBeInTheDocument()
|
|
66
|
+
},
|
|
67
|
+
} as Story
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// import FP from '../fp'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import UI from '#components/ui'
|
|
4
|
+
|
|
5
|
+
type InheritedProps = React.ComponentProps<typeof UI>
|
|
6
|
+
|
|
7
|
+
type TextElements =
|
|
8
|
+
| 'a'
|
|
9
|
+
| 'b'
|
|
10
|
+
| 'blockquote'
|
|
11
|
+
| 'b'
|
|
12
|
+
| 'blockquote'
|
|
13
|
+
| 'cite'
|
|
14
|
+
| 'code'
|
|
15
|
+
| 'em'
|
|
16
|
+
| 'i'
|
|
17
|
+
| 'em'
|
|
18
|
+
| 'i'
|
|
19
|
+
| 'kbd'
|
|
20
|
+
| 'mark'
|
|
21
|
+
| 'p'
|
|
22
|
+
| 's'
|
|
23
|
+
| 'small'
|
|
24
|
+
| 'span'
|
|
25
|
+
| 'span'
|
|
26
|
+
| 'strong'
|
|
27
|
+
| 'mark'
|
|
28
|
+
| 'p'
|
|
29
|
+
| 's'
|
|
30
|
+
| 'small'
|
|
31
|
+
| 'span'
|
|
32
|
+
| 'span'
|
|
33
|
+
| 'strong'
|
|
34
|
+
| 'sub'
|
|
35
|
+
| 'sup'
|
|
36
|
+
| 'time'
|
|
37
|
+
| 'time'
|
|
38
|
+
| 'u'
|
|
39
|
+
|
|
40
|
+
export type TextProps = {
|
|
41
|
+
/**
|
|
42
|
+
* Text element to to use
|
|
43
|
+
* Text element to to use
|
|
44
|
+
*/
|
|
45
|
+
elm?: TextElements
|
|
46
|
+
/** Pass a text element or string */
|
|
47
|
+
text?: string
|
|
48
|
+
} & InheritedProps
|
|
49
|
+
|
|
50
|
+
export const Text = ({
|
|
51
|
+
elm = 'p',
|
|
52
|
+
id,
|
|
53
|
+
text,
|
|
54
|
+
styles,
|
|
55
|
+
classes,
|
|
56
|
+
children,
|
|
57
|
+
...props
|
|
58
|
+
}: TextProps) => {
|
|
59
|
+
return (
|
|
60
|
+
<UI as={elm} id={id} styles={styles} className={classes} {...props}>
|
|
61
|
+
<UI as={elm} id={id} styles={styles} className={classes} {...props}>
|
|
62
|
+
{children || text}
|
|
63
|
+
</UI>
|
|
64
|
+
</UI>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type TitleProps = {
|
|
69
|
+
/**
|
|
70
|
+
* HTML headings
|
|
71
|
+
*/
|
|
72
|
+
elm?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
|
|
73
|
+
} & InheritedProps
|
|
74
|
+
|
|
75
|
+
export const Title = ({
|
|
76
|
+
elm = 'h3',
|
|
77
|
+
id,
|
|
78
|
+
children,
|
|
79
|
+
styles,
|
|
80
|
+
classes,
|
|
81
|
+
...props
|
|
82
|
+
}: TitleProps) => {
|
|
83
|
+
return (
|
|
84
|
+
<Text as={elm} id={id} styles={styles} className={classes} {...props}>
|
|
85
|
+
{children}
|
|
86
|
+
</Text>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export default Text
|
|
91
|
+
|
|
92
|
+
Text.displayName = 'Text'
|
|
93
|
+
Title.displayName = 'Title'
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Text-to-Speech Components
|
|
2
|
+
|
|
3
|
+
This folder contains components and hooks related to text-to-speech functionality in the FPKit React library.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
|
|
7
|
+
1. `useTextToSpeech.tsx`: A custom React hook that provides text-to-speech functionality.
|
|
8
|
+
2. `TextToSpeech.tsx`: A React component that implements the text-to-speech functionality using the `useTextToSpeech` hook.
|
|
9
|
+
3. `TextToSpeech.stories.tsx`: Storybook stories for the TextToSpeech component.
|
|
10
|
+
4. `useTextToSpeech.mdx`: Documentation for the `useTextToSpeech` hook.
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### useTextToSpeech Hook
|
|
15
|
+
|
|
16
|
+
The `useTextToSpeech` hook provides a simple way to add text-to-speech capabilities to your React components.
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import { useTextToSpeech } from '@fpkit/react';
|
|
20
|
+
|
|
21
|
+
function MyComponent() {
|
|
22
|
+
const { speak, pause, resume, cancel } = useTextToSpeech();
|
|
23
|
+
|
|
24
|
+
const handleSpeak = () => {
|
|
25
|
+
speak('Hello, world!');
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<button onClick={handleSpeak}>
|
|
30
|
+
Speak
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
## Example 2
|
|
38
|
+
|
|
39
|
+
Here's a detailed example of how to use the `useTextToSpeech` hook in a React component:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import React, { useState, useEffect } from 'react';
|
|
43
|
+
import { useTextToSpeech } from '@fpkit/react';
|
|
44
|
+
|
|
45
|
+
const TextToSpeechExample: React.FC = () => {
|
|
46
|
+
const [text, setText] = useState('Welcome to the text-to-speech example!');
|
|
47
|
+
const [rate, setRate] = useState(1);
|
|
48
|
+
const [pitch, setPitch] = useState(1);
|
|
49
|
+
const [volume, setVolume] = useState(1);
|
|
50
|
+
|
|
51
|
+
const {
|
|
52
|
+
speak,
|
|
53
|
+
pause,
|
|
54
|
+
resume,
|
|
55
|
+
cancel,
|
|
56
|
+
isSpeaking,
|
|
57
|
+
isPaused,
|
|
58
|
+
error
|
|
59
|
+
} = useTextToSpeech({ rate, pitch, volume });
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (error) {
|
|
63
|
+
console.error('Text-to-speech error:', error);
|
|
64
|
+
}
|
|
65
|
+
}, [error]);
|
|
66
|
+
|
|
67
|
+
const handleSpeak = () => {
|
|
68
|
+
speak(text);
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div>
|
|
73
|
+
<h2>Text-to-Speech Example</h2>
|
|
74
|
+
<textarea
|
|
75
|
+
value={text}
|
|
76
|
+
onChange={(e) => setText(e.target.value)}
|
|
77
|
+
rows={4}
|
|
78
|
+
cols={50}
|
|
79
|
+
/>
|
|
80
|
+
<div>
|
|
81
|
+
<label>
|
|
82
|
+
Rate:
|
|
83
|
+
<input
|
|
84
|
+
type="range"
|
|
85
|
+
min="0.1"
|
|
86
|
+
max="10"
|
|
87
|
+
step="0.1"
|
|
88
|
+
value={rate}
|
|
89
|
+
onChange={(e) => setRate(parseFloat(e.target.value))}
|
|
90
|
+
/>
|
|
91
|
+
{rate}
|
|
92
|
+
</label>
|
|
93
|
+
</div>
|
|
94
|
+
<div>
|
|
95
|
+
<label>
|
|
96
|
+
Pitch:
|
|
97
|
+
<input
|
|
98
|
+
type="range"
|
|
99
|
+
min="0"
|
|
100
|
+
max="2"
|
|
101
|
+
step="0.1"
|
|
102
|
+
value={pitch}
|
|
103
|
+
onChange={(e) => setPitch(parseFloat(e.target.value))}
|
|
104
|
+
/>
|
|
105
|
+
{pitch}
|
|
106
|
+
</label>
|
|
107
|
+
</div>
|
|
108
|
+
<div>
|
|
109
|
+
<label>
|
|
110
|
+
Volume:
|
|
111
|
+
<input
|
|
112
|
+
type="range"
|
|
113
|
+
min="0"
|
|
114
|
+
max="1"
|
|
115
|
+
step="0.1"
|
|
116
|
+
value={volume}
|
|
117
|
+
onChange={(e) => setVolume(parseFloat(e.target.value))}
|
|
118
|
+
/>
|
|
119
|
+
{volume}
|
|
120
|
+
</label>
|
|
121
|
+
</div>
|
|
122
|
+
<div>
|
|
123
|
+
<button onClick={handleSpeak} disabled={isSpeaking && !isPaused}>
|
|
124
|
+
Speak
|
|
125
|
+
</button>
|
|
126
|
+
<button onClick={pause} disabled={!isSpeaking || isPaused}>
|
|
127
|
+
Pause
|
|
128
|
+
</button>
|
|
129
|
+
<button onClick={resume} disabled={!isPaused}>
|
|
130
|
+
Resume
|
|
131
|
+
</button>
|
|
132
|
+
<button onClick={cancel} disabled={!isSpeaking && !isPaused}>
|
|
133
|
+
Cancel
|
|
134
|
+
</button>
|
|
135
|
+
</div>
|
|
136
|
+
<div>
|
|
137
|
+
Status: {isSpeaking ? (isPaused ? 'Paused' : 'Speaking') : 'Idle'}
|
|
138
|
+
</div>
|
|
139
|
+
{error && <div style={{ color: 'red' }}>Error: {error.message}</div>}
|
|
140
|
+
</div>
|
|
141
|
+
);
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export default TextToSpeechExample;
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This example demonstrates:
|
|
148
|
+
|
|
149
|
+
1. Using all the functions and state variables returned by the hook.
|
|
150
|
+
2. Allowing users to input custom text.
|
|
151
|
+
3. Providing controls for rate, pitch, and volume.
|
|
152
|
+
4. Displaying the current speaking status.
|
|
153
|
+
5. Handling and displaying errors.
|
|
154
|
+
6. Disabling buttons based on the current state.
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
For more detailed information on the `useTextToSpeech` hook, refer to the `useTextToSpeech.mdx` file.
|
|
159
|
+
|
|
160
|
+
### TextToSpeech Component
|
|
161
|
+
|
|
162
|
+
The `TextToSpeech` component provides a ready-to-use implementation of text-to-speech functionality.
|
|
163
|
+
|
|
164
|
+
```tsx
|
|
165
|
+
import { TextToSpeech } from '@fpkit/react';
|
|
166
|
+
|
|
167
|
+
function MyComponent() {
|
|
168
|
+
return (
|
|
169
|
+
<TextToSpeech text="Hello, world!" />
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Documentation
|
|
175
|
+
|
|
176
|
+
For comprehensive documentation on the `useTextToSpeech` hook, including API details and usage examples, please refer to the `useTextToSpeech.mdx` file in this folder.
|
|
177
|
+
|
|
178
|
+
## Storybook
|
|
179
|
+
|
|
180
|
+
The `TextToSpeech.stories.tsx` file contains Storybook stories that demonstrate the usage and various configurations of the TextToSpeech component. You can run Storybook to interact with these components in isolation and see how they behave with different props.
|
|
181
|
+
|
|
182
|
+
## Contributing
|
|
183
|
+
|
|
184
|
+
When contributing to this folder, please ensure that:
|
|
185
|
+
|
|
186
|
+
1. Any new functionality is properly documented in the respective MDX files.
|
|
187
|
+
2. Storybook stories are updated or added for new features or components.
|
|
188
|
+
3. The README is kept up-to-date with any significant changes.
|
|
189
|
+
|
|
190
|
+
## Testing
|
|
191
|
+
|
|
192
|
+
Ensure that all components and hooks in this folder have appropriate unit tests. Run the test suite before submitting any pull requests.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React, { ChangeEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
interface TextInputProps {
|
|
4
|
+
value: string;
|
|
5
|
+
onChange: (e: ChangeEvent<HTMLTextAreaElement>) => void;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const TextInput: React.FC<TextInputProps> = ({ value, onChange }) => {
|
|
9
|
+
return (
|
|
10
|
+
<textarea
|
|
11
|
+
value={value}
|
|
12
|
+
onChange={onChange}
|
|
13
|
+
placeholder="Enter text to speak"
|
|
14
|
+
style={{ width: '100%', height: '100px', marginBottom: '10px' }}
|
|
15
|
+
/>
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default TextInput;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { Meta, StoryObj } from '@storybook/react'
|
|
3
|
+
import TextToSpeech from './TextToSpeech'
|
|
4
|
+
import './text-to-speech.scss'
|
|
5
|
+
|
|
6
|
+
const meta: Meta<typeof TextToSpeech> = {
|
|
7
|
+
title: 'FP.REACT Components/TextToSpeech',
|
|
8
|
+
component: TextToSpeech,
|
|
9
|
+
tags: ['autodocs'],
|
|
10
|
+
argTypes: {
|
|
11
|
+
voice: {
|
|
12
|
+
control: {
|
|
13
|
+
type: 'select',
|
|
14
|
+
options: [
|
|
15
|
+
'Google US English',
|
|
16
|
+
'Google UK English Female',
|
|
17
|
+
'Google UK English Male',
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
} as Story
|
|
23
|
+
|
|
24
|
+
export default meta
|
|
25
|
+
|
|
26
|
+
type Story = StoryObj<typeof TextToSpeech>
|
|
27
|
+
|
|
28
|
+
export const Default: Story = {
|
|
29
|
+
args: {
|
|
30
|
+
label: 'Read post',
|
|
31
|
+
initialText:
|
|
32
|
+
'Hello, how are you? I am a Text to Speech (TTS) assistant! Update this text and click the [speak] button to hear the change.',
|
|
33
|
+
},
|
|
34
|
+
parameters: {
|
|
35
|
+
docs: {
|
|
36
|
+
description: {
|
|
37
|
+
story:
|
|
38
|
+
"This story demonstrates the TextToSpeechComponent with predefined text and hidden text input. It's useful for scenarios where you want to display the component with a fixed text, without allowing the user to modify it.",
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
} as Story
|
|
43
|
+
|
|
44
|
+
export const WithText: Story = {
|
|
45
|
+
args: {
|
|
46
|
+
// Add props to simulate text input
|
|
47
|
+
initialText:
|
|
48
|
+
'This is a test of the text to speech component. Here the test is passed as a prop and the text input is hidden.',
|
|
49
|
+
showTextInput: false,
|
|
50
|
+
},
|
|
51
|
+
parameters: {
|
|
52
|
+
docs: {
|
|
53
|
+
description: {
|
|
54
|
+
story:
|
|
55
|
+
"This story demonstrates the TextToSpeechComponent with predefined text and hidden text input. It's useful for scenarios where you want to display the component with a fixed text, without allowing the user to modify it.",
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
} as Story
|
|
60
|
+
|
|
61
|
+
export const FullFeature: Story = {
|
|
62
|
+
args: {
|
|
63
|
+
initialText:
|
|
64
|
+
'Welcome to the full-featured Text-to-Speech component. You can modify this text, choose a voice, adjust pitch and rate, and then click the speak button to hear it.',
|
|
65
|
+
showTextInput: true,
|
|
66
|
+
pitch: 1,
|
|
67
|
+
rate: 1,
|
|
68
|
+
},
|
|
69
|
+
parameters: {
|
|
70
|
+
docs: {
|
|
71
|
+
description: {
|
|
72
|
+
story:
|
|
73
|
+
"This story showcases all features of the TextToSpeechComponent, including text input, voice selection, pitch and rate adjustment. It provides a comprehensive demonstration of the component's capabilities.",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
render: (args) => {
|
|
78
|
+
const [selectedVoice, setSelectedVoice] = React.useState<
|
|
79
|
+
SpeechSynthesisVoice | undefined
|
|
80
|
+
>(undefined)
|
|
81
|
+
const [pitch, setPitch] = React.useState(1)
|
|
82
|
+
const [rate, setRate] = React.useState(1)
|
|
83
|
+
|
|
84
|
+
React.useEffect(() => {
|
|
85
|
+
const voices = window.speechSynthesis.getVoices()
|
|
86
|
+
setSelectedVoice(
|
|
87
|
+
voices.find((voice) => voice.name === 'Google US English') || voices[0],
|
|
88
|
+
)
|
|
89
|
+
}, [])
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div>
|
|
93
|
+
<TextToSpeech
|
|
94
|
+
{...args}
|
|
95
|
+
voice={selectedVoice}
|
|
96
|
+
pitch={pitch}
|
|
97
|
+
rate={rate}
|
|
98
|
+
/>
|
|
99
|
+
<div style={{ marginTop: '20px' }}>
|
|
100
|
+
<label htmlFor="voice-select">Select Voice: </label>
|
|
101
|
+
<select
|
|
102
|
+
id="voice-select"
|
|
103
|
+
onChange={(e) =>
|
|
104
|
+
setSelectedVoice(
|
|
105
|
+
window.speechSynthesis
|
|
106
|
+
.getVoices()
|
|
107
|
+
.find((v) => v.name === e.target.value),
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
>
|
|
111
|
+
{window.speechSynthesis.getVoices().map((voice) => (
|
|
112
|
+
<option key={voice.name} value={voice.name}>
|
|
113
|
+
{voice.name} ({voice.lang})
|
|
114
|
+
</option>
|
|
115
|
+
))}
|
|
116
|
+
</select>
|
|
117
|
+
</div>
|
|
118
|
+
<div style={{ marginTop: '10px' }}>
|
|
119
|
+
<label htmlFor="pitch-range">Pitch: {pitch}</label>
|
|
120
|
+
<input
|
|
121
|
+
id="pitch-range"
|
|
122
|
+
type="range"
|
|
123
|
+
min="0.5"
|
|
124
|
+
max="2"
|
|
125
|
+
step="0.1"
|
|
126
|
+
value={pitch}
|
|
127
|
+
onChange={(e) => setPitch(parseFloat(e.target.value))}
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
<div style={{ marginTop: '10px' }}>
|
|
131
|
+
<label htmlFor="rate-range">Rate: {rate}</label>
|
|
132
|
+
<input
|
|
133
|
+
id="rate-range"
|
|
134
|
+
type="range"
|
|
135
|
+
min="0.5"
|
|
136
|
+
max="2"
|
|
137
|
+
step="0.1"
|
|
138
|
+
value={rate}
|
|
139
|
+
onChange={(e) => setRate(parseFloat(e.target.value))}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
},
|
|
145
|
+
} as Story
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React, { useState, ChangeEvent, useEffect } from 'react'
|
|
2
|
+
import { useTextToSpeech } from './useTextToSpeech'
|
|
3
|
+
import Textarea from '#components/form/textarea.jsx'
|
|
4
|
+
import TextToSpeechControls from './views/TextToSpeechControls'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Props for the TextToSpeechComponent.
|
|
8
|
+
* @interface TextToSpeechComponentProps
|
|
9
|
+
*/
|
|
10
|
+
interface TextToSpeechComponentProps {
|
|
11
|
+
/** Initial text to be spoken. Defaults to an empty string. */
|
|
12
|
+
initialText?: string
|
|
13
|
+
/** Whether to show the text input field. Defaults to true. */
|
|
14
|
+
showTextInput?: boolean
|
|
15
|
+
/** The voice to be used for speech synthesis. */
|
|
16
|
+
voice?: SpeechSynthesisVoice | undefined
|
|
17
|
+
/** The pitch of the voice. Defaults to 1. */
|
|
18
|
+
pitch?: number
|
|
19
|
+
/** The rate of speech. Defaults to 1. */
|
|
20
|
+
rate?: number
|
|
21
|
+
/** The language to be used for speech synthesis. */
|
|
22
|
+
language?: string
|
|
23
|
+
/** Player label */
|
|
24
|
+
label?: string | React.ReactNode
|
|
25
|
+
/** Callback function to be called when speech ends. */
|
|
26
|
+
onEnd?: () => void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* A component that converts text to speech using the Web Speech API.
|
|
31
|
+
* @param {TextToSpeechComponentProps} props - The props for the component.
|
|
32
|
+
* @returns {JSX.Element} The rendered TextToSpeechComponent.
|
|
33
|
+
*/
|
|
34
|
+
export const TextToSpeech: React.FC<TextToSpeechComponentProps> = ({
|
|
35
|
+
initialText = '',
|
|
36
|
+
showTextInput = false,
|
|
37
|
+
voice,
|
|
38
|
+
pitch = 1,
|
|
39
|
+
rate = 1,
|
|
40
|
+
language,
|
|
41
|
+
label,
|
|
42
|
+
onEnd,
|
|
43
|
+
}) => {
|
|
44
|
+
const {
|
|
45
|
+
speak,
|
|
46
|
+
pause,
|
|
47
|
+
resume,
|
|
48
|
+
cancel,
|
|
49
|
+
isSpeaking,
|
|
50
|
+
isPaused,
|
|
51
|
+
getAvailableLanguages,
|
|
52
|
+
availableVoices,
|
|
53
|
+
} = useTextToSpeech()
|
|
54
|
+
const [text, setText] = useState<string>(initialText)
|
|
55
|
+
console.log(getAvailableLanguages())
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
setText(initialText)
|
|
59
|
+
}, [initialText])
|
|
60
|
+
|
|
61
|
+
const handleSpeak = (): void => {
|
|
62
|
+
if (text.trim() !== '') {
|
|
63
|
+
speak(text, { voice, pitch, rate }, handleEnd)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
|
|
68
|
+
setText(e.target.value)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const handleEnd = (): void => {
|
|
72
|
+
if (onEnd) {
|
|
73
|
+
onEnd()
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return (
|
|
78
|
+
<>
|
|
79
|
+
{showTextInput && <Textarea value={text} onChange={handleChange} />}
|
|
80
|
+
<TextToSpeechControls
|
|
81
|
+
label={label}
|
|
82
|
+
isSpeaking={isSpeaking}
|
|
83
|
+
isPaused={isPaused}
|
|
84
|
+
onSpeak={handleSpeak}
|
|
85
|
+
onPause={pause}
|
|
86
|
+
onResume={resume}
|
|
87
|
+
onCancel={cancel}
|
|
88
|
+
/>
|
|
89
|
+
</>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default TextToSpeech
|
|
94
|
+
TextToSpeech.displayName = 'TextToSpeechComponent'
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[data-tts] {
|
|
2
|
+
--tts-gap: 0.5rem;
|
|
3
|
+
--tts-bg: #fff;
|
|
4
|
+
--tts-border-width: 0.125rem;
|
|
5
|
+
--tts-border-style: solid;
|
|
6
|
+
--tts-border-color: currentColor;
|
|
7
|
+
--tts-radius: 99rem;
|
|
8
|
+
--tts-padding: 0.5rem;
|
|
9
|
+
--tts-min-width: 20.3125rem;
|
|
10
|
+
--tts-align-items: center;
|
|
11
|
+
--tts-justify-content: center;
|
|
12
|
+
|
|
13
|
+
display: flex;
|
|
14
|
+
gap: var(--tts-gap);
|
|
15
|
+
align-items: var(--tts-align-items);
|
|
16
|
+
justify-content: var(--tts-justify-content);
|
|
17
|
+
background-color: var(--tts-bg);
|
|
18
|
+
border: none;
|
|
19
|
+
outline: var(--tts-border-width) var(--tts-border-style)
|
|
20
|
+
var(--tts-border-color);
|
|
21
|
+
border-radius: var(--tts-radius);
|
|
22
|
+
padding: var(--tts-padding);
|
|
23
|
+
min-width: var(--tts-min-width);
|
|
24
|
+
button[data-btn~='tts-btn'],
|
|
25
|
+
.tts-border {
|
|
26
|
+
outline: none;
|
|
27
|
+
&:hover {
|
|
28
|
+
outline: var(--tts-border-width) var(--tts-border-style);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|