@indietabletop/appkit 3.2.0-5 → 3.2.0-7

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.
@@ -12,6 +12,16 @@ export type FormSubmitButtonProps = FormSubmitProps & {
12
12
  loading: ReactNode;
13
13
  };
14
14
 
15
+ /**
16
+ * Renders Ariakit FormSubmit component.
17
+ *
18
+ * It's main responsibility is to render the loading component (provided via
19
+ * the `loading` prop) when the form is in the submitting state. This component
20
+ * will be rendered over the usual content of the button, which will be hidden
21
+ * as long as the form is submitting.
22
+ *
23
+ * @remarks Must be rendered within Ariakit Form Context.
24
+ */
15
25
  export function FormSubmitButton(props: FormSubmitButtonProps) {
16
26
  const { children, className, style, loading, ...submitProps } = props;
17
27
  const form = useFormContext();
@@ -0,0 +1,85 @@
1
+ import { Heading, type HeadingProps } from "@ariakit/react";
2
+ import type { RecipeVariants } from "@vanilla-extract/recipes";
3
+ import type { ComponentProps, ReactNode } from "react";
4
+ import { cx } from "../class-names.ts";
5
+ import { interactiveText } from "../common.css.ts";
6
+ import { ExternalLink } from "../ExternalLink.tsx";
7
+ import {
8
+ FormSubmitButton,
9
+ type FormSubmitButtonProps,
10
+ } from "../FormSubmitButton.tsx";
11
+ import { IndieTabletopClubLogo } from "../IndieTabletopClubLogo.tsx";
12
+ import { IndieTabletopClubSymbol } from "../IndieTabletopClubSymbol.tsx";
13
+ import { LoadingIndicator } from "../LoadingIndicator.tsx";
14
+ import * as css from "./style.css.ts";
15
+
16
+ export type LetterheadHeadingProps = RecipeVariants<typeof css.heading> &
17
+ HeadingProps;
18
+
19
+ export function LetterheadHeading(props: LetterheadHeadingProps) {
20
+ const { align, margin, ...rest } = props;
21
+ return <Heading {...rest} {...cx(props, css.heading({ align, margin }))} />;
22
+ }
23
+
24
+ type LetterheadParagraphProps = RecipeVariants<typeof css.paragraph> &
25
+ ComponentProps<"p">;
26
+
27
+ export function LetterheadParagraph(props: LetterheadParagraphProps) {
28
+ const { size, align, ...rest } = props;
29
+ return <p {...rest} {...cx(props, css.paragraph({ size, align }))} />;
30
+ }
31
+
32
+ type LetterheadFooterProps = ComponentProps<"div">;
33
+
34
+ export function LetterheadFooter(props: LetterheadFooterProps) {
35
+ return (
36
+ <div {...props} {...cx(props, css.letterheadFooter)}>
37
+ <IndieTabletopClubLogo {...cx(css.letterheadFooterLogo)} />
38
+
39
+ <LetterheadParagraph {...cx(css.letterheadFooterInfo)} size="small">
40
+ Indie Tabletop Club supports independent game creators with premium
41
+ digital tools.{" "}
42
+ <ExternalLink
43
+ href="https://indietabletop.club"
44
+ className={interactiveText}
45
+ >
46
+ Learn more
47
+ </ExternalLink>
48
+ .
49
+ </LetterheadParagraph>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ type LetterheadSubmitButton = RecipeVariants<typeof css.button> &
55
+ Omit<FormSubmitButtonProps, "loading">;
56
+
57
+ export function LetterheadSubmitButton(props: LetterheadSubmitButton) {
58
+ const { marginBlockStart, ...rest } = props;
59
+ return (
60
+ <FormSubmitButton
61
+ {...rest}
62
+ {...cx(css.button({ marginBlockStart }), props)}
63
+ loading={<LoadingIndicator />}
64
+ />
65
+ );
66
+ }
67
+
68
+ export type LetterheadProps = RecipeVariants<typeof css.letterhead> & {
69
+ headerIcon?: ReactNode;
70
+ children: ReactNode;
71
+ };
72
+
73
+ export function Letterhead(props: LetterheadProps) {
74
+ const { children, textAlign, headerIcon = true } = props;
75
+
76
+ return (
77
+ <div className={css.letterhead({ textAlign })}>
78
+ {headerIcon && <IndieTabletopClubSymbol {...cx(css.letterheadSymbol)} />}
79
+
80
+ {children}
81
+
82
+ <LetterheadFooter />
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,45 @@
1
+ import { FormProvider } from "@ariakit/react";
2
+ import type { Meta, StoryObj } from "@storybook/react-vite";
3
+ import {
4
+ Letterhead,
5
+ LetterheadHeading,
6
+ LetterheadParagraph,
7
+ LetterheadSubmitButton,
8
+ } from "./index.tsx";
9
+
10
+ const meta = {
11
+ title: "Letterhead",
12
+ component: Letterhead,
13
+ tags: ["autodocs"],
14
+ args: {
15
+ textAlign: "start",
16
+ },
17
+ } satisfies Meta<typeof Letterhead>;
18
+
19
+ export default meta;
20
+
21
+ type Story = StoryObj<typeof meta>;
22
+
23
+ export const Default: Story = {
24
+ args: {
25
+ children: (
26
+ <>
27
+ <LetterheadHeading align="center" margin="letterhead">
28
+ Lorem ipsum dolor
29
+ </LetterheadHeading>
30
+
31
+ <LetterheadParagraph>
32
+ Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut enim ad
33
+ minima veniam, quis <em>nostrum</em> exercitationem ullam corporis
34
+ suscipit laboriosam.
35
+ </LetterheadParagraph>
36
+
37
+ <FormProvider>
38
+ <LetterheadSubmitButton marginBlockStart="footerMargin">
39
+ Lorem ipsum
40
+ </LetterheadSubmitButton>
41
+ </FormProvider>
42
+ </>
43
+ ),
44
+ },
45
+ };
@@ -0,0 +1,141 @@
1
+ import { createTheme, style } from "@vanilla-extract/css";
2
+ import { recipe } from "@vanilla-extract/recipes";
3
+ import { textVariants } from "../atomic.css.ts";
4
+ import { manofa, minion } from "../common.css.ts";
5
+ import { Hover } from "../media.ts";
6
+
7
+ const align = {
8
+ start: textVariants({ textAlign: "start" }),
9
+ center: textVariants({ textAlign: "center" }),
10
+ end: textVariants({ textAlign: "end" }),
11
+ };
12
+
13
+ export const [letterheadTheme, { padding, footerMargin }] = createTheme({
14
+ padding: "clamp(1rem, 8vw, 4rem)",
15
+ footerMargin: "3rem",
16
+ });
17
+
18
+ export const letterhead = recipe({
19
+ base: [
20
+ letterheadTheme,
21
+ minion,
22
+ {
23
+ backgroundColor: "white",
24
+ padding: `calc(${padding} / 2) ${padding} ${padding}`,
25
+ borderRadius: "1rem",
26
+ marginInline: "auto",
27
+ maxInlineSize: "36rem",
28
+ },
29
+ ],
30
+
31
+ defaultVariants: {
32
+ textAlign: "center",
33
+ },
34
+
35
+ variants: {
36
+ textAlign: align,
37
+ },
38
+ });
39
+
40
+ export const letterheadSymbol = style({
41
+ marginBlockEnd: "0.5rem",
42
+ marginInline: "auto",
43
+ display: "block",
44
+ inlineSize: "2.5rem",
45
+ blockSize: "2.5rem",
46
+ });
47
+
48
+ export const heading = recipe({
49
+ base: [
50
+ manofa,
51
+ {
52
+ fontWeight: 400,
53
+ fontSize: "1.5rem",
54
+ marginBlockEnd: "1rem",
55
+ lineHeight: 1.2,
56
+ },
57
+ ],
58
+
59
+ variants: {
60
+ align,
61
+
62
+ margin: {
63
+ letterhead: {
64
+ marginBlockEnd: `min(calc(${padding} / 2), 1.5rem)`,
65
+ },
66
+ },
67
+ },
68
+ });
69
+
70
+ export const paragraph = recipe({
71
+ base: [
72
+ minion,
73
+ {
74
+ lineHeight: 1.5,
75
+ selectors: {
76
+ "& + &": { marginTop: "0.5lh" },
77
+ },
78
+ },
79
+ ],
80
+
81
+ defaultVariants: {
82
+ size: "default",
83
+ },
84
+
85
+ variants: {
86
+ size: {
87
+ small: { fontSize: "0.875rem" },
88
+ default: { fontSize: "1rem" },
89
+ },
90
+ align,
91
+ },
92
+ });
93
+
94
+ export const button = recipe({
95
+ base: [
96
+ manofa,
97
+ {
98
+ letterSpacing: 0,
99
+ textTransform: "uppercase",
100
+ backgroundColor: "black",
101
+ color: "white",
102
+ width: "100%",
103
+ border: "none",
104
+ borderRadius: "0.5rem",
105
+ padding: "1rem 1.5rem",
106
+ fontSize: "0.875rem",
107
+
108
+ "@media": {
109
+ [Hover.HOVER]: {
110
+ transition: "box-shadow 400ms",
111
+
112
+ ":hover": {
113
+ boxShadow: "inset 0 0 0 2px hsl(0 0% 100% / 0.5)",
114
+ },
115
+ },
116
+ },
117
+ },
118
+ ],
119
+
120
+ variants: {
121
+ marginBlockStart: {
122
+ footerMargin: { marginBlockStart: `calc(${footerMargin} - 0.5rem)` },
123
+ },
124
+ },
125
+ });
126
+
127
+ export const letterheadFooter = style({
128
+ textAlign: "center",
129
+ marginBlockStart: footerMargin,
130
+ paddingBlockStart: "2rem",
131
+ borderBlockStart: "1px solid #ececec",
132
+ });
133
+
134
+ export const letterheadFooterLogo = style({
135
+ margin: "0 auto 1.125rem",
136
+ });
137
+
138
+ export const letterheadFooterInfo = style({
139
+ margin: "0 auto",
140
+ maxInlineSize: "25rem",
141
+ });
package/lib/atomic.css.ts CHANGED
@@ -1,3 +1,11 @@
1
- import { createSprinkles } from "@vanilla-extract/sprinkles";
1
+ import { createSprinkles, defineProperties } from "@vanilla-extract/sprinkles";
2
2
 
3
- createSprinkles()
3
+ const atomic = defineProperties({
4
+ properties: {
5
+ textAlign: ["start", "center", "end"],
6
+ },
7
+ });
8
+
9
+ export const textVariants = createSprinkles(atomic);
10
+
11
+ export type TextVariants = Parameters<typeof textVariants>[0];
@@ -1,8 +1,33 @@
1
+ type Falsy = false | null | undefined;
2
+
3
+ type PropsWithClassName = {
4
+ className?: ClassName;
5
+ };
6
+
7
+ type ClassName = string | PropsWithClassName | Falsy;
8
+
1
9
  /**
2
- * Combines a list of strings into a single string. Falsy values are ignored.
10
+ * Combines a list of strings or objects with className property into a single
11
+ * string. Falsy values are ignored.
3
12
  */
4
- export function classNames(
5
- ...classNames: (string | false | null | undefined)[]
6
- ) {
7
- return classNames.filter((c) => !!c).join(" ");
13
+ export function classNames(...classNames: ClassName[]) {
14
+ return classNames
15
+ .filter((cn) => !!cn)
16
+ .map((cn) => (typeof cn === "object" ? cn?.className : cn))
17
+ .join(" ");
18
+ }
19
+
20
+ /**
21
+ * Given a list of strings or objects with the className property, returns an
22
+ * object with className property and combined className. Falsy values will
23
+ * be filtered out.
24
+ *
25
+ * @example
26
+ * <h1 {...clx(props, 'heading', 'bold')}>Hello world!</h1>
27
+ */
28
+
29
+ export function cx(...cns: ClassName[]) {
30
+ return {
31
+ className: classNames(...cns),
32
+ };
8
33
  }
package/lib/common.css.ts CHANGED
@@ -15,6 +15,8 @@ export const itcSymbol = style({
15
15
 
16
16
  export const manofa = style({
17
17
  fontFamily: `"manofa", sans-serif`,
18
+ fontFeatureSettings: `"ss01"`,
19
+ letterSpacing: "-.01em",
18
20
  });
19
21
 
20
22
  export const minion = style({
package/lib/index.ts CHANGED
@@ -3,8 +3,7 @@ export * from "./ExternalLink.tsx";
3
3
  export * from "./FormSubmitButton.tsx";
4
4
  export * from "./FullscreenDismissBlocker.tsx";
5
5
  export * from "./IndieTabletopClubSymbol.tsx";
6
- export * from "./Letterhead.tsx";
7
- export * from "./LetterheadFooter.tsx";
6
+ export * from "./Letterhead/index.tsx";
8
7
  export * from "./LoadingIndicator.tsx";
9
8
  export * from "./ServiceWorkerHandler.tsx";
10
9
 
@@ -23,6 +22,7 @@ export * from "./async-op.ts";
23
22
  export * from "./caught-value.ts";
24
23
  export * from "./class-names.ts";
25
24
  export * from "./client.ts";
25
+ export * from "./knownFailure.ts";
26
26
  export * from "./media.ts";
27
27
  export * from "./structs.ts";
28
28
  export * from "./types.ts";
@@ -1,6 +1,5 @@
1
1
  import { createVar, style } from "@vanilla-extract/css";
2
2
  import { bounce } from "./animations.css.ts";
3
- import { minion } from "./common.css.ts";
4
3
 
5
4
  export const animationDelay = createVar();
6
5
 
@@ -9,18 +8,3 @@ export const dot = style({
9
8
  opacity: 0.8,
10
9
  animation: `${bounce} 2s ${animationDelay} infinite`,
11
10
  });
12
-
13
- export const padding = createVar();
14
-
15
- export const letterhead = style([
16
- minion,
17
- {
18
- vars: { [padding]: "clamp(1rem, 8vw, 4rem)" },
19
-
20
- backgroundColor: "white",
21
- padding: `max(1rem, calc(${padding} - .5rem)) ${padding} ${padding}`,
22
- borderRadius: "1rem",
23
- marginInline: "auto",
24
- maxInlineSize: "36rem",
25
- },
26
- ]);
@@ -0,0 +1,9 @@
1
+ import type { FailurePayload } from "./types.ts";
2
+
3
+ export function toKnownFailureMessage(failure: FailurePayload) {
4
+ if (failure.type === "NETWORK_ERROR") {
5
+ return "Could not submit form due to network error. Make sure you are connected to the internet and try again.";
6
+ }
7
+
8
+ return "Could not submit form due to an unexpected error. Please refresh the page and try again.";
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indietabletop/appkit",
3
- "version": "3.2.0-5",
3
+ "version": "3.2.0-7",
4
4
  "description": "A collection of modules used in apps built by Indie Tabletop Club",
5
5
  "private": false,
6
6
  "type": "module",
@@ -39,6 +39,7 @@
39
39
  "@indietabletop/tooling": "^5.0.0",
40
40
  "@vanilla-extract/css": "^1.17.2",
41
41
  "@vanilla-extract/dynamic": "^2.1.3",
42
+ "@vanilla-extract/recipes": "^0.5.7",
42
43
  "@vanilla-extract/sprinkles": "^1.6.3",
43
44
  "superstruct": "^2.0.2"
44
45
  }
@@ -1,33 +0,0 @@
1
- import type { ReactNode } from "react";
2
- import { IndieTabletopClubSymbol } from "./IndieTabletopClubSymbol.tsx";
3
- import { letterhead } from "./internal.css.ts";
4
- import { LetterheadFooter } from "./LetterheadFooter.tsx";
5
-
6
- export type LetterheadProps = {
7
- headerIcon?: boolean;
8
- children: ReactNode;
9
- };
10
-
11
- export function Letterhead(props: LetterheadProps) {
12
- const { children, headerIcon = true } = props;
13
-
14
- return (
15
- <div className={letterhead}>
16
- {headerIcon && (
17
- <IndieTabletopClubSymbol
18
- style={{
19
- marginBlock: "-1rem 1rem",
20
- marginInline: "auto",
21
- display: "block",
22
- inlineSize: "2.5rem",
23
- blockSize: "2.5rem",
24
- }}
25
- />
26
- )}
27
-
28
- {children}
29
-
30
- <LetterheadFooter />
31
- </div>
32
- );
33
- }
@@ -1,37 +0,0 @@
1
- import { interactiveText } from "./common.css.ts";
2
- import { ExternalLink } from "./ExternalLink.tsx";
3
- import { IndieTabletopClubLogo } from "./IndieTabletopClubLogo.tsx";
4
-
5
- export function LetterheadFooter() {
6
- return (
7
- <div
8
- style={{
9
- textAlign: "center",
10
- paddingBlockStart: "2rem",
11
- borderBlockStart: "1px solid #ececec",
12
- marginBlockStart: "3rem",
13
- }}
14
- >
15
- <IndieTabletopClubLogo style={{ margin: "0 auto 1.125rem" }} />
16
- <p
17
- style={{
18
- margin: "0 auto",
19
- maxInlineSize: "25rem",
20
- fontSize: "0.875rem",
21
- lineHeight: "1.25rem",
22
- fontFamily: "minion-pro, serif",
23
- }}
24
- >
25
- Indie Tabletop Club supports independent game creators with high‑quality
26
- digital tools.{" "}
27
- <ExternalLink
28
- href="https://indietabletop.club"
29
- className={interactiveText}
30
- >
31
- Learn more
32
- </ExternalLink>
33
- .
34
- </p>
35
- </div>
36
- );
37
- }