@dxos/plugin-status-bar 0.6.8-main.046e6cf

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 (49) hide show
  1. package/LICENSE +8 -0
  2. package/README.md +15 -0
  3. package/dist/lib/browser/chunk-PL5XPTPX.mjs +18 -0
  4. package/dist/lib/browser/chunk-PL5XPTPX.mjs.map +7 -0
  5. package/dist/lib/browser/index.mjs +372 -0
  6. package/dist/lib/browser/index.mjs.map +7 -0
  7. package/dist/lib/browser/meta.json +1 -0
  8. package/dist/lib/browser/meta.mjs +9 -0
  9. package/dist/lib/browser/meta.mjs.map +7 -0
  10. package/dist/types/src/StatusBarPlugin.d.ts +4 -0
  11. package/dist/types/src/StatusBarPlugin.d.ts.map +1 -0
  12. package/dist/types/src/components/FeedbackForm.d.ts +5 -0
  13. package/dist/types/src/components/FeedbackForm.d.ts.map +1 -0
  14. package/dist/types/src/components/StatusBar.d.ts +42 -0
  15. package/dist/types/src/components/StatusBar.d.ts.map +1 -0
  16. package/dist/types/src/components/StatusBar.stories.d.ts +50 -0
  17. package/dist/types/src/components/StatusBar.stories.d.ts.map +1 -0
  18. package/dist/types/src/components/StatusBarImpl.d.ts +3 -0
  19. package/dist/types/src/components/StatusBarImpl.d.ts.map +1 -0
  20. package/dist/types/src/components/index.d.ts +3 -0
  21. package/dist/types/src/components/index.d.ts.map +1 -0
  22. package/dist/types/src/hooks/index.d.ts +2 -0
  23. package/dist/types/src/hooks/index.d.ts.map +1 -0
  24. package/dist/types/src/hooks/useForm.d.ts +36 -0
  25. package/dist/types/src/hooks/useForm.d.ts.map +1 -0
  26. package/dist/types/src/index.d.ts +5 -0
  27. package/dist/types/src/index.d.ts.map +1 -0
  28. package/dist/types/src/meta.d.ts +15 -0
  29. package/dist/types/src/meta.d.ts.map +1 -0
  30. package/dist/types/src/translations.d.ts +13 -0
  31. package/dist/types/src/translations.d.ts.map +1 -0
  32. package/dist/types/src/util/index.d.ts +2 -0
  33. package/dist/types/src/util/index.d.ts.map +1 -0
  34. package/dist/types/src/util/validation.d.ts +7 -0
  35. package/dist/types/src/util/validation.d.ts.map +1 -0
  36. package/package.json +70 -0
  37. package/src/StatusBarPlugin.tsx +33 -0
  38. package/src/components/FeedbackForm.tsx +127 -0
  39. package/src/components/StatusBar.stories.tsx +43 -0
  40. package/src/components/StatusBar.tsx +110 -0
  41. package/src/components/StatusBarImpl.tsx +45 -0
  42. package/src/components/index.ts +6 -0
  43. package/src/hooks/index.ts +5 -0
  44. package/src/hooks/useForm.ts +119 -0
  45. package/src/index.ts +10 -0
  46. package/src/meta.tsx +18 -0
  47. package/src/translations.ts +40 -0
  48. package/src/util/index.ts +5 -0
  49. package/src/util/validation.ts +22 -0
@@ -0,0 +1,50 @@
1
+ import '@dxosTheme';
2
+ import React from 'react';
3
+ declare const _default: {
4
+ title: string;
5
+ component: {
6
+ Container: React.ForwardRefExoticComponent<Omit<{
7
+ children: React.ReactNode;
8
+ }, "className"> & {
9
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
10
+ } & React.RefAttributes<HTMLDivElement>>;
11
+ Text: React.ForwardRefExoticComponent<Omit<{
12
+ children: React.ReactNode;
13
+ }, "className"> & {
14
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
15
+ } & React.RefAttributes<HTMLSpanElement>>;
16
+ Item: React.ForwardRefExoticComponent<Omit<{
17
+ children: React.ReactNode;
18
+ }, "className"> & {
19
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
20
+ } & React.HTMLAttributes<HTMLDivElement> & React.RefAttributes<HTMLDivElement>>;
21
+ Button: React.ForwardRefExoticComponent<Omit<{
22
+ children: React.ReactNode;
23
+ asChild?: boolean;
24
+ }, "className"> & {
25
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
26
+ } & React.HTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>>;
27
+ StartContent: React.ForwardRefExoticComponent<Omit<{
28
+ children: React.ReactNode;
29
+ }, "className"> & {
30
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
31
+ } & React.RefAttributes<HTMLDivElement>>;
32
+ EndContent: React.ForwardRefExoticComponent<Omit<{
33
+ children: React.ReactNode;
34
+ }, "className"> & {
35
+ classNames?: import("packages/ui/react-ui-types/dist/types/src").ClassNameValue;
36
+ } & React.RefAttributes<HTMLDivElement>>;
37
+ };
38
+ actions: {
39
+ argTypesRegex: string;
40
+ };
41
+ decorators: import("@storybook/react/*").Decorator[];
42
+ parameters: {
43
+ chromatic: {
44
+ disableSnapshot: boolean;
45
+ };
46
+ };
47
+ };
48
+ export default _default;
49
+ export declare const Normal: (_props: any) => React.JSX.Element;
50
+ //# sourceMappingURL=StatusBar.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBar.stories.d.ts","sourceRoot":"","sources":["../../../../src/components/StatusBar.stories.tsx"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAGpB,OAAO,KAAK,MAAM,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAM1B,wBAME;AAEF,eAAO,MAAM,MAAM,WAAY,GAAG,sBAqBjC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import React from 'react';
2
+ export declare const StatusBarImpl: () => React.JSX.Element;
3
+ //# sourceMappingURL=StatusBarImpl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StatusBarImpl.d.ts","sourceRoot":"","sources":["../../../../src/components/StatusBarImpl.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAmB,MAAM,OAAO,CAAC;AASxC,eAAO,MAAM,aAAa,yBA8BzB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './StatusBar';
2
+ export * from './StatusBarImpl';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/index.ts"],"names":[],"mappings":"AAIA,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from './useForm';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/hooks/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type ChangeEvent } from 'react';
2
+ import { type ValidationError } from '../util';
3
+ interface FormOptions<T> {
4
+ initialValues: T;
5
+ validate: (values: any) => ValidationError[] | undefined;
6
+ onSubmit: (values: T) => void;
7
+ }
8
+ /**
9
+ * Creates a hook for managing form state, including values, validation, and submission.
10
+ *
11
+ * @example
12
+ * const { values, handleChange, handleSubmit, errors, canSubmit, getInputProps } = useForm({
13
+ * initialValues: { name: '', email: '' },
14
+ * validate: (values) => { yourValidationLogic(values); },
15
+ * onSubmit: (values) => { submissionLogic(values); },
16
+ * });
17
+ */
18
+ export declare const useForm: <T extends object>({ initialValues, validate, onSubmit }: FormOptions<T>) => {
19
+ values: T;
20
+ handleChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
21
+ handleSubmit: () => void;
22
+ errors: Record<keyof T, string>;
23
+ canSubmit: boolean;
24
+ touched: Record<keyof T, boolean>;
25
+ handleBlur: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
26
+ onValidate: (values: T) => boolean;
27
+ getInputProps: (key: keyof T) => {
28
+ name: keyof T;
29
+ value: T[keyof T];
30
+ onChange: (event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
31
+ onBlur: (event: React.FocusEvent<HTMLInputElement | HTMLTextAreaElement>) => void;
32
+ 'aria-invalid': boolean;
33
+ };
34
+ };
35
+ export {};
36
+ //# sourceMappingURL=useForm.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useForm.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useForm.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,WAAW,EAAyB,MAAM,OAAO,CAAC;AAEhE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,SAAS,CAAC;AAK/C,UAAU,WAAW,CAAC,CAAC;IACrB,aAAa,EAAE,CAAC,CAAC;IACjB,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,KAAK,eAAe,EAAE,GAAG,SAAS,CAAC;IACzD,QAAQ,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;CAC/B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,OAAO,GAAI,CAAC,SAAS,MAAM,yCAAyC,WAAW,CAAC,CAAC,CAAC;;0BAkBnF,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC;;;;;wBAYnD,KAAK,CAAC,UAAU,CAAC,gBAAgB,GAAG,mBAAmB,CAAC;yBAtBvD,CAAC;yBAiDJ,MAAM,CAAC;;;0BAvCL,WAAW,CAAC,gBAAgB,GAAG,mBAAmB,CAAC;wBAYnD,KAAK,CAAC,UAAU,CAAC,gBAAgB,GAAG,mBAAmB,CAAC;;;CAgDnE,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { StatusBarPlugin } from './StatusBarPlugin';
2
+ export default StatusBarPlugin;
3
+ export * from './StatusBarPlugin';
4
+ export * from './components';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,eAAe,eAAe,CAAC;AAE/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,cAAc,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type IconProps } from '@phosphor-icons/react';
2
+ import React from 'react';
3
+ export declare const STATUS_BAR_PLUGIN = "dxos.org/plugin/status-bar";
4
+ declare const _default: {
5
+ id: string;
6
+ shortId?: string;
7
+ name?: string;
8
+ description?: string;
9
+ homePage?: string;
10
+ tags?: string[];
11
+ iconComponent?: React.FC<IconProps>;
12
+ iconSymbol?: string;
13
+ };
14
+ export default _default;
15
+ //# sourceMappingURL=meta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../../src/meta.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAQ,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,MAAM,OAAO,CAAC;AAI1B,eAAO,MAAM,iBAAiB,+BAA+B,CAAC;;;;;;;;;;;AAE9D,wBAMG"}
@@ -0,0 +1,13 @@
1
+ import { type useTranslation } from '@dxos/react-ui';
2
+ import { STATUS_BAR_PLUGIN } from './meta';
3
+ type StatusBarTranslationKeys = 'plugin name' | 'email input label' | 'email input placeholder' | 'feedback text area label' | 'feedback text area placeholder' | 'send feedback label' | 'name label' | 'name placeholder';
4
+ type RequiredTranslation = {
5
+ [key in StatusBarTranslationKeys]: string;
6
+ };
7
+ type TranslationEntry = {
8
+ [STATUS_BAR_PLUGIN]: RequiredTranslation;
9
+ };
10
+ export declare const mkTranslation: (t: ReturnType<typeof useTranslation>["t"]) => (key: StatusBarTranslationKeys) => string;
11
+ declare const _default: Record<string, TranslationEntry>[];
12
+ export default _default;
13
+ //# sourceMappingURL=translations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translations.d.ts","sourceRoot":"","sources":["../../../src/translations.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAE3C,KAAK,wBAAwB,GACzB,aAAa,GACb,mBAAmB,GACnB,yBAAyB,GACzB,0BAA0B,GAC1B,gCAAgC,GAChC,qBAAqB,GACrB,YAAY,GACZ,kBAAkB,CAAC;AAEvB,KAAK,mBAAmB,GAAG;KAAG,GAAG,IAAI,wBAAwB,GAAG,MAAM;CAAE,CAAC;AACzE,KAAK,gBAAgB,GAAG;IAAE,CAAC,iBAAiB,CAAC,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAKrE,eAAO,MAAM,aAAa,MAAO,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC,GAAG,CAAC,WAAW,wBAAwB,WAAW,CAAC;;AAetH,wBAA8B"}
@@ -0,0 +1,2 @@
1
+ export * from './validation';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/util/index.ts"],"names":[],"mappings":"AAIA,cAAc,cAAc,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { S } from '@dxos/echo-schema';
2
+ export type ValidationError = {
3
+ path: string;
4
+ message: string;
5
+ };
6
+ export declare const validate: <T>(schema: S.Schema<T>, data: any) => ValidationError[] | undefined;
7
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../../../src/util/validation.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,CAAC,EAAE,MAAM,mBAAmB,CAAC;AAEtC,MAAM,MAAM,eAAe,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhE,eAAO,MAAM,QAAQ,GAAI,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAG,eAAe,EAAE,GAAG,SAWhF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@dxos/plugin-status-bar",
3
+ "version": "0.6.8-main.046e6cf",
4
+ "description": "Status bar plugin",
5
+ "homepage": "https://dxos.org",
6
+ "bugs": "https://github.com/dxos/dxos/issues",
7
+ "license": "MIT",
8
+ "author": "DXOS.org",
9
+ "exports": {
10
+ ".": {
11
+ "browser": "./dist/lib/browser/index.mjs",
12
+ "types": "./dist/types/src/index.d.ts"
13
+ },
14
+ "./meta": {
15
+ "browser": "./dist/lib/browser/meta.mjs",
16
+ "types": "./dist/types/src/meta.d.ts"
17
+ }
18
+ },
19
+ "types": "dist/types/src/index.d.ts",
20
+ "typesVersions": {
21
+ "*": {
22
+ "meta": [
23
+ "dist/types/src/meta.d.ts"
24
+ ]
25
+ }
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "dependencies": {
32
+ "@effect/schema": "^0.67.16",
33
+ "@fluentui/react-tabster": "^9.19.0",
34
+ "@preact/signals-core": "^1.6.0",
35
+ "@radix-ui/react-slot": "^1.0.1",
36
+ "effect": "^3.2.7",
37
+ "leaflet": "^1.9.3",
38
+ "react-leaflet": "^4.2.0",
39
+ "react-resize-detector": "^11.0.1",
40
+ "@dxos/async": "0.6.8-main.046e6cf",
41
+ "@dxos/echo-schema": "0.6.8-main.046e6cf",
42
+ "@dxos/plugin-client": "0.6.8-main.046e6cf",
43
+ "@dxos/app-framework": "0.6.8-main.046e6cf",
44
+ "@dxos/plugin-space": "0.6.8-main.046e6cf",
45
+ "@dxos/plugin-stack": "0.6.8-main.046e6cf",
46
+ "@dxos/react-client": "0.6.8-main.046e6cf"
47
+ },
48
+ "devDependencies": {
49
+ "@phosphor-icons/react": "^2.1.5",
50
+ "@types/leaflet": "^1.9.0",
51
+ "@types/react": "~18.2.0",
52
+ "@types/react-dom": "~18.2.0",
53
+ "react": "~18.2.0",
54
+ "react-dom": "~18.2.0",
55
+ "vite": "^5.3.4",
56
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
57
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf",
58
+ "@dxos/storybook-utils": "0.6.8-main.046e6cf"
59
+ },
60
+ "peerDependencies": {
61
+ "@phosphor-icons/react": "^2.0.5",
62
+ "react": "^18.0.0",
63
+ "react-dom": "^18.0.0",
64
+ "@dxos/react-ui": "0.6.8-main.046e6cf",
65
+ "@dxos/react-ui-theme": "0.6.8-main.046e6cf"
66
+ },
67
+ "publishConfig": {
68
+ "access": "public"
69
+ }
70
+ }
@@ -0,0 +1,33 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { type PluginDefinition, type SurfaceProvides, type TranslationsProvides } from '@dxos/app-framework';
8
+
9
+ import { StatusBarImpl } from './components';
10
+ import meta from './meta';
11
+ import translations from './translations';
12
+
13
+ export type StatusBarPluginProvides = SurfaceProvides & TranslationsProvides;
14
+
15
+ export const StatusBarPlugin = (): PluginDefinition<StatusBarPluginProvides> => {
16
+ return {
17
+ meta,
18
+ provides: {
19
+ translations,
20
+ surface: {
21
+ component: ({ role }) => {
22
+ switch (role) {
23
+ case 'status-bar': {
24
+ return <StatusBarImpl />;
25
+ }
26
+ }
27
+
28
+ return null;
29
+ },
30
+ },
31
+ },
32
+ };
33
+ };
@@ -0,0 +1,127 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { X, PaperPlaneTilt } from '@phosphor-icons/react';
6
+ import React, { useCallback } from 'react';
7
+
8
+ import { useIntentDispatcher } from '@dxos/app-framework';
9
+ import { S } from '@dxos/echo-schema';
10
+ import { Button, Input, Popover, useTranslation } from '@dxos/react-ui';
11
+ import { getSize } from '@dxos/react-ui-theme';
12
+
13
+ import { useForm } from '../hooks';
14
+ import { STATUS_BAR_PLUGIN } from '../meta';
15
+ import { mkTranslation } from '../translations';
16
+ import { validate } from '../util';
17
+
18
+ const Email = S.String.pipe(
19
+ S.nonEmpty({ message: () => 'Email is required.' }),
20
+ S.pattern(/^(?!\.)(?!.*\.\.)([A-Z0-9_+-.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9-]*\.)+[A-Z]{2,}$/i, {
21
+ message: () => 'Invalid email address.',
22
+ }),
23
+ );
24
+
25
+ const nonEmpty = (field: string) => S.nonEmpty({ message: () => `${field} is required.` });
26
+ const maxLength = (field: string, length: number) => S.maxLength(length, { message: () => `${field} is too long.` });
27
+
28
+ // -- Types and validation.
29
+ const FeedbackFormSchema = S.Struct({
30
+ name: S.String.pipe(nonEmpty('Name'), maxLength('Name', 256)),
31
+ email: Email.pipe(maxLength('Email', 256)),
32
+ message: S.String.pipe(nonEmpty('Feedback'), maxLength('Feedback', 32_768)),
33
+ });
34
+
35
+ type FeedbackFormState = S.Schema.Type<typeof FeedbackFormSchema>;
36
+
37
+ const initialValues: FeedbackFormState = { name: '', email: '', message: '' };
38
+
39
+ export const FeedbackForm = ({ onClose }: { onClose: () => void }) => {
40
+ const { t } = useTranslation(STATUS_BAR_PLUGIN);
41
+ const translation = mkTranslation(t);
42
+ const dispatch = useIntentDispatcher();
43
+
44
+ const onSubmit = useCallback(
45
+ (values: FeedbackFormState) => {
46
+ void dispatch({ action: 'dxos.org/plugin/observability/capture-feedback', data: values });
47
+ onClose();
48
+ },
49
+ [dispatch, onClose],
50
+ );
51
+
52
+ const { errors, handleSubmit, canSubmit, touched, getInputProps } = useForm<FeedbackFormState>({
53
+ initialValues,
54
+ validate: (values) => validate(FeedbackFormSchema, values),
55
+ onSubmit: (values) => onSubmit(values),
56
+ });
57
+
58
+ return (
59
+ <div role='form' className='p-3 flex flex-col gap-2'>
60
+ <div role='none' className='space-b-1'>
61
+ <Input.Root validationValence={touched.name && errors.name ? 'error' : undefined}>
62
+ <Input.Label>{translation('name label')}</Input.Label>
63
+ <Input.TextInput
64
+ classNames={'text-sm'}
65
+ placeholder={translation('name placeholder')}
66
+ autoFocus
67
+ {...getInputProps('name')}
68
+ />
69
+ <Input.DescriptionAndValidation>
70
+ <Input.Validation>{touched.name && errors.name}</Input.Validation>
71
+ </Input.DescriptionAndValidation>
72
+ </Input.Root>
73
+ </div>
74
+
75
+ <div role='none' className='space-b-1'>
76
+ <Input.Root validationValence={touched.email && errors.email ? 'error' : undefined}>
77
+ <Input.Label>{translation('email input label')}</Input.Label>
78
+ <Input.TextInput
79
+ classNames={'text-sm'}
80
+ placeholder={translation('email input placeholder')}
81
+ {...getInputProps('email')}
82
+ />
83
+ <Input.DescriptionAndValidation>
84
+ <Input.Validation>{touched.email && errors.email}</Input.Validation>
85
+ </Input.DescriptionAndValidation>
86
+ </Input.Root>
87
+ </div>
88
+
89
+ <div role='none' className='space-b-1'>
90
+ <Input.Root validationValence={touched.message && errors.message ? 'error' : undefined}>
91
+ <Input.Label>{translation('feedback text area label')}</Input.Label>
92
+ <Input.TextArea
93
+ classNames={'text-sm'}
94
+ rows={5}
95
+ cols={30}
96
+ placeholder={translation('feedback text area placeholder')}
97
+ {...getInputProps('message')}
98
+ />
99
+ <Input.DescriptionAndValidation>
100
+ <Input.Validation>{touched.message && errors.message}</Input.Validation>
101
+ </Input.DescriptionAndValidation>
102
+ </Input.Root>
103
+ </div>
104
+
105
+ <div role='none' className='space-b-2'>
106
+ <Button
107
+ type='submit'
108
+ variant='primary'
109
+ classNames='is-full flex gap-2'
110
+ disabled={!canSubmit}
111
+ onClick={handleSubmit}
112
+ >
113
+ <span>{translation('send feedback label')}</span>
114
+ <div className='grow' />
115
+ <PaperPlaneTilt className={getSize(5)} />
116
+ </Button>
117
+ <Popover.Close asChild>
118
+ <Button classNames='is-full flex gap-2'>
119
+ <span>{t('cancel label', { ns: 'os' })}</span>
120
+ <div className='grow' />
121
+ <X className={getSize(5)} />
122
+ </Button>
123
+ </Popover.Close>
124
+ </div>
125
+ </div>
126
+ );
127
+ };
@@ -0,0 +1,43 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import '@dxosTheme';
6
+
7
+ import { Mailbox, DiscordLogo, Lightning } from '@phosphor-icons/react';
8
+ import React from 'react';
9
+
10
+ import { withTheme } from '@dxos/storybook-utils';
11
+
12
+ import { StatusBar } from './StatusBar';
13
+
14
+ export default {
15
+ title: 'plugin-status/StatusBar',
16
+ component: StatusBar,
17
+ actions: { argTypesRegex: '^on.*' },
18
+ decorators: [withTheme],
19
+ parameters: { chromatic: { disableSnapshot: false } },
20
+ };
21
+
22
+ export const Normal = (_props: any) => (
23
+ <>
24
+ <StatusBar.Container>
25
+ <StatusBar.EndContent>
26
+ <StatusBar.Button>
27
+ <Mailbox />
28
+ <StatusBar.Text>Quick feedback</StatusBar.Text>
29
+ </StatusBar.Button>
30
+ <a href='https://discord.gg/' target='_blank' rel='noopener noreferrer'>
31
+ <StatusBar.Button>
32
+ <DiscordLogo />
33
+ <StatusBar.Text>Join us on Discord</StatusBar.Text>
34
+ </StatusBar.Button>
35
+ </a>
36
+ <StatusBar.Item>
37
+ <Lightning />
38
+ <StatusBar.Text>Online</StatusBar.Text>
39
+ </StatusBar.Item>
40
+ </StatusBar.EndContent>
41
+ </StatusBar.Container>
42
+ </>
43
+ );
@@ -0,0 +1,110 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { useArrowNavigationGroup } from '@fluentui/react-tabster';
6
+ import { Slot } from '@radix-ui/react-slot';
7
+ import React, { forwardRef, type HTMLAttributes, type ReactNode } from 'react';
8
+
9
+ import { type ThemedClassName } from '@dxos/react-ui';
10
+ import { mx } from '@dxos/react-ui-theme';
11
+
12
+ type StatusBarTextProps = ThemedClassName<{ children: ReactNode }>;
13
+
14
+ const StatusBarText = forwardRef<HTMLSpanElement, StatusBarTextProps>(({ classNames, children }, forwardedRef) => (
15
+ <span className={mx(classNames)} ref={forwardedRef}>
16
+ {children}
17
+ </span>
18
+ ));
19
+
20
+ type StatusBarButtonProps = ThemedClassName<{ children: ReactNode; asChild?: boolean }> &
21
+ HTMLAttributes<HTMLButtonElement>;
22
+
23
+ const StatusBarButton = forwardRef<HTMLButtonElement, StatusBarButtonProps>(
24
+ ({ classNames, children, asChild, ...props }, forwardedRef) => {
25
+ const classes = mx(
26
+ 'flex items-center gap-2 p-1 px-2 rounded-sm',
27
+ 'select-none cursor-pointer',
28
+ 'hover:bg-neutral-75 active:bg-neutral-150',
29
+ 'dark:hover:bg-neutral-750 dark:active:bg-neutral-700',
30
+ classNames,
31
+ );
32
+
33
+ return asChild ? (
34
+ <Slot role='button' ref={forwardedRef} className={classes} {...props}>
35
+ {children}
36
+ </Slot>
37
+ ) : (
38
+ <button className={classes} ref={forwardedRef} {...props}>
39
+ {children}
40
+ </button>
41
+ );
42
+ },
43
+ );
44
+
45
+ export default StatusBarButton;
46
+
47
+ type StatusBarItemProps = ThemedClassName<{ children: ReactNode }> & HTMLAttributes<HTMLDivElement>;
48
+
49
+ const StatusBarItem = forwardRef<HTMLDivElement, StatusBarItemProps>(
50
+ ({ classNames, children, ...props }, forwardedRef) => (
51
+ <div
52
+ role='menuitem'
53
+ className={mx('flex items-center gap-2 p-1 px-2 rounded-sm select-none', classNames)}
54
+ ref={forwardedRef}
55
+ {...props}
56
+ >
57
+ {children}
58
+ </div>
59
+ ),
60
+ );
61
+ type StatusBarContainerProps = ThemedClassName<{ children: ReactNode }>;
62
+
63
+ // TODO(zan): tabable group with tabster
64
+ const StatusBarContainer = forwardRef<HTMLDivElement, StatusBarContainerProps>(
65
+ ({ classNames, children }, forwardedRef) => {
66
+ const groupAttrs = useArrowNavigationGroup({ axis: 'horizontal' });
67
+ return (
68
+ <div
69
+ {...groupAttrs}
70
+ className={mx(
71
+ 'bs-[--statusbar-size]',
72
+ 'flex justify-end items-center gap-2',
73
+ 'surface-base fg-description',
74
+ 'border-bs separator-separator',
75
+ 'text-lg pointer-fine:text-xs',
76
+ classNames,
77
+ )}
78
+ ref={forwardedRef}
79
+ role='menubar'
80
+ >
81
+ {children}
82
+ </div>
83
+ );
84
+ },
85
+ );
86
+
87
+ type StartContentProps = ThemedClassName<{ children: ReactNode }>;
88
+
89
+ const StartContent = forwardRef<HTMLDivElement, StartContentProps>(({ classNames, children }, forwardedRef) => (
90
+ <div role='none' className={mx('flex-grow flex items-center space-x-2', classNames)} ref={forwardedRef}>
91
+ {children}
92
+ </div>
93
+ ));
94
+
95
+ type EndContentProps = ThemedClassName<{ children: ReactNode }>;
96
+
97
+ const EndContent = forwardRef<HTMLDivElement, EndContentProps>(({ classNames, children }, forwardedRef) => (
98
+ <div role='none' className={mx('flex-grow flex items-center justify-end', classNames)} ref={forwardedRef}>
99
+ {children}
100
+ </div>
101
+ ));
102
+
103
+ export const StatusBar = {
104
+ Container: StatusBarContainer,
105
+ Text: StatusBarText,
106
+ Item: StatusBarItem,
107
+ Button: StatusBarButton,
108
+ StartContent,
109
+ EndContent,
110
+ };
@@ -0,0 +1,45 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ import { DiscordLogo, PaperPlaneTilt } from '@phosphor-icons/react';
6
+ import React, { useState } from 'react';
7
+
8
+ import { Surface } from '@dxos/app-framework';
9
+ import { Popover } from '@dxos/react-ui';
10
+
11
+ import { FeedbackForm } from './FeedbackForm';
12
+ import { StatusBar } from './StatusBar';
13
+
14
+ // TODO(wittjosiah): Rename.
15
+ export const StatusBarImpl = () => {
16
+ const [open, setOpen] = useState(false);
17
+
18
+ // TODO(wittjosiah): Factor out feedback and discord buttons.
19
+ return (
20
+ <Popover.Root open={open} onOpenChange={setOpen}>
21
+ <StatusBar.Container>
22
+ <StatusBar.EndContent>
23
+ <Popover.Trigger asChild>
24
+ <StatusBar.Button aria-label='Give feedback about composer' data-joyride='welcome/feedback'>
25
+ <PaperPlaneTilt />
26
+ <StatusBar.Text classNames='hidden sm:block'>Feedback</StatusBar.Text>
27
+ </StatusBar.Button>
28
+ </Popover.Trigger>
29
+ {/* TODO(zan): Configure this label? */}
30
+ <StatusBar.Button aria-label='Open DXOS Discord' asChild>
31
+ <a href='https://discord.gg/zsxWrKjteV' target='_blank' rel='noopener noreferrer'>
32
+ <DiscordLogo />
33
+ <StatusBar.Text classNames='hidden sm:block'>Discord</StatusBar.Text>
34
+ </a>
35
+ </StatusBar.Button>
36
+ </StatusBar.EndContent>
37
+ <Surface role='status' />
38
+ </StatusBar.Container>
39
+ <Popover.Content classNames='shadow-lg'>
40
+ <FeedbackForm onClose={() => setOpen(false)} />
41
+ <Popover.Arrow />
42
+ </Popover.Content>
43
+ </Popover.Root>
44
+ );
45
+ };
@@ -0,0 +1,6 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './StatusBar';
6
+ export * from './StatusBarImpl';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2024 DXOS.org
3
+ //
4
+
5
+ export * from './useForm';