paginar 0.1.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 (54) hide show
  1. package/.editorconfig +13 -0
  2. package/.env +1 -0
  3. package/.vscode/extensions.json +3 -0
  4. package/README.md +14 -0
  5. package/demo/slot/slot-chapter1.html +142 -0
  6. package/demo/slot/slot-chapter2.html +359 -0
  7. package/demo/slot/slot-chapter3.html +259 -0
  8. package/demo/summary/_content/1chapter.html +97 -0
  9. package/demo/summary/_content/2chapter.html +311 -0
  10. package/demo/summary/_content/3chapter.html +211 -0
  11. package/demo/summary/index.html +99 -0
  12. package/dist/favicon.ico +0 -0
  13. package/dist/index.es.js +10224 -0
  14. package/dist/index.es.js.map +1 -0
  15. package/dist/style.css +975 -0
  16. package/index.html +18 -0
  17. package/package.json +35 -0
  18. package/postcss.config.js +7 -0
  19. package/public/favicon.ico +0 -0
  20. package/src/App.ce.vue +104 -0
  21. package/src/assets/main.css +100 -0
  22. package/src/components/EngineWrapper.vue +23 -0
  23. package/src/components/FileContent.ce.vue +20 -0
  24. package/src/components/FooterSlot.ce.vue +22 -0
  25. package/src/components/HeaderSlot/HeaderSlot.ce.vue +37 -0
  26. package/src/components/HeaderSlot/OptionsButton/OptionsButton.ce.vue +63 -0
  27. package/src/components/HeaderSlot/OptionsButton/OptionsColor.vue +28 -0
  28. package/src/components/HeaderSlot/OptionsButton/OptionsColumns.vue +41 -0
  29. package/src/components/HeaderSlot/OptionsButton/OptionsDropdown.vue +40 -0
  30. package/src/components/HeaderSlot/OptionsButton/OptionsFontFamily.vue +17 -0
  31. package/src/components/HeaderSlot/OptionsButton/OptionsFontSize.vue +17 -0
  32. package/src/components/HeaderSlot/SummaryButton/SummaryButton.ce.vue +64 -0
  33. package/src/components/HeaderSlot/SummaryButton/SummaryDropdown.vue +29 -0
  34. package/src/components/NavigationButton.ce.vue +34 -0
  35. package/src/components/ReaderWrapper.vue +16 -0
  36. package/src/components/icons/Close.vue +6 -0
  37. package/src/components/icons/DoubleColumn.vue +15 -0
  38. package/src/components/icons/Expand.vue +7 -0
  39. package/src/components/icons/Eye.vue +9 -0
  40. package/src/components/icons/Home.vue +6 -0
  41. package/src/components/icons/Moon.vue +5 -0
  42. package/src/components/icons/SingleColumn.vue +3 -0
  43. package/src/components/icons/Summary.vue +10 -0
  44. package/src/components/icons/Sun.vue +5 -0
  45. package/src/components/icons/ThreeDots.vue +8 -0
  46. package/src/composables/useEstimatePages.js +28 -0
  47. package/src/composables/usePagination.js +57 -0
  48. package/src/composables/useReaderSettings.js +125 -0
  49. package/src/composables/useStyles.js +38 -0
  50. package/src/composables/useTextContent.js +40 -0
  51. package/src/main.js +21 -0
  52. package/src/tailwind.css +975 -0
  53. package/tailwind.config.js +12 -0
  54. package/vite.config.js +32 -0
package/index.html ADDED
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <link rel="icon" href="/favicon.ico" />
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <title>Paginar</title>
9
+ </head>
10
+
11
+ <body>
12
+ <div>
13
+ <p><a href="/demo/summary/index.html">Sumário (arquivos externos)</a></p>
14
+ <p><a href="/demo/slot/slot-chapter1.html">Slot (conteúdo direto na pagina)</a></p>
15
+ </div>
16
+ </body>
17
+
18
+ </html>
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "paginar",
3
+ "author": "Editora Sabiá (https://sabia.pub/)",
4
+ "description": "A flipping page web component for reading long texts on the brower",
5
+ "main": "dist/index.es.js",
6
+ "module": "dist/index.es.js",
7
+ "version": "0.1.0",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/educkf/paginar.git"
11
+ },
12
+ "license": "Apache-2.0",
13
+ "homepage": "https://github.com/educkf/paginar",
14
+ "scripts": {
15
+ "dev": "concurrently \"npm run start:css\" \"npm run start\"",
16
+ "start": "vite",
17
+ "start:css": "tailwindcss -o src/tailwind.css --watch",
18
+ "build": "npm run build:css && vite build",
19
+ "build:css": "tailwindcss -o src/tailwind.css",
20
+ "preview": "vite preview --port 4173"
21
+ },
22
+ "dependencies": {
23
+ "@vueuse/core": "^9.2.0",
24
+ "vue": "^3.2.37"
25
+ },
26
+ "devDependencies": {
27
+ "@vitejs/plugin-vue": "^3.0.1",
28
+ "autoprefixer": "^10.4.8",
29
+ "concurrently": "^7.4.0",
30
+ "postcss": "^8.4.16",
31
+ "sass": "^1.54.2",
32
+ "tailwindcss": "^3.1.8",
33
+ "vite": "^3.0.1"
34
+ }
35
+ }
@@ -0,0 +1,7 @@
1
+ module.exports = {
2
+ plugins: {
3
+ 'tailwindcss/nesting': {},
4
+ tailwindcss: {},
5
+ autoprefixer: {},
6
+ },
7
+ }
Binary file
package/src/App.ce.vue ADDED
@@ -0,0 +1,104 @@
1
+ <script setup>
2
+ import { ref, onMounted, defineProps } from 'vue'
3
+ import { useWindowSize, watchDebounced } from '@vueuse/core'
4
+
5
+ import HeaderSlot from './components/HeaderSlot/HeaderSlot.ce.vue'
6
+ import FileContent from './components/FileContent.ce.vue'
7
+ import EngineWrapper from './components/EngineWrapper.vue'
8
+ import ReaderWrapper from './components/ReaderWrapper.vue'
9
+ import FooterSlot from './components/FooterSlot.ce.vue'
10
+
11
+ import usePagination from './composables/usePagination'
12
+ import useEstimatePages from './composables/useEstimatePages'
13
+ import useTextContent from './composables/useTextContent'
14
+ import useReaderSettings from './composables/useReaderSettings'
15
+ import useStyles from './composables/useStyles'
16
+
17
+ const props = defineProps({
18
+ bookTitle: String,
19
+ bookContent: String,
20
+ readerSettings: String,
21
+ cssFile: String
22
+ })
23
+
24
+ const { initSettings, baseFont, textFont, fontSize, columns, setColumns } = useReaderSettings
25
+ const { currentPage, totalPages, init } = usePagination
26
+ const { content } = useTextContent
27
+ const { width, height } = useWindowSize()
28
+
29
+ const readerComponent = ref(null)
30
+ const contentArea = ref(null)
31
+ const rootComponent = ref(null)
32
+
33
+ onMounted(async () => {
34
+ init(readerComponent, contentArea)
35
+ initSettings(props.readerSettings)
36
+
37
+ if (props.cssFile) {
38
+ useStyles.stylesheetLoader(props.cssFile, rootComponent)
39
+ }
40
+ })
41
+
42
+ watchDebounced(
43
+ [width, height, content, columns, fontSize, textFont],
44
+ () => {
45
+ if (width.value < 1024 && columns.value === 'doubleColumns')
46
+ setColumns('singleColumns')
47
+ else
48
+ useEstimatePages.estimate(readerComponent, contentArea)
49
+ },
50
+ { debounce: 125, maxWait: 250 }
51
+ )
52
+ </script>
53
+
54
+ <template>
55
+ <main ref="rootComponent" :style="`font-family: ${baseFont}`">
56
+ <HeaderSlot>
57
+ <template #header>
58
+ <slot name="header">
59
+ <p class="text-white text-center text-xs">
60
+ {{ props.bookTitle }}
61
+ </p>
62
+ </slot>
63
+ </template>
64
+
65
+ <template #summaryTop>
66
+ <slot name="summaryTop"></slot>
67
+ </template>
68
+
69
+ <template #summaryBottom>
70
+ <slot name="summaryBottom"></slot>
71
+ </template>
72
+
73
+ <template #optionsTop>
74
+ <slot name="optionsTop"></slot>
75
+ </template>
76
+
77
+ <template #optionsBottom>
78
+ <slot name="optionsBottom"></slot>
79
+ </template>
80
+ </HeaderSlot>
81
+
82
+ <EngineWrapper>
83
+ <div id="reader-component" ref="readerComponent">
84
+ <ReaderWrapper>
85
+ <section id="content-area" ref="contentArea"
86
+ :style="`font-family: ${textFont}; font-size: ${fontSize}px`"
87
+ >
88
+ <slot name="content">
89
+ <!-- default when no content slot, and there is a summary -->
90
+ <FileContent :bookContent="bookContent" />
91
+ </slot>
92
+ </section>
93
+ </ReaderWrapper>
94
+ </div>
95
+ </EngineWrapper>
96
+
97
+ <FooterSlot
98
+ :currPage="currentPage"
99
+ :totalPages="totalPages"
100
+ />
101
+ </main>
102
+ </template>
103
+
104
+ <style lang="scss" src="./assets/main.css"></style>
@@ -0,0 +1,100 @@
1
+ header.wrapper {
2
+ width: 200px;
3
+ margin: 0 auto;
4
+ }
5
+
6
+ #engine {
7
+ height: calc(100vh - 7.6rem);
8
+ position: relative;
9
+ right: 0;
10
+ left: 0;
11
+ bottom: 0;
12
+ width: 100%;
13
+
14
+ &.light {
15
+ background-color: #fff7e0
16
+ }
17
+
18
+ &.dark {
19
+ background-color: #2d2d2d;
20
+
21
+ #content-area {
22
+ color: #fff
23
+ }
24
+ }
25
+ }
26
+
27
+ #reader-component {
28
+ width: 100%;
29
+ }
30
+
31
+ .typeArea {
32
+ width: 100%;
33
+ }
34
+
35
+ .engineWrapper {
36
+ height: calc(100% - 2rem);
37
+ display: flex;
38
+ justify-content: space-between;
39
+ position: relative;
40
+ right: 0;
41
+ left: 0;
42
+ bottom: 0;
43
+ width: 100%;
44
+ }
45
+
46
+ .doubleColumns {
47
+ -moz-column-count: 2;
48
+ column-count: 2;
49
+ grid-column-gap: 0;
50
+ -moz-column-gap: 0;
51
+ column-gap: 0;
52
+ height: 100%;
53
+ padding: 0;
54
+ width: 100%;
55
+
56
+ @media only screen and (max-width: 1020px) {
57
+ -moz-column-count: 1;
58
+ column-count: 1;
59
+ }
60
+ }
61
+
62
+ .singleColumns {
63
+ column-count: 1;
64
+ column-gap: 0;
65
+ height: 100%;
66
+ padding: 0;
67
+ width: 100%;
68
+
69
+ #content-area {
70
+ max-width: 37em;
71
+ margin: 0 auto;
72
+ }
73
+ }
74
+
75
+
76
+ .nav-prev {
77
+ @apply left-0;
78
+ }
79
+
80
+ .nav-next {
81
+ @apply right-0;
82
+ }
83
+
84
+ .viewer-nav {
85
+ height: 100%;
86
+
87
+ @apply fixed md:relative z-10 inset-y-0 transition duration-500 ease-in-out self-center w-10 cursor-pointer flex-shrink-0;
88
+
89
+
90
+ &:focus,
91
+ &:active {
92
+ border: none !important;
93
+ box-shadow: none !important;
94
+ outline-color: transparent !important;
95
+ }
96
+ }
97
+
98
+ .viewer-nav-icon {
99
+ @apply opacity-0 md:opacity-80;
100
+ }
@@ -0,0 +1,23 @@
1
+ <script setup>
2
+ import NavigationButton from './NavigationButton.ce.vue'
3
+
4
+ import usePagination from '../composables/usePagination'
5
+ import useReaderSettings from '../composables/useReaderSettings'
6
+
7
+ const { next, prev } = usePagination
8
+ const { mode } = useReaderSettings
9
+
10
+ </script>
11
+
12
+ <template>
13
+ <div id="engine"
14
+ class="pt-10 transition-colors"
15
+ :class="mode"
16
+ >
17
+ <div class="engineWrapper overflow-hidden">
18
+ <NavigationButton target="prev" @clicked="prev()" />
19
+ <slot></slot>
20
+ <NavigationButton target="next" @clicked="next()" />
21
+ </div>
22
+ </div>
23
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup>
2
+ import { defineProps, onMounted } from 'vue'
3
+
4
+ import useTextContent from '../composables/useTextContent'
5
+ const { content, initContent } = useTextContent
6
+
7
+ const props = defineProps({
8
+ bookContent: String
9
+ })
10
+
11
+ onMounted(() => {
12
+ initContent(props.bookContent)
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div>
18
+ <div v-html="content"></div>
19
+ </div>
20
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+ import usePagination from '../composables/usePagination';
3
+ const { totalPages, currentPage, set } = usePagination
4
+ </script>
5
+
6
+ <template>
7
+ <div class="bg-gray-800 flex justify-center items-center p-2 text-white">
8
+ <div>
9
+ <input type="range"
10
+ :min="1"
11
+ :max="totalPages"
12
+ :value="currentPage"
13
+ @input="event => set(Number(event.target.value))"
14
+ >
15
+
16
+ <slot name="footer">
17
+ <span>Página {{ currentPage }} de {{ totalPages }}</span>
18
+ <slot name="default"></slot>
19
+ </slot>
20
+ </div>
21
+ </div>
22
+ </template>
@@ -0,0 +1,37 @@
1
+ <script setup>
2
+ import SummaryButton from './SummaryButton/SummaryButton.ce.vue';
3
+ import OptionsButton from './OptionsButton/OptionsButton.ce.vue';
4
+ </script>
5
+
6
+ <template>
7
+ <div class="bg-gray-800 flex justify-between items-center p-4">
8
+ <summary-button>
9
+ <template #summaryTop>
10
+ <slot name="summaryTop" />
11
+ </template>
12
+ <template #summaryBottom>
13
+ <slot name="summaryBottom" />
14
+ </template>
15
+ </summary-button>
16
+
17
+ <div>
18
+ <slot name="header" />
19
+ </div>
20
+
21
+ <options-button>
22
+ <template #optionsTop>
23
+ <slot name="optionsTop" />
24
+ </template>
25
+ <template #optionsBottom>
26
+ <slot name="optionsBottom" />
27
+ </template>
28
+ </options-button>
29
+ </div>
30
+ </template>
31
+
32
+ <style>
33
+ .headerSlot {
34
+ text-align: center;
35
+ border: 1px dashed #000;
36
+ }
37
+ </style>
@@ -0,0 +1,63 @@
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+ import { onClickOutside, onKeyStroke, useFullscreen } from '@vueuse/core'
4
+
5
+ import IconThreeDots from '../../icons/ThreeDots.vue'
6
+ import IconClose from '../../icons/Close.vue'
7
+ import IconExpand from '../../icons/Expand.vue'
8
+ import OptionsDropdown from './OptionsDropdown.vue'
9
+
10
+ const show = ref(false)
11
+ const button = ref(null)
12
+
13
+ const toggleSummary = () => show.value = !show.value
14
+ const hide = () => show.value = false
15
+
16
+ onClickOutside(button, () => hide())
17
+ onKeyStroke('Escape', () => hide())
18
+
19
+ const { toggle: toggleFullScreen } = useFullscreen()
20
+ </script>
21
+
22
+ <template>
23
+ <div ref="button" class="relative">
24
+ <div class="flex">
25
+ <button @click.prevent="toggleSummary()" class="border p-3 shadow flex items-center" :class="{
26
+ 'border-white text-white': invertBackground || show,
27
+ 'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground && !show,
28
+ 'bg-terra': show
29
+ }" id="summary-menu" aria-haspopup="true" :aria-expanded="show">
30
+ <span class="block mr-3">
31
+ Opções
32
+ </span>
33
+
34
+ <IconClose v-if="show" class="w-6 h-6" />
35
+ <IconThreeDots v-else class="w-6 h-6" />
36
+ </button>
37
+
38
+ <button @click.prevent="toggleFullScreen()" class="hidden md:flex items-center border p-3 shadow ml-3" :class="{
39
+ 'border-white text-white': invertBackground,
40
+ 'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground
41
+ }">
42
+ <IconExpand class="w-6 h-6" />
43
+ </button>
44
+ </div>
45
+
46
+ <transition enter-active-class="transition ease-out duration-100 transform" enter-class="opacity-0 scale-95"
47
+ enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75 transform"
48
+ leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95"
49
+ >
50
+ <OptionsDropdown v-show="show">
51
+ <template #optionsTop>
52
+ <slot name="optionsTop"/>
53
+ </template>
54
+ <template #optionsBottom>
55
+ <slot name="optionsBottom"/>
56
+ </template>
57
+ </OptionsDropdown>
58
+ </transition>
59
+ </div>
60
+ </template>
61
+
62
+ <style>
63
+ </style>
@@ -0,0 +1,28 @@
1
+ <script setup>
2
+ import IconSun from '../../icons/Sun.vue'
3
+ import IconMoon from '../../icons/Moon.vue'
4
+
5
+ import useReaderSettings from '../../../composables/useReaderSettings'
6
+ const { mode, setMode } = useReaderSettings
7
+
8
+ </script>
9
+
10
+ <template>
11
+ <button @click.prevent="setMode('light')"
12
+ title="Leitura diurna"
13
+ class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center"
14
+ :class="{
15
+ 'bg-gray-100 text-gray-800 border-2 border-gray-500': mode === 'light'
16
+ }">
17
+ <IconSun class="w-5 h-5" />
18
+ </button>
19
+
20
+ <button @click.prevent="setMode('dark')"
21
+ title="Leitura noturna"
22
+ class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center"
23
+ :class="{
24
+ 'bg-gray-100 text-gray-800 border-2 border-gray-500': mode === 'dark'
25
+ }">
26
+ <IconMoon class="w-5 h-5" />
27
+ </button>
28
+ </template>
@@ -0,0 +1,41 @@
1
+ <script setup>
2
+ import { useWindowSize } from '@vueuse/core'
3
+ import SingleColumn from '../../icons/SingleColumn.vue'
4
+ import DoubleColumn from '../../icons/DoubleColumn.vue'
5
+
6
+ import useReaderSettings from '../../../composables/useReaderSettings'
7
+ const { columns, setColumns } = useReaderSettings
8
+ const { width } = useWindowSize()
9
+
10
+ </script>
11
+
12
+ <template>
13
+ <div class="w-full hidden md:grid grid-cols-3 gap-2 mt-3">
14
+ <button
15
+ class="col-span-1 text-center cursor-pointer py-3 rounded-sm border"
16
+ :class="{
17
+ 'border-gray-300': columns !== 'singleColumns',
18
+ 'bg-gray-100 text-gray-800 border-2 border-gray-500': columns === 'singleColumns'
19
+ }"
20
+ @click.prevent="setColumns('singleColumns')"
21
+ aria-label="Coluna única"
22
+ title="Coluna única"
23
+ >
24
+ <SingleColumn class="mx-auto h-10 opacity-75" />
25
+ </button>
26
+
27
+ <button
28
+ class="col-span-2 text-center cursor-pointer py-3 rounded-sm border"
29
+ :class="{
30
+ 'border-gray-300': columns !== 'doubleColumns',
31
+ 'bg-gray-100 text-gray-800 border-2 border-gray-500': columns === 'doubleColumns',
32
+ 'opacity-30 bg-gray-300': width < 1024
33
+ }"
34
+ @click.prevent="width >= 1024 && setColumns('doubleColumns')"
35
+ aria-label="Coluna dupla"
36
+ title="Coluna dupla"
37
+ >
38
+ <DoubleColumn name="DoubleColumn" class="mx-auto h-10 divide-opacity-75" />
39
+ </button>
40
+ </div>
41
+ </template>
@@ -0,0 +1,40 @@
1
+ <script setup>
2
+ import IconEye from '../../icons/Eye.vue'
3
+ import OptionsColumns from './OptionsColumns.vue';
4
+ import OptionsFontFamily from './OptionsFontFamily.vue';
5
+ import OptionsFontSize from './OptionsFontSize.vue';
6
+ import OptionsColor from './OptionsColor.vue'
7
+ </script>
8
+
9
+ <template>
10
+ <div class="absolute top-14 py-4 px-3 shadow-lg w-60 md:w-104
11
+ text-areia -right-2 md:right-16 z-10 bg-white
12
+ "
13
+ role="Opções"
14
+ aria-orientation="vertical"
15
+ aria-labelledby="options-menu"
16
+ >
17
+ <div>
18
+
19
+ <slot name="optionsTop" />
20
+
21
+ <div class="w-full flex justify-center items-center py-3">
22
+ <IconEye class="w-6 h-6" />
23
+ <span class="ml-4">Visualização</span>
24
+ </div>
25
+
26
+ <OptionsColumns />
27
+ <OptionsFontFamily />
28
+
29
+ <div class="w-full grid grid-cols-4 gap-2 my-3">
30
+
31
+ <OptionsFontSize />
32
+ <OptionsColor />
33
+ </div>
34
+
35
+ <div class="hidden w-full border-b border-areia my-8"></div>
36
+
37
+ <slot name="optionsBottom" />
38
+ </div>
39
+ </div>
40
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup>
2
+ import useReaderSettings from '../../../composables/useReaderSettings'
3
+ const { textFont, fontsOptions, setTextFont } = useReaderSettings
4
+
5
+ </script>
6
+
7
+ <template>
8
+ <div class="w-full grid grid-cols-4 gap-2 my-3">
9
+ <button v-for="font in fontsOptions" :key="font.name" @click.prevent="setTextFont(font.name)"
10
+ class="font-light text-lg tracking-wide border border-urucum w-full h-10 flex justify-center items-center"
11
+ :class="{
12
+ 'bg-gray-100 text-gray-800 border-2 border-gray-500': textFont === font.name
13
+ }" :style="`font-family: ${font.name}`" :title="font.label ?? font.name">
14
+ Ff
15
+ </button>
16
+ </div>
17
+ </template>
@@ -0,0 +1,17 @@
1
+ <script setup>
2
+ import useReaderSettings from '../../../composables/useReaderSettings'
3
+ const { fontSize, setFontSize } = useReaderSettings
4
+
5
+ </script>
6
+
7
+ <template>
8
+ <button @click.prevent="setFontSize(fontSize + 1)"
9
+ class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center">
10
+ + A
11
+ </button>
12
+
13
+ <button @click.prevent="setFontSize(fontSize - 1)"
14
+ class="font-light text-base border border-urucum w-full h-10 flex justify-center items-center">
15
+ - A
16
+ </button>
17
+ </template>
@@ -0,0 +1,64 @@
1
+ <script setup>
2
+ import { ref } from 'vue'
3
+ import { onClickOutside, onKeyStroke } from '@vueuse/core'
4
+
5
+ import IconHome from '../../icons/Home.vue'
6
+ import IconClose from '../../icons/Close.vue'
7
+ import IconSummary from '../../icons/Summary.vue'
8
+ import SummaryDropdown from './SummaryDropdown.vue'
9
+
10
+ const show = ref(false)
11
+ const button = ref(null)
12
+
13
+ const toggleSummary = () => show.value = !show.value
14
+ const hide = () => show.value = false
15
+
16
+ onClickOutside(button, () => hide())
17
+ onKeyStroke('Escape', () => hide())
18
+ </script>
19
+
20
+ <template>
21
+ <div ref="button" class="relative">
22
+ <div class="flex">
23
+ <a href="/" title="Homepage"
24
+ class="hidden md:flex items-center border p-3 shadow mr-3
25
+ border-gray-800 text-gray-800 dark:border-white dark:text-white"
26
+ >
27
+ <IconHome class="w-6 h-6"/>
28
+ </a>
29
+
30
+ <button
31
+ @click.prevent="toggleSummary()"
32
+ class="border p-3 shadow flex items-center"
33
+ :class="{
34
+ 'border-white text-white': invertBackground || show,
35
+ 'border-gray-800 text-gray-800 dark:border-white dark:text-white': !invertBackground && !show,
36
+ 'bg-terra': show
37
+ }"
38
+ id="summary-menu" aria-haspopup="true" :aria-expanded="show"
39
+ >
40
+ <IconSummary v-if="!show" />
41
+ <IconClose v-else class="w-6 h-6" />
42
+
43
+ <span class="block ml-3">
44
+ Sumário
45
+ </span>
46
+ </button>
47
+ </div>
48
+
49
+ <transition enter-active-class="transition ease-out duration-100 transform" enter-class="opacity-0 scale-95"
50
+ enter-to-class="opacity-100 scale-100" leave-active-class="transition ease-in duration-75 transform"
51
+ leave-class="opacity-100 scale-100" leave-to-class="opacity-0 scale-95"
52
+ >
53
+ <SummaryDropdown v-if="show">
54
+ <template #summaryTop>
55
+ <slot name="summaryTop"/>
56
+ </template>
57
+
58
+ <template #summaryBottom>
59
+ <slot name="summaryBottom"/>
60
+ </template>
61
+ </SummaryDropdown>
62
+ </transition>
63
+ </div>
64
+ </template>
@@ -0,0 +1,29 @@
1
+ <script setup>
2
+ import useTextContent from '../../../composables/useTextContent'
3
+ import usePagination from '../../../composables/usePagination'
4
+ const { summary } = useTextContent
5
+
6
+ function getChapter(item) {
7
+ useTextContent.getContent(item.file)
8
+ usePagination.set(1)
9
+ }
10
+ </script>
11
+
12
+ <template>
13
+ <div class="absolute top-14 py-4 px-3 shadow-lg w-60 md:w-104
14
+ text-areia -left-2 md:left-16 z-10 bg-white" :role="Sumário" aria-orientation="vertical"
15
+ aria-labelledby="summary-menu">
16
+ <nav>
17
+ <slot name="summaryTop" />
18
+
19
+ <component v-for="item in summary" :is="item.link ? 'a' : 'button'" :key="item.link"
20
+ :href="item.link" :title="`Navegar para capítulo ${item.title}`"
21
+ class="w-full text-left block text-black py-2 px-3 hover:bg-gray-100 rounded mb-2"
22
+ @click="item.file ? getChapter(item) : null">
23
+ {{ item.title }}
24
+ </component>
25
+
26
+ <slot name="summaryBottom" />
27
+ </nav>
28
+ </div>
29
+ </template>