diff --git a/scm-ui/ui-components/src/DateFromNow.stories.tsx b/scm-ui/ui-components/src/Date.stories.tsx
similarity index 68%
rename from scm-ui/ui-components/src/DateFromNow.stories.tsx
rename to scm-ui/ui-components/src/Date.stories.tsx
index 8259bf6240..4d2fe16fbe 100644
--- a/scm-ui/ui-components/src/DateFromNow.stories.tsx
+++ b/scm-ui/ui-components/src/Date.stories.tsx
@@ -24,25 +24,41 @@
import React from "react";
import DateFromNow from "./DateFromNow";
import { storiesOf } from "@storybook/react";
+import DateShort from "./DateShort";
+import styled from "styled-components";
const baseProps = {
timeZone: "Europe/Berlin",
baseDate: "2019-10-12T13:56:42+02:00"
};
-storiesOf("DateFromNow", module).add("Default", () => (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-));
+const dates = [
+ "2009-06-30T18:30:00+02:00",
+ "2019-06-30T18:30:00+02:00",
+ "2019-10-12T13:56:40+02:00",
+ "2019-10-11T13:56:40+02:00"
+];
+
+const Wrapper = styled.div`
+ padding: 2rem;
+`;
+
+storiesOf("Date", module)
+ .add("Date from now", () => (
+
+ {dates.map(d => (
+
+
+
+ ))}
+
+ ))
+ .add("Short", () => (
+
+ {dates.map(d => (
+
+
+
+ ))}
+
+ ));
diff --git a/scm-ui/ui-components/src/dates.ts b/scm-ui/ui-components/src/DateElement.ts
similarity index 78%
rename from scm-ui/ui-components/src/dates.ts
rename to scm-ui/ui-components/src/DateElement.ts
index f79fe25e63..5c4dd25afa 100644
--- a/scm-ui/ui-components/src/dates.ts
+++ b/scm-ui/ui-components/src/DateElement.ts
@@ -22,21 +22,10 @@
* SOFTWARE.
*/
import styled from "styled-components";
-import { parseISO } from "date-fns";
-export type DateInput = Date | string;
-
-export const ShortDateFormat = "yyyy-MM-dd";
-export const FullDateFormat = "yyyy-MM-dd HH:mm:ss";
-
-export const DateElement = styled.time`
+const DateElement = styled.time`
border-bottom: 1px dotted rgba(219, 219, 219);
cursor: help;
`;
-export const toDate = (value: DateInput): Date => {
- if (value instanceof Date) {
- return value;
- }
- return parseISO(value);
-};
+export default DateElement;
diff --git a/scm-ui/ui-components/src/DateFromNow.tsx b/scm-ui/ui-components/src/DateFromNow.tsx
index b01cfcdc7b..8b5a6c0a37 100644
--- a/scm-ui/ui-components/src/DateFromNow.tsx
+++ b/scm-ui/ui-components/src/DateFromNow.tsx
@@ -21,100 +21,26 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React from "react";
-import { withTranslation, WithTranslation } from "react-i18next";
-import { formatDistance, format, Locale } from "date-fns";
-import { enUS, de, es } from "date-fns/locale";
-import { DateInput, DateElement, FullDateFormat, toDate } from "./dates";
-type LocaleMap = {
- [key: string]: Locale;
-};
+import React, { FC } from "react";
+import useDateFormatter, { DateProps } from "./useDateFormatter";
+import DateElement from "./DateElement";
-export const supportedLocales: LocaleMap = {
- enUS,
- en: enUS,
- de,
- es
-};
-
-type Props = WithTranslation & {
- date?: DateInput;
- timeZone?: string;
-
- /**
- * baseDate is the date from which the distance is calculated,
- * default is the current time (new Date()). This property
- * is required to keep snapshots tests green over the time on
- * ci server.
- */
- baseDate?: DateInput;
+type Props = DateProps & {
className?: string;
};
-type Options = {
- addSuffix: boolean;
- locale: Locale;
- timeZone?: string;
-};
-
-export const chooseLocale = (language: string, languages?: string[]) => {
- for (const lng of languages || []) {
- const locale = supportedLocales[lng];
- if (locale) {
- return locale;
- }
- }
-
- const locale = supportedLocales[language];
- if (locale) {
- return locale;
- }
-
- return enUS;
-};
-
-class DateFromNow extends React.Component {
- getLocale = (): Locale => {
- const { i18n } = this.props;
- return chooseLocale(i18n.language, i18n.languages);
- };
-
- createOptions = () => {
- const { timeZone } = this.props;
- const options: Options = {
- addSuffix: true,
- locale: this.getLocale()
- };
- if (timeZone) {
- options.timeZone = timeZone;
- }
- return options;
- };
-
- getBaseDate = () => {
- const { baseDate } = this.props;
- if (baseDate) {
- return toDate(baseDate);
- }
- return new Date();
- };
-
- render() {
- const { date, className } = this.props;
- if (date) {
- const isoDate = toDate(date);
- const options = this.createOptions();
- const distance = formatDistance(isoDate, this.getBaseDate(), options);
- const formatted = format(isoDate, FullDateFormat, options);
- return (
-
- {distance}
-
- );
- }
+const DateFromNow: FC = ({ className, ...dateProps }) => {
+ const formatter = useDateFormatter(dateProps);
+ if (!formatter) {
return null;
}
-}
-export default withTranslation()(DateFromNow);
+ return (
+
+ {formatter.formatDistance()}
+
+ );
+};
+
+export default DateFromNow;
diff --git a/scm-ui/ui-components/src/DateShort.tsx b/scm-ui/ui-components/src/DateShort.tsx
index 86510177ad..d0da04cb87 100644
--- a/scm-ui/ui-components/src/DateShort.tsx
+++ b/scm-ui/ui-components/src/DateShort.tsx
@@ -21,23 +21,24 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
-import React, { FC } from "react";
-import { format } from "date-fns";
-import { toDate, ShortDateFormat, FullDateFormat, DateElement } from "./dates";
-type Props = {
- value?: Date | string;
+import React, { FC } from "react";
+import useDateFormatter, { DateProps } from "./useDateFormatter";
+import DateElement from "./DateElement";
+
+type Props = DateProps & {
className?: string;
};
-const DateShort: FC = ({ value, className }) => {
- if (!value) {
+const DateShort: FC = ({ className, ...dateProps }) => {
+ const formatter = useDateFormatter(dateProps);
+ if (!formatter) {
return null;
}
- const date = toDate(value);
+
return (
-
- {format(date, ShortDateFormat)}
+
+ {formatter.formatShort()}
);
};
diff --git a/scm-ui/ui-components/src/repos/annotate/Annotate.tsx b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx
index ab1cc69696..9ee38a7733 100644
--- a/scm-ui/ui-components/src/repos/annotate/Annotate.tsx
+++ b/scm-ui/ui-components/src/repos/annotate/Annotate.tsx
@@ -28,7 +28,7 @@ import { Repository, AnnotatedSource, AnnotatedLine } from "@scm-manager/ui-type
// @ts-ignore
import { LightAsync as ReactSyntaxHighlighter, createElement } from "react-syntax-highlighter";
import { arduinoLight } from "react-syntax-highlighter/dist/cjs/styles/hljs";
-import { DateInput } from "../../dates";
+import { DateInput } from "../../useDateFormatter";
import Popover from "./Popover";
import AnnotateLine from "./AnnotateLine";
import { Action } from "./actions";
diff --git a/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx b/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx
index cb6b2ffb86..7d8401aa70 100644
--- a/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx
+++ b/scm-ui/ui-components/src/repos/annotate/AnnotateLine.tsx
@@ -134,7 +134,7 @@ const AnnotateLine: FC = ({ annotation, showAnnotation, dispatch, nr, chi
{annotation.author.name}
{" "}
-
+
{" "}
{nr} {children}
diff --git a/scm-ui/ui-components/src/repos/annotate/Popover.tsx b/scm-ui/ui-components/src/repos/annotate/Popover.tsx
index b4edb785cb..1c4590af04 100644
--- a/scm-ui/ui-components/src/repos/annotate/Popover.tsx
+++ b/scm-ui/ui-components/src/repos/annotate/Popover.tsx
@@ -27,7 +27,7 @@ import styled from "styled-components";
import { Link } from "react-router-dom";
import DateFromNow from "../../DateFromNow";
import { SingleContributor } from "../changesets";
-import { DateInput } from "../../dates";
+import { DateInput } from "../../useDateFormatter";
import { Repository, AnnotatedLine } from "@scm-manager/ui-types";
import AuthorImage from "./AuthorImage";
import { Action } from "./actions";
diff --git a/scm-ui/ui-components/src/DateFromNow.test.ts b/scm-ui/ui-components/src/useDateFormatter.test.ts
similarity index 96%
rename from scm-ui/ui-components/src/DateFromNow.test.ts
rename to scm-ui/ui-components/src/useDateFormatter.test.ts
index 19acc08fda..4f0238332c 100644
--- a/scm-ui/ui-components/src/DateFromNow.test.ts
+++ b/scm-ui/ui-components/src/useDateFormatter.test.ts
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
-import { chooseLocale, supportedLocales } from "./DateFromNow";
+import { chooseLocale, supportedLocales } from "./useDateFormatter";
describe("test choose locale", () => {
it("should choose de", () => {
diff --git a/scm-ui/ui-components/src/useDateFormatter.ts b/scm-ui/ui-components/src/useDateFormatter.ts
new file mode 100644
index 0000000000..798b2d5d39
--- /dev/null
+++ b/scm-ui/ui-components/src/useDateFormatter.ts
@@ -0,0 +1,126 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2020-present Cloudogu GmbH and Contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+import { useTranslation } from "react-i18next";
+import { enUS, de, es } from "date-fns/locale";
+import { formatDistance, format, Locale, parseISO } from "date-fns";
+
+type LocaleMap = {
+ [key: string]: Locale;
+};
+
+export const supportedLocales: LocaleMap = {
+ enUS,
+ en: enUS,
+ de,
+ es
+};
+
+type Options = {
+ addSuffix: boolean;
+ locale: Locale;
+ timeZone?: string;
+};
+
+export const chooseLocale = (language: string, languages?: string[]) => {
+ for (const lng of languages || []) {
+ const locale = supportedLocales[lng];
+ if (locale) {
+ return locale;
+ }
+ }
+
+ const locale = supportedLocales[language];
+ if (locale) {
+ return locale;
+ }
+
+ return enUS;
+};
+
+export type DateInput = Date | string;
+
+export type DateProps = {
+ date?: DateInput;
+ timeZone?: string;
+
+ /**
+ * baseDate is the date from which the distance is calculated,
+ * default is the current time (new Date()). This property
+ * is required to keep snapshots tests green over the time on
+ * ci server.
+ */
+ baseDate?: DateInput;
+};
+
+const createOptions = (locale: Locale, timeZone?: string) => {
+ const options: Options = {
+ addSuffix: true,
+ locale
+ };
+ if (timeZone) {
+ options.timeZone = timeZone;
+ }
+ return options;
+};
+
+const createBaseDate = (baseDate?: DateInput) => {
+ if (baseDate) {
+ return toDate(baseDate);
+ }
+ return new Date();
+};
+
+const toDate = (value: DateInput): Date => {
+ if (value instanceof Date) {
+ return value;
+ }
+ return parseISO(value);
+};
+
+const useDateFormatter = ({ date, baseDate, timeZone }: DateProps) => {
+ const { i18n } = useTranslation();
+ if (!date) {
+ return null;
+ }
+
+ const isoDate = toDate(date);
+ const base = createBaseDate(baseDate);
+
+ const locale = chooseLocale(i18n.language, i18n.languages);
+ const options = createOptions(locale, timeZone);
+ return {
+ formatShort() {
+ return format(isoDate, "yyyy-MM-dd", options);
+ },
+ formatFull() {
+ return format(isoDate, "yyyy-MM-dd HH:mm:ss", options);
+ },
+ formatDistance() {
+ return formatDistance(isoDate, base, options);
+ }
+ };
+};
+
+export default useDateFormatter;