diff --git a/@codexteam/ui/package.json b/@codexteam/ui/package.json index 70c7ea3f..1f69aaa1 100644 --- a/@codexteam/ui/package.json +++ b/@codexteam/ui/package.json @@ -54,6 +54,7 @@ }, "devDependencies": { "@codexteam/icons": "^0.3.3", + "@csstools/postcss-global-data": "^2.1.1", "@editorjs/header": "^2.8.6", "@editorjs/nested-list": "^1.4.2", "@types/node": "^20.11.15", diff --git a/@codexteam/ui/postcss.config.js b/@codexteam/ui/postcss.config.js index 6f1ee2c6..8333ef19 100644 --- a/@codexteam/ui/postcss.config.js +++ b/@codexteam/ui/postcss.config.js @@ -1,3 +1,5 @@ +import { fileURLToPath } from 'url'; +import postcssGlobalData from '@csstools/postcss-global-data'; import postcssNested from 'postcss-nested'; import postcssPresetEnv from 'postcss-preset-env'; import postcssApply from 'postcss-apply'; @@ -10,6 +12,12 @@ import postcssHoverMediaFeature from 'postcss-hover-media-feature'; */ export default function () { return { - plugins: [postcssNested(), postcssPresetEnv(), postcssApply(), postcssHoverMediaFeature()], + plugins: [ + postcssGlobalData({ files: [fileURLToPath(new URL('./src/styles/breakpoints.pcss', import.meta.url))] }), + postcssNested(), + postcssPresetEnv(), + postcssApply(), + postcssHoverMediaFeature(), + ], }; } diff --git a/@codexteam/ui/src/styles/breakpoints.pcss b/@codexteam/ui/src/styles/breakpoints.pcss new file mode 100644 index 00000000..2d24d834 --- /dev/null +++ b/@codexteam/ui/src/styles/breakpoints.pcss @@ -0,0 +1,4 @@ +@custom-media --mobile (max-width: 768px); + +/* 700px content plus two 300px sidebars */ +@custom-media --narrow (max-width: 1300px); diff --git a/@codexteam/ui/src/styles/dimensions.pcss b/@codexteam/ui/src/styles/dimensions.pcss index fa0b3646..07c60d9a 100644 --- a/@codexteam/ui/src/styles/dimensions.pcss +++ b/@codexteam/ui/src/styles/dimensions.pcss @@ -18,6 +18,7 @@ */ --size-icon: 20px; --size-avatar: 30px; + --size-cover: 150px; /** * Layout @@ -25,6 +26,7 @@ --layout-content-width: 700px; --layout-sidebar-width: 300px; --layout-navbar-height: 51px; + --layout-text-width-min: 120px; /** * Height of the line between list items @@ -48,6 +50,12 @@ --radius-field: var(--radius-m); } +@media (--mobile) { + :root { + --size-cover: 96px; + } +} + [data-dimensions="small"] { --v-padding: var(--spacing-xs); --h-padding: var(--spacing-ms); diff --git a/@codexteam/ui/src/styles/typography.pcss b/@codexteam/ui/src/styles/typography.pcss index 195f0140..f7e63620 100644 --- a/@codexteam/ui/src/styles/typography.pcss +++ b/@codexteam/ui/src/styles/typography.pcss @@ -27,12 +27,15 @@ */ font-size: 15px; + /* iOS Safari zooms into focused fields below 16px */ + --font-size-field-mobile: 16px; + /** * Content styles * Should be used only for content (user-written text), not for UI elements. */ --text-h1 { - font-size: 2.8rem; + font-size: clamp(2rem, 1.17rem + 3.17vw, 2.8rem); line-height: 110%; font-weight: 700; margin: 0; @@ -41,7 +44,7 @@ --text-h2 { font-weight: 700; line-height: 120%; - font-size: 1.8rem; + font-size: clamp(1.5rem, 1.19rem + 1.19vw, 1.8rem); } --text-h3 { diff --git a/@codexteam/ui/src/vue/components/card/Card.vue b/@codexteam/ui/src/vue/components/card/Card.vue index 93bfafb3..37caab26 100644 --- a/@codexteam/ui/src/vue/components/card/Card.vue +++ b/@codexteam/ui/src/vue/components/card/Card.vue @@ -28,7 +28,12 @@ {{ subtitle }} - +
+ +
@@ -79,8 +84,6 @@ withDefaults( diff --git a/@codexteam/ui/src/vue/components/popup/Popup.vue b/@codexteam/ui/src/vue/components/popup/Popup.vue index 2cb38604..fcf198bc 100644 --- a/@codexteam/ui/src/vue/components/popup/Popup.vue +++ b/@codexteam/ui/src/vue/components/popup/Popup.vue @@ -94,6 +94,27 @@ withDefaults( color: var(--base--text); } } + + @media (--mobile) { + grid-template-columns: minmax(0, 1fr); + justify-items: center; + padding: var(--spacing-ml); + overflow-y: auto; + + &__container { + grid-column: 1; + grid-row: 2; + max-width: 100%; + box-sizing: border-box; + } + + &__icon { + grid-column: 1; + grid-row: 1; + justify-self: end; + margin: 0 0 var(--spacing-s); + } + } } diff --git a/@codexteam/ui/src/vue/components/row/Row.vue b/@codexteam/ui/src/vue/components/row/Row.vue index 1f84e7e8..f02517ae 100644 --- a/@codexteam/ui/src/vue/components/row/Row.vue +++ b/@codexteam/ui/src/vue/components/row/Row.vue @@ -103,6 +103,7 @@ defineProps<{ &__center { flex: 1; + min-width: 0; display: flex; flex-direction: column; gap: var(--spacing-very-x); @@ -112,5 +113,19 @@ defineProps<{ display: flex; align-items: center; } + + @media (--mobile) { + &__body-inner { + flex-wrap: wrap; + } + + &__center { + flex-basis: var(--layout-text-width-min); + } + + &__right { + margin-left: auto; + } + } } diff --git a/@codexteam/ui/src/vue/components/tabbar/Tab.vue b/@codexteam/ui/src/vue/components/tabbar/Tab.vue index 28952bc3..439f6d25 100644 --- a/@codexteam/ui/src/vue/components/tabbar/Tab.vue +++ b/@codexteam/ui/src/vue/components/tabbar/Tab.vue @@ -43,6 +43,7 @@ diff --git a/@codexteam/ui/src/vue/components/vertical-menu/VerticalMenu.vue b/@codexteam/ui/src/vue/components/vertical-menu/VerticalMenu.vue index 4c61e412..315d08dd 100644 --- a/@codexteam/ui/src/vue/components/vertical-menu/VerticalMenu.vue +++ b/@codexteam/ui/src/vue/components/vertical-menu/VerticalMenu.vue @@ -29,5 +29,9 @@ defineProps<{ gap: var(--spacing-very-x); background-color: var(--base--bg-secondary); width: max-content; + + @media (--mobile) { + width: auto; + } } diff --git a/@codexteam/ui/src/vue/composables/useViewport/index.ts b/@codexteam/ui/src/vue/composables/useViewport/index.ts new file mode 100644 index 00000000..ef00cd1b --- /dev/null +++ b/@codexteam/ui/src/vue/composables/useViewport/index.ts @@ -0,0 +1,8 @@ +import { createSharedComposable, useMediaQuery } from '@vueuse/core'; + +// Keep in sync with --mobile in styles/breakpoints.pcss +const MOBILE_MEDIA_QUERY = '(max-width: 768px)'; + +export const useViewport = createSharedComposable(() => ({ + isMobile: useMediaQuery(MOBILE_MEDIA_QUERY), +})); diff --git a/@codexteam/ui/src/vue/index.ts b/@codexteam/ui/src/vue/index.ts index 4cd7c227..37f0c6b0 100644 --- a/@codexteam/ui/src/vue/index.ts +++ b/@codexteam/ui/src/vue/index.ts @@ -19,6 +19,7 @@ export * from './components/popover'; export * from './components/popup'; export * from './components/confirm'; export * from './composables/useTheme'; +export * from './composables/useViewport'; export * from './components/checkbox'; export * from './components/select'; export * from './components/chart'; diff --git a/@codexteam/ui/src/vue/layout/navbar/Navbar.vue b/@codexteam/ui/src/vue/layout/navbar/Navbar.vue index 328e1f97..3a4212d8 100644 --- a/@codexteam/ui/src/vue/layout/navbar/Navbar.vue +++ b/@codexteam/ui/src/vue/layout/navbar/Navbar.vue @@ -26,5 +26,9 @@ &__right { margin-left: auto; } + + @media (--mobile) { + padding: var(--spacing-ms) var(--spacing-m); + } } diff --git a/@codexteam/ui/src/vue/layout/page-block/PageBlock.vue b/@codexteam/ui/src/vue/layout/page-block/PageBlock.vue index 3d5c382f..1b07029a 100644 --- a/@codexteam/ui/src/vue/layout/page-block/PageBlock.vue +++ b/@codexteam/ui/src/vue/layout/page-block/PageBlock.vue @@ -79,4 +79,26 @@ defineProps({ padding: var(--spacing-xxl) var(--spacing-ml); } } + +@media (--narrow) { + .page-block__sidebar:empty { + display: none; + } +} + +@media (--mobile) { + .page-block { + flex-direction: column; + } + + .page-block__content { + max-width: none; + padding: var(--spacing-l) var(--spacing-ml); + } + + .page-block__sidebar { + width: auto; + padding: var(--spacing-l) var(--spacing-ml) 0; + } +} diff --git a/package.json b/package.json index 4d005a77..0aad1ab5 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "vue-router": "^4.2.4" }, "devDependencies": { + "@csstools/postcss-global-data": "^2.1.1", "@eslint/eslintrc": "^3.1.0", "@eslint/js": "^9.3.0", "@hawk.so/vite-plugin": "^1.0.2", diff --git a/postcss.config.js b/postcss.config.js index 6f1ee2c6..025beb13 100644 --- a/postcss.config.js +++ b/postcss.config.js @@ -1,3 +1,5 @@ +import { fileURLToPath } from 'url'; +import postcssGlobalData from '@csstools/postcss-global-data'; import postcssNested from 'postcss-nested'; import postcssPresetEnv from 'postcss-preset-env'; import postcssApply from 'postcss-apply'; @@ -10,6 +12,12 @@ import postcssHoverMediaFeature from 'postcss-hover-media-feature'; */ export default function () { return { - plugins: [postcssNested(), postcssPresetEnv(), postcssApply(), postcssHoverMediaFeature()], + plugins: [ + postcssGlobalData({ files: [fileURLToPath(new URL('./@codexteam/ui/src/styles/breakpoints.pcss', import.meta.url))] }), + postcssNested(), + postcssPresetEnv(), + postcssApply(), + postcssHoverMediaFeature(), + ], }; } diff --git a/src/App.vue b/src/App.vue index 70751eb9..9e8c495d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -46,5 +46,6 @@ body { -moz-osx-font-smoothing: grayscale; display: grid; grid-template-rows: auto 1fr; + grid-template-columns: minmax(0, 1fr); } diff --git a/src/presentation/components/note-header/NoteHeader.vue b/src/presentation/components/note-header/NoteHeader.vue index 1d26df61..48b570c8 100644 --- a/src/presentation/components/note-header/NoteHeader.vue +++ b/src/presentation/components/note-header/NoteHeader.vue @@ -21,6 +21,7 @@ opacity: var(--opacity); transition: opacity 100ms; justify-content: space-between; + gap: var(--spacing-s); padding: var(--spacing-s) var(--spacing-m); position: sticky; top: var(--layout-navbar-height); @@ -28,11 +29,25 @@ z-index: var(--z-note-header); &__left { + flex: 1 1 0; + min-width: 0; color: var(--base--text-secondary); + + @media (--mobile) { + overflow-x: auto; + white-space: nowrap; + scrollbar-width: none; + + &::-webkit-scrollbar { + display: none; + } + } } &__right { display: flex; + flex-wrap: wrap; + justify-content: flex-end; gap: var(--spacing-s); align-items: center; } diff --git a/src/presentation/components/note-list/NoteList.vue b/src/presentation/components/note-list/NoteList.vue index b70626aa..23e44fcf 100644 --- a/src/presentation/components/note-list/NoteList.vue +++ b/src/presentation/components/note-list/NoteList.vue @@ -86,12 +86,26 @@ function getSubtitle(note: Note): string | undefined { diff --git a/src/presentation/pages/Note.vue b/src/presentation/pages/Note.vue index d04c68a5..f2c839b9 100644 --- a/src/presentation/pages/Note.vue +++ b/src/presentation/pages/Note.vue @@ -44,7 +44,10 @@
-