tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
28
pulls
pipelines
added a slider to adjust page-wdith to leaflets
cozylittle.house
2 months ago
cf958cb4
d742da94
+173
-14
8 changed files
expand all
collapse all
unified
split
app
globals.css
components
ThemeManager
Pickers
ImagePicker.tsx
PageThemePickers.tsx
PageWidthSetter.tsx
PubPickers
PubTextPickers.tsx
ThemeProvider.tsx
ThemeSetter.tsx
src
replicache
attributes.ts
+19
-6
app/globals.css
···
107
107
--highlight-3: 255, 205, 195;
108
108
109
109
--list-marker-width: 36px;
110
110
-
--page-width-unitless: min(624, calc(var(--leaflet-width-unitless) - 12));
111
111
-
--page-width-units: min(624px, calc(100vw - 12px));
110
110
+
--page-max-width-unitless: 624;
111
111
+
--page-width-unitless: min(
112
112
+
var(--page-max-width-unitless),
113
113
+
calc(var(--leaflet-width-unitless) - 12)
114
114
+
);
115
115
+
--page-width-units: min(
116
116
+
calc(var(--page-max-width-unitless) * 1px),
117
117
+
calc(100vw - 12px)
118
118
+
);
112
119
113
120
--gripperSVG: url("/gripperPattern.svg");
114
121
--gripperSVG2: url("/gripperPattern2.svg");
···
126
133
@media (min-width: 640px) {
127
134
:root {
128
135
--page-width-unitless: min(
129
129
-
624,
136
136
+
var(--page-max-width-unitless),
130
137
calc(var(--leaflet-width-unitless) - 128)
131
138
);
132
132
-
--page-width-units: min(624px, calc(100vw - 128px));
139
139
+
--page-width-units: min(
140
140
+
calc(var(--page-max-width-unitless) * 1px),
141
141
+
calc(100vw - 128px)
142
142
+
);
133
143
}
134
144
}
135
145
136
146
@media (min-width: 1280px) {
137
147
:root {
138
148
--page-width-unitless: min(
139
139
-
624,
149
149
+
var(--page-max-width-unitless),
140
150
calc((var(--leaflet-width-unitless) / 2) - 32)
141
151
);
142
142
-
--page-width-units: min(624px, calc((100vw / 2) - 32px));
152
152
+
--page-width-units: min(
153
153
+
calc(var(--page-max-width-unitless) * 1px),
154
154
+
calc((100vw / 2) - 32px)
155
155
+
);
143
156
}
144
157
}
145
158
+2
-2
components/ThemeManager/Pickers/ImagePicker.tsx
···
127
127
<Slider.Thumb
128
128
className={`
129
129
flex w-4 h-4 rounded-full border-2 border-white cursor-pointer
130
130
-
${repeat ? "bg-[#595959]" : " bg-[#C3C3C3] "}
131
131
-
${repeat && "shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]"} `}
130
130
+
${repeat ? "bg-[#595959] shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]" : " bg-[#C3C3C3] "}
131
131
+
`}
132
132
aria-label="Volume"
133
133
/>
134
134
</Slider.Root>
+2
-2
components/ThemeManager/Pickers/PageThemePickers.tsx
···
51
51
<hr className="border-border-light w-full" />
52
52
</>
53
53
)}
54
54
-
<PageTextPicker
54
54
+
<TextPickers
55
55
value={primaryValue}
56
56
setValue={set("theme/primary")}
57
57
openPicker={props.openPicker}
···
347
347
);
348
348
};
349
349
350
350
-
export const PageTextPicker = (props: {
350
350
+
export const TextPickers = (props: {
351
351
openPicker: pickers;
352
352
setOpenPicker: (thisPicker: pickers) => void;
353
353
value: Color;
+120
components/ThemeManager/Pickers/PageWidthSetter.tsx
···
1
1
+
import * as Slider from "@radix-ui/react-slider";
2
2
+
import { Input } from "components/Input";
3
3
+
import { useEntity, useReplicache } from "src/replicache";
4
4
+
import { pickers } from "../ThemeSetter";
5
5
+
import { useState } from "react";
6
6
+
7
7
+
export const PageWidthSetter = (props: {
8
8
+
entityID: string;
9
9
+
openPicker: pickers;
10
10
+
thisPicker: pickers;
11
11
+
setOpenPicker: (thisPicker: pickers) => void;
12
12
+
closePicker: () => void;
13
13
+
}) => {
14
14
+
let { rep } = useReplicache();
15
15
+
let pageWidth = useEntity(props.entityID, "theme/page-width");
16
16
+
let currentValue = pageWidth?.data.value || 624;
17
17
+
let [interimValue, setInterimValue] = useState<number>(currentValue);
18
18
+
let min = 324;
19
19
+
let max = 1200;
20
20
+
21
21
+
let open = props.openPicker == props.thisPicker;
22
22
+
23
23
+
return (
24
24
+
<div className="pageWidthSetter flex flex-col gap-2 px-2 py-[6px] border border-[#CCCCCC] rounded-md">
25
25
+
<div className="flex flex-col gap-2">
26
26
+
<div className="flex gap-2 items-center">
27
27
+
<button
28
28
+
className="font-bold text-[#000000] shrink-0 grow-0 w-fit"
29
29
+
onClick={() => {
30
30
+
if (props.openPicker === props.thisPicker) {
31
31
+
props.setOpenPicker("null");
32
32
+
} else {
33
33
+
props.setOpenPicker(props.thisPicker);
34
34
+
}
35
35
+
}}
36
36
+
>
37
37
+
Max Page Width
38
38
+
</button>
39
39
+
<div className="flex font-normal text-[#969696]">
40
40
+
<Input
41
41
+
type="number"
42
42
+
className="relative w-10 text-right appearance-none bg-transparent"
43
43
+
max={max}
44
44
+
min={min}
45
45
+
value={interimValue}
46
46
+
onFocus={(e) => {
47
47
+
e.preventDefault();
48
48
+
props.setOpenPicker(props.thisPicker);
49
49
+
}}
50
50
+
onChange={(e) => {
51
51
+
setInterimValue(parseInt(e.currentTarget.value));
52
52
+
}}
53
53
+
onKeyDown={(e) => {
54
54
+
if (e.key === "Enter" || e.key === "Escape") {
55
55
+
e.preventDefault();
56
56
+
let clampedValue = interimValue;
57
57
+
if (!isNaN(interimValue)) {
58
58
+
clampedValue = Math.max(min, Math.min(max, interimValue));
59
59
+
setInterimValue(clampedValue);
60
60
+
}
61
61
+
rep?.mutate.assertFact({
62
62
+
entity: props.entityID,
63
63
+
attribute: "theme/page-width",
64
64
+
data: {
65
65
+
type: "number",
66
66
+
value: clampedValue,
67
67
+
},
68
68
+
});
69
69
+
}
70
70
+
}}
71
71
+
onBlur={() => {
72
72
+
let clampedValue = interimValue;
73
73
+
if (!isNaN(interimValue)) {
74
74
+
clampedValue = Math.max(min, Math.min(max, interimValue));
75
75
+
setInterimValue(clampedValue);
76
76
+
}
77
77
+
rep?.mutate.assertFact({
78
78
+
entity: props.entityID,
79
79
+
attribute: "theme/page-width",
80
80
+
data: {
81
81
+
type: "number",
82
82
+
value: clampedValue,
83
83
+
},
84
84
+
});
85
85
+
}}
86
86
+
/>
87
87
+
px
88
88
+
</div>
89
89
+
</div>
90
90
+
{open && (
91
91
+
<Slider.Root
92
92
+
className="relative grow flex items-center select-none touch-none w-full h-fit px-1 mb-1"
93
93
+
value={[interimValue]}
94
94
+
max={max}
95
95
+
min={min}
96
96
+
step={16}
97
97
+
onValueChange={(value) => {
98
98
+
setInterimValue(value[0]);
99
99
+
}}
100
100
+
onPointerUp={() => {
101
101
+
rep?.mutate.assertFact({
102
102
+
entity: props.entityID,
103
103
+
attribute: "theme/page-width",
104
104
+
data: { type: "number", value: interimValue },
105
105
+
});
106
106
+
}}
107
107
+
>
108
108
+
<Slider.Track className="bg-[#595959] relative grow rounded-full h-[3px]" />
109
109
+
<Slider.Thumb
110
110
+
className="flex w-4 h-4 outline-none! rounded-full border-2 border-white cursor-pointer bg-[#595959]
111
111
+
focus:shadow-[0_0_0_1px_#8C8C8C,inset_0_0_0_1px_#8C8C8C]
112
112
+
"
113
113
+
aria-label="Max Page Width"
114
114
+
/>
115
115
+
</Slider.Root>
116
116
+
)}
117
117
+
</div>
118
118
+
</div>
119
119
+
);
120
120
+
};
+2
-2
components/ThemeManager/PubPickers/PubTextPickers.tsx
···
1
1
import { pickers } from "../ThemeSetter";
2
2
-
import { PageTextPicker } from "../Pickers/PageThemePickers";
2
2
+
import { TextPickers } from "../Pickers/PageThemePickers";
3
3
import { Color } from "react-aria-components";
4
4
5
5
export const PagePickers = (props: {
···
20
20
: "transparent",
21
21
}}
22
22
>
23
23
-
<PageTextPicker
23
23
+
<TextPickers
24
24
value={props.primary}
25
25
setValue={props.setPrimary}
26
26
openPicker={props.openPicker}
+13
components/ThemeManager/ThemeProvider.tsx
···
74
74
let accent1 = useColorAttribute(props.entityID, "theme/accent-background");
75
75
let accent2 = useColorAttribute(props.entityID, "theme/accent-text");
76
76
77
77
+
let pageWidth = useEntity(props.entityID, "theme/page-width");
78
78
+
77
79
return (
78
80
<CardBorderHiddenContext.Provider value={!!cardBorderHiddenValue}>
79
81
<BaseThemeProvider
···
87
89
accent1={accent1}
88
90
accent2={accent2}
89
91
showPageBackground={showPageBackground}
92
92
+
pageWidth={pageWidth?.data.value}
90
93
>
91
94
{props.children}
92
95
</BaseThemeProvider>
···
106
109
highlight2,
107
110
highlight3,
108
111
showPageBackground,
112
112
+
pageWidth,
109
113
children,
110
114
}: {
111
115
local?: boolean;
···
118
122
highlight1?: string;
119
123
highlight2: AriaColor;
120
124
highlight3: AriaColor;
125
125
+
pageWidth?: number;
121
126
children: React.ReactNode;
122
127
}) => {
123
128
// set accent contrast to the accent color that has the highest contrast with the page background
···
196
201
"--accent-1-is-contrast",
197
202
accentContrast === accent1 ? "1" : "0",
198
203
);
204
204
+
205
205
+
// Set page width CSS variable
206
206
+
el?.style.setProperty(
207
207
+
"--page-max-width-unitless",
208
208
+
(pageWidth || 624).toString(),
209
209
+
);
199
210
}, [
200
211
local,
201
212
bgLeaflet,
···
207
218
accent1,
208
219
accent2,
209
220
accentContrast,
221
221
+
pageWidth,
210
222
]);
211
223
return (
212
224
<div
···
226
238
: "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
227
239
"--highlight-2": colorToString(highlight2, "rgb"),
228
240
"--highlight-3": colorToString(highlight3, "rgb"),
241
241
+
"--page-max-width-unitless": pageWidth || 624,
229
242
} as CSSProperties
230
243
}
231
244
>
+11
-2
components/ThemeManager/ThemeSetter.tsx
···
10
10
PageBorderHider,
11
11
PageThemePickers,
12
12
} from "./Pickers/PageThemePickers";
13
13
+
import { PageWidthSetter } from "./Pickers/PageWidthSetter";
13
14
import { useMemo, useState } from "react";
14
15
import { ReplicacheMutators, useEntity, useReplicache } from "src/replicache";
15
16
import { Replicache } from "replicache";
···
35
36
| "highlight-1"
36
37
| "highlight-2"
37
38
| "highlight-3"
38
38
-
| "page-background-image";
39
39
+
| "page-background-image"
40
40
+
| "page-width";
39
41
40
42
export function setColorAttribute(
41
43
rep: Replicache<ReplicacheMutators> | null,
···
75
77
return (
76
78
<>
77
79
<Popover
78
78
-
className="w-80 bg-white"
80
80
+
className="w-80 bg-white py-3!"
79
81
arrowFill="#FFFFFF"
80
82
asChild
81
83
side={isMobile ? "top" : "right"}
···
114
116
if (pub?.publications) return null;
115
117
return (
116
118
<div className="themeSetterContent flex flex-col w-full overflow-y-scroll no-scrollbar">
119
119
+
<PageWidthSetter
120
120
+
entityID={props.entityID}
121
121
+
thisPicker={"page-width"}
122
122
+
openPicker={openPicker}
123
123
+
setOpenPicker={setOpenPicker}
124
124
+
closePicker={() => setOpenPicker("null")}
125
125
+
/>
117
126
<div className="themeBGLeaflet flex">
118
127
<div className={`bgPicker flex flex-col gap-0 -mb-[6px] z-10 w-full `}>
119
128
<div className="bgPickerBody w-full flex flex-col gap-2 p-2 mt-1 border border-[#CCCCCC] rounded-md">
+4
src/replicache/attributes.ts
···
191
191
type: "boolean",
192
192
cardinality: "one",
193
193
},
194
194
+
"theme/page-width": {
195
195
+
type: "number",
196
196
+
cardinality: "one",
197
197
+
},
194
198
"theme/page-background": {
195
199
type: "color",
196
200
cardinality: "one",